Skip to content
This repository was archived by the owner on Oct 13, 2024. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ cython_debug/
Contents/Info.plist

# Remove plexhints files
plexhints-temp
plexhints/
plexhints-temp/
*cache.sqlite

# Remove python modules
Expand Down
1 change: 1 addition & 0 deletions Contents/Code/default_prefs.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
enum_webapp_locale='en',
str_webapp_http_host='0.0.0.0',
int_webapp_http_port='9494',
bool_webapp_require_login='False',
bool_webapp_log_werkzeug_messages='False',
bool_migrate_locked_themes='False',
bool_migrate_locked_collection_fields='False',
Expand Down
53 changes: 53 additions & 0 deletions Contents/Code/plex_api_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from plexapi.base import PlexPartialObject
from plexapi.exceptions import BadRequest
import plexapi.server
from plexapi.myplex import MyPlexAccount # must be imported after plexapi.server otherwise it breaks
from plexapi.utils import reverseSearchType

# local imports
Expand Down Expand Up @@ -597,6 +598,58 @@ def get_plex_item(rating_key):
return item


def get_user_info(token):
# type: (str) -> Optional[MyPlexAccount]
"""
Get the Plex user info.

Parameters
----------
token : str
The Plex token.

Returns
-------
Optional[MyPlexAccount]
The Plex user info.

Examples
--------
>>> get_user_info(token='...')
...
"""
try:
return MyPlexAccount(token=token)
except Exception as e:
Log.Error('Error getting user info: {}'.format(e))
return None


def is_server_owner(user):
# type: (MyPlexAccount) -> bool
"""
Check if the user is the owner of the Plex server.

Parameters
----------
user : MyPlexAccount
The Plex user info.

Returns
-------
py:class:`bool`
True if the user is the owner of the Plex server, False otherwise.

Examples
--------
>>> is_server_owner(user=...)
...
"""
plex = setup_plexapi()

return plex.account().username in {user.email, user.username}


def process_queue():
# type: () -> None
"""
Expand Down
165 changes: 163 additions & 2 deletions Contents/Code/webapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import logging
import os
from threading import Lock, Thread
from typing import Optional
import uuid

# plex debugging
try:
Expand All @@ -23,7 +25,7 @@

# lib imports
import flask
from flask import Flask, Response, render_template, send_from_directory
from flask import Flask, Response, render_template as flask_render_template, send_from_directory, session
from flask_babel import Babel
import polib
from six.moves.urllib.parse import quote_plus
Expand All @@ -32,10 +34,39 @@
# local imports
from constants import contributes_to, issue_urls, plugin_directory, plugin_identifier, themerr_data_directory
import general_helper
from plex_api_helper import get_database_info, setup_plexapi
from plex_api_helper import get_database_info, get_user_info, is_server_owner, setup_plexapi
import themerr_db_helper
import tmdb_helper


def render_template(*args, **kwargs):
# type: (str, str) -> flask.render_template
"""
Render a template.

This function is a wrapper for flask.render_template that adds various useful globals to the template context.

Parameters
----------
*args : str
The template name.
**kwargs : str
The template context.

Returns
-------
flask.render_template
The rendered template.

Examples
--------
>>> render_template('home.html', title='Home', items=items)
"""
kwargs['Prefs'] = Prefs
kwargs['is_logged_in'] = is_logged_in()
return flask_render_template(*args, **kwargs)


# setup flask app
app = Flask(
import_name=__name__,
Expand Down Expand Up @@ -107,6 +138,32 @@
database_cache_lock = Lock()


def create_secret():
"""
Create secret file with random uuid.

Examples
--------
>>> create_secret()
"""
secret_file = os.path.join(themerr_data_directory, 'secret.json')
try:
with open(secret_file, 'r') as f:
app.secret_key = json.load(f)['secret']
except Exception:
# create random secret
Log.Info('Creating random secret')
app.secret_key = uuid.uuid4().hex
try:
with open(secret_file, 'w') as f:
json.dump({'secret': app.secret_key}, f)
except Exception as e:
Log.Error('Error saving secret: {}'.format(e))


create_secret()


responses = {
500: Response(response='Internal Server Error', status=500, mimetype='text/plain')
}
Expand Down Expand Up @@ -495,3 +552,107 @@ def translations():
return Response(response=json.dumps(data),
status=200,
mimetype='application/json')


def is_logged_in():
# type: () -> bool
"""
Check if the user is logged in.

Returns
-------
py:class:`bool`
True if the user is logged in, otherwise False.

Examples
--------
>>> is_logged_in()
"""
if not Prefs['bool_webapp_require_login']:
return True

if "token" not in session:
return False

token = session["token"]
user = get_user_info(token=token)
logged_in = user and is_server_owner(user=user)
return logged_in


@app.route("/logout", methods=["GET"])
def logout():
# type: () -> Response
"""
Logout the user.

Returns
-------
Response
The logout response.

Examples
--------
>>> logout()
"""
session.pop("token", None)
return flask.redirect(flask.url_for('home'))


@app.before_request
def check_login_status():
# type: () -> Optional[flask.redirect]
"""
Check if the user is logged in.

If the user is not logged in, redirect to the login page.

Examples
--------
>>> check_login_status()
"""
if not Prefs['bool_webapp_require_login']:
return

if flask.request.path.startswith('/web'):
return

if flask.request.path in {'/login', '/logout'}:
return

if not is_logged_in():
# not logged in, redirect to login page
return flask.redirect(flask.url_for('login', redirect_uri=flask.request.path))


@app.route("/login", methods=["GET"])
def login(redirect_uri="/"):
# type: (str) -> render_template
"""
Serve the login page.

Returns
-------
render_template
The rendered page.

Notes
-----
The following routes trigger this function.

- `/login`

Examples
--------
>>> login()
"""
return render_template('login.html', title='Login', redirect_uri=redirect_uri)


@app.route("/login", methods=["POST"])
def login_post():
session.permanent = True
session["token"] = flask.request.form["token"]
if not is_logged_in():
return flask.Response(status=401)
return flask.Response(status=200)
8 changes: 8 additions & 0 deletions Contents/DefaultPrefs.json
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,14 @@
"default": "9494",
"secure": "false"
},
{
"id": "bool_webapp_require_login",
"type": "bool",
"label": "Require login to access Web UI",
"default": "True",
"secure": "false",
"hidden": "true"
},
{
"id": "bool_webapp_log_werkzeug_messages",
"type": "bool",
Expand Down
Loading