From fe5e98f766d5c3a0d374e76aa4a328d81107429d Mon Sep 17 00:00:00 2001 From: westsurname <155189104+westsurname@users.noreply.github.com> Date: Thu, 1 Aug 2024 23:04:57 -0400 Subject: [PATCH 1/9] Auto-authentication and move authentication to path --- plex_authentication.py | 56 +++++++++++++++++++--------- plex_request.py | 6 ++- plex_request_nginx.conf | 82 ++++++++++++++++++++++++++++++++++------- shared/shared.py | 17 ++++++--- 4 files changed, 123 insertions(+), 38 deletions(-) diff --git a/plex_authentication.py b/plex_authentication.py index 9dce503..88d44ba 100644 --- a/plex_authentication.py +++ b/plex_authentication.py @@ -1,11 +1,13 @@ import requests import json import urllib.parse -from flask import Flask, jsonify, redirect, url_for +from flask import Flask, jsonify, redirect, url_for, request from shared.shared import watchlist, plexHeaders, tokensFilename from shared.overseerr import getUserForPlexToken from shared.plex import getServerToken +from werkzeug.serving import run_simple from werkzeug.middleware.proxy_fix import ProxyFix +from werkzeug.middleware.dispatcher import DispatcherMiddleware # instantiate the app app = Flask(__name__) @@ -32,29 +34,47 @@ def setupComplete(pin): authToken = pinRequest.json()['authToken'] if authToken: - user = getUserForPlexToken(authToken) - serverToken = getServerToken(authToken) - userId = user['id'] + return handleToken(authToken) - with open(tokensFilename, 'r+') as tokensFile: - tokens = json.load(tokensFile) - token = tokens.get(userId, { 'etag': '' }) - token['token'] = authToken - token['serverToken'] = serverToken + return jsonify('There was an error, please try again.') - if serverToken == authToken: - token['owner'] = True +@app.route('/token', methods=['POST']) +def receiveToken(): + token = request.json.get('token') + if token: + return handleToken(token) + else: + return createResponse({'error': 'No token provided'}, 400) - tokens[userId] = token - tokensFile.seek(0) - json.dump(tokens, tokensFile) - tokensFile.truncate() +def handleToken(token): + user = getUserForPlexToken(token) + serverToken = getServerToken(token) + userId = user['id'] - return jsonify('Successfully authenticated!') + updateTokensFile(userId, token, serverToken) - return jsonify('There was an error, please try again.') + return createResponse({'message': 'Token received and stored successfully'}, 201) + +def updateTokensFile(userId, token, serverToken): + with open(tokensFilename, 'r+') as tokensFile: + tokens = json.load(tokensFile) + tokenEntry = tokens.get(userId, {'etag': ''}) + tokenEntry['token'] = token + tokenEntry['serverToken'] = serverToken + tokens[userId] = tokenEntry + tokensFile.seek(0) + json.dump(tokens, tokensFile) + tokensFile.truncate() + +def createResponse(data, statusCode): + response = jsonify(data), statusCode + # response[0].headers.add('Access-Control-Allow-Origin', '*') + # response[0].headers.add('Access-Control-Allow-Headers', 'Content-Type') + # response[0].headers.add('Access-Control-Allow-Methods', 'POST, OPTIONS') + return response app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1, x_prefix=1) - +app.wsgi_app = DispatcherMiddleware(run_simple, {'/auth': app.wsgi_app}) + if __name__ == '__main__': app.run('127.0.0.1', 12598) \ No newline at end of file diff --git a/plex_request.py b/plex_request.py index 945d3aa..407bbd6 100644 --- a/plex_request.py +++ b/plex_request.py @@ -258,6 +258,8 @@ def all(): urlSuffix = "/children" if mediaTypeNum == mediaTypeNums['season'] else "" metadataAllRequest = requests.get(f"{plex['metadataHost']}library/metadata/{guid}{urlSuffix}", headers=metadataHeaders, params=args) if metadataAllRequest.status_code == 200: + libraryId = plex['serverMovieLibraryId'] if mediaType == 'movie' else plex['serverTvShowLibraryId'] + additionalMetadata = metadataAllRequest.json()['MediaContainer']['Metadata'][0] if mediaTypeNum == mediaTypeNums['season'] or mediaTypeNum == mediaTypeNums['episode']: additionalMetadata['key'] = f"/library/request/{mediaType}/{mediaTypeNum}/{guid}/season/{season}" @@ -266,8 +268,8 @@ def all(): additionalMetadata['ratingKey'] = "12065" additionalMetadata['librarySectionTitle'] = "Request Season :" if mediaTypeNum == mediaTypeNums['episode'] else "Request :" - additionalMetadata['librarySectionID'] = plex['serverMovieLibraryId'] if mediaType == 'movie' else plex['serverTvShowLibraryId'] - additionalMetadata['librarySectionKey'] = f"/library/sections/{additionalMetadata['librarySectionID']}" + additionalMetadata['librarySectionID'] = libraryId + additionalMetadata['librarySectionKey'] = f"/library/sections/{libraryId}" additionalMetadata['Media'] = [{ "videoResolution": "Request Season :" if mediaTypeNum == mediaTypeNums['episode'] else "Request :" }] diff --git a/plex_request_nginx.conf b/plex_request_nginx.conf index bbea33b..4bf215b 100644 --- a/plex_request_nginx.conf +++ b/plex_request_nginx.conf @@ -98,19 +98,23 @@ server { proxy_pass ${plex_server_host}; proxy_read_timeout 86400; - # add_header 'Access-Control-Allow-Origin' '*'; - # add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE, PATCH'; - # add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; - - # if ($request_method = 'OPTIONS') { - # add_header 'Access-Control-Allow-Origin' '*'; - # add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE, PATCH'; - # add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization'; - # add_header 'Access-Control-Max-Age' 1728000; - # add_header 'Content-Type' 'text/plain; charset=utf-8'; - # add_header 'Content-Length' 0; - # return 204; - # } + proxy_hide_header 'Access-Control-Allow-Origin'; + proxy_hide_header 'Access-Control-Allow-Methods'; + proxy_hide_header 'Access-Control-Allow-Headers'; + + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE, PATCH'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; + + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE, PATCH'; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain; charset=utf-8'; + add_header 'Content-Length' 0; + return 204; + } # return 302 https://$request_uri; @@ -125,6 +129,58 @@ server { # error_page 401 =200 /ldaplogin; } + location /auth { + proxy_pass http://plex_authentication:8000; + } + + location /web/index.html { + proxy_pass http://localhost:12575/web/index.html; + + access_log logs/plex.access.log; + + sub_filter '' ''; + sub_filter_once on; + } + location = /library/all { proxy_pass http://plex_request:8000; diff --git a/shared/shared.py b/shared/shared.py index c8c20e8..d81ca9a 100644 --- a/shared/shared.py +++ b/shared/shared.py @@ -7,12 +7,19 @@ default_pattern = r"<[a-z0-9_]+>" -@env.parser_for("string") -def stringEnvParser(value): +def commonEnvParser(value): if value is not None and re.match(default_pattern, value): return None return value +@env.parser_for("integer") +def integerEnvParser(value): + return commonEnvParser(value) + +@env.parser_for("string") +def stringEnvParser(value): + return commonEnvParser(value) + watchlist = { 'plexProduct': env.string('WATCHLIST_PLEX_PRODUCT', default=None), 'plexVersion': env.string('WATCHLIST_PLEX_VERSION', default=None), @@ -24,9 +31,9 @@ def stringEnvParser(value): 'radarrPath': env.string('BLACKHOLE_RADARR_PATH', default=None), 'sonarrPath': env.string('BLACKHOLE_SONARR_PATH', default=None), 'failIfNotCached': env.bool('BLACKHOLE_FAIL_IF_NOT_CACHED', default=None), - 'rdMountRefreshSeconds': env.int('BLACKHOLE_RD_MOUNT_REFRESH_SECONDS', default=None), - 'waitForTorrentTimeout': env.int('BLACKHOLE_WAIT_FOR_TORRENT_TIMEOUT', default=None), - 'historyPageSize': env.int('BLACKHOLE_HISTORY_PAGE_SIZE', default=None), + 'rdMountRefreshSeconds': env.integer('BLACKHOLE_RD_MOUNT_REFRESH_SECONDS', default=None), + 'waitForTorrentTimeout': env.integer('BLACKHOLE_WAIT_FOR_TORRENT_TIMEOUT', default=None), + 'historyPageSize': env.integer('BLACKHOLE_HISTORY_PAGE_SIZE', default=None), } server = { From d7790f57cc5debe9e9fba1d62248ca149bc3fbdd Mon Sep 17 00:00:00 2001 From: westsurname <155189104+westsurname@users.noreply.github.com> Date: Fri, 2 Aug 2024 10:00:53 -0400 Subject: [PATCH 2/9] Removed owner check --- shared/overseerr.py | 2 +- shared/shared.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/overseerr.py b/shared/overseerr.py index 3ae2cb9..32c00e0 100644 --- a/shared/overseerr.py +++ b/shared/overseerr.py @@ -18,7 +18,7 @@ def getUserForPlexToken(token): def getUserForPlexServerToken(serverToken): with open(tokensFilename, 'r') as tokensFile: tokens = json.load(tokensFile).values() - token = next((token['token'] for token in tokens if token['serverToken'] == serverToken), next(token['token'] for token in tokens if token['owner'] == True)) + token = next((token['token'] for token in tokens if token['serverToken'] == serverToken), plex['serverApiKey']) return getUserForPlexToken(token) diff --git a/shared/shared.py b/shared/shared.py index d81ca9a..98f5c65 100644 --- a/shared/shared.py +++ b/shared/shared.py @@ -46,8 +46,8 @@ def stringEnvParser(value): 'serverHost': env.string('PLEX_SERVER_HOST', default=None), 'serverMachineId': env.string('PLEX_SERVER_MACHINE_ID', default=None), 'serverApiKey': env.string('PLEX_SERVER_API_KEY', default=None), - 'serverMovieLibraryId': env.int('PLEX_SERVER_MOVIE_LIBRARY_ID', default=None), - 'serverTvShowLibraryId': env.int('PLEX_SERVER_TV_SHOW_LIBRARY_ID', default=None) + 'serverMovieLibraryId': env.integer('PLEX_SERVER_MOVIE_LIBRARY_ID', default=None), + 'serverTvShowLibraryId': env.integer('PLEX_SERVER_TV_SHOW_LIBRARY_ID', default=None) } overseerr = { From aa8ac42122219045feea042ee0899fa3cf569ee1 Mon Sep 17 00:00:00 2001 From: westsurname <155189104+westsurname@users.noreply.github.com> Date: Fri, 2 Aug 2024 12:58:37 -0400 Subject: [PATCH 3/9] Fix for env parser --- shared/shared.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/shared/shared.py b/shared/shared.py index 98f5c65..bc28432 100644 --- a/shared/shared.py +++ b/shared/shared.py @@ -7,14 +7,14 @@ default_pattern = r"<[a-z0-9_]+>" -def commonEnvParser(value): +def commonEnvParser(value, convert): if value is not None and re.match(default_pattern, value): return None - return value + return convert(value) if convert else value @env.parser_for("integer") def integerEnvParser(value): - return commonEnvParser(value) + return commonEnvParser(value, int) @env.parser_for("string") def stringEnvParser(value): From 9c264c5f5d7c5c70394686c4f4a501993c7c589a Mon Sep 17 00:00:00 2001 From: westsurname <155189104+westsurname@users.noreply.github.com> Date: Sat, 3 Aug 2024 22:55:16 -0400 Subject: [PATCH 4/9] Fix --- shared/shared.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/shared.py b/shared/shared.py index bc28432..b496f47 100644 --- a/shared/shared.py +++ b/shared/shared.py @@ -7,7 +7,7 @@ default_pattern = r"<[a-z0-9_]+>" -def commonEnvParser(value, convert): +def commonEnvParser(value, convert=None): if value is not None and re.match(default_pattern, value): return None return convert(value) if convert else value From fac2add90d6e423f4111354be1125c9629c6d96a Mon Sep 17 00:00:00 2001 From: westsurname <155189104+westsurname@users.noreply.github.com> Date: Sat, 3 Aug 2024 23:41:34 -0400 Subject: [PATCH 5/9] Fix --- plex_request_nginx.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plex_request_nginx.conf b/plex_request_nginx.conf index 4bf215b..68da935 100644 --- a/plex_request_nginx.conf +++ b/plex_request_nginx.conf @@ -134,7 +134,7 @@ server { } location /web/index.html { - proxy_pass http://localhost:12575/web/index.html; + proxy_pass ${plex_server_host}/web/index.html; access_log logs/plex.access.log; From e5e16a185661f7f8b1f60220964c19e8c975cf1b Mon Sep 17 00:00:00 2001 From: westsurname <155189104+westsurname@users.noreply.github.com> Date: Sun, 4 Aug 2024 00:30:24 -0400 Subject: [PATCH 6/9] Fix --- plex_authentication.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/plex_authentication.py b/plex_authentication.py index 88d44ba..de76546 100644 --- a/plex_authentication.py +++ b/plex_authentication.py @@ -5,7 +5,6 @@ from shared.shared import watchlist, plexHeaders, tokensFilename from shared.overseerr import getUserForPlexToken from shared.plex import getServerToken -from werkzeug.serving import run_simple from werkzeug.middleware.proxy_fix import ProxyFix from werkzeug.middleware.dispatcher import DispatcherMiddleware @@ -68,13 +67,10 @@ def updateTokensFile(userId, token, serverToken): def createResponse(data, statusCode): response = jsonify(data), statusCode - # response[0].headers.add('Access-Control-Allow-Origin', '*') - # response[0].headers.add('Access-Control-Allow-Headers', 'Content-Type') - # response[0].headers.add('Access-Control-Allow-Methods', 'POST, OPTIONS') return response app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1, x_prefix=1) -app.wsgi_app = DispatcherMiddleware(run_simple, {'/auth': app.wsgi_app}) +app.wsgi_app = DispatcherMiddleware(None, {'/auth': app.wsgi_app}) if __name__ == '__main__': app.run('127.0.0.1', 12598) \ No newline at end of file From 22fb572baab008bb13061a6e44a3a48c574fe150 Mon Sep 17 00:00:00 2001 From: westsurname <155189104+westsurname@users.noreply.github.com> Date: Sun, 4 Aug 2024 00:45:48 -0400 Subject: [PATCH 7/9] Fix --- plex_authentication.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plex_authentication.py b/plex_authentication.py index de76546..36e9006 100644 --- a/plex_authentication.py +++ b/plex_authentication.py @@ -7,6 +7,7 @@ from shared.plex import getServerToken from werkzeug.middleware.proxy_fix import ProxyFix from werkzeug.middleware.dispatcher import DispatcherMiddleware +from werkzeug.exceptions import NotFound # instantiate the app app = Flask(__name__) @@ -70,7 +71,7 @@ def createResponse(data, statusCode): return response app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1, x_prefix=1) -app.wsgi_app = DispatcherMiddleware(None, {'/auth': app.wsgi_app}) +app.wsgi_app = DispatcherMiddleware(NotFound(), {'/auth': app.wsgi_app}) if __name__ == '__main__': app.run('127.0.0.1', 12598) \ No newline at end of file From cd1240c310d7ae3d46141140c2848a6607d9fbac Mon Sep 17 00:00:00 2001 From: westsurname <155189104+westsurname@users.noreply.github.com> Date: Sun, 4 Aug 2024 01:27:54 -0400 Subject: [PATCH 8/9] Move plex request and auth to sockets and more middlware fixes --- .dockerignore | 1 + Dockerfile.plex_authentication | 7 ++----- Dockerfile.plex_request | 7 ++----- docker-compose.yml | 8 ++++---- plex_authentication.py | 3 +-- plex_request_nginx.conf | 10 +++++----- sockets/.gitignore | 4 ++++ 7 files changed, 19 insertions(+), 21 deletions(-) create mode 100644 sockets/.gitignore diff --git a/.dockerignore b/.dockerignore index 9e5de6a..9697da3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,3 +5,4 @@ docker-compose.yml Dockerfile.* README.md shared/tokens.json +sockets/ \ No newline at end of file diff --git a/Dockerfile.plex_authentication b/Dockerfile.plex_authentication index 5367cd0..ad142df 100644 --- a/Dockerfile.plex_authentication +++ b/Dockerfile.plex_authentication @@ -19,8 +19,5 @@ RUN grep -E "#.*($SERVICE_NAME|all)" requirements.txt | awk '{print $0}' > servi # Copy the rest of the application COPY . . -# Expose port 8000 to the outside world -EXPOSE 8000 - -# Run gunicorn on port 8000, add more workers if required -CMD ["gunicorn", "--bind", "0.0.0.0:8000", "plex_authentication_wsgi:app"] +# Run gunicorn using Unix socket +CMD ["gunicorn", "--bind", "unix:/app/sockets/plex_authentication.sock", "plex_authentication_wsgi:app"] \ No newline at end of file diff --git a/Dockerfile.plex_request b/Dockerfile.plex_request index 7728bb3..95ed726 100644 --- a/Dockerfile.plex_request +++ b/Dockerfile.plex_request @@ -19,8 +19,5 @@ RUN grep -E "#.*($SERVICE_NAME|all)" requirements.txt | awk '{print $0}' > servi # Copy the rest of the application COPY . . -# Expose port 8000 to the outside world -EXPOSE 8000 - -# Run gunicorn on port 8000, add more workers if required -CMD ["gunicorn", "--bind", "0.0.0.0:8000", "plex_request_wsgi:app"] +# Run gunicorn using Unix socket +CMD ["gunicorn", "--bind", "unix:/app/sockets/plex_request.sock", "plex_request_wsgi:app"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index ee1962b..03c8c69 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -159,8 +159,7 @@ services: pull_policy: always volumes: - ./shared/tokens.json:/app/shared/tokens.json - ports: - - 8010:8000 + - ./sockets:/app/sockets env_file: - .env restart: unless-stopped @@ -175,8 +174,7 @@ services: pull_policy: always volumes: - ./shared/tokens.json:/app/shared/tokens.json - ports: - - 8011:8000 + - ./sockets:/app/sockets env_file: - .env restart: unless-stopped @@ -191,6 +189,7 @@ services: pull_policy: always volumes: - ${PLEX_REQUEST_SSL_PATH:-/dev/null}:${PLEX_REQUEST_SSL_PATH:-/dev/null}:ro + - ./sockets:/app/sockets ports: - 8012:8000 env_file: @@ -199,6 +198,7 @@ services: profiles: [plex_request, all] depends_on: - plex_request + - plex_authentication networks: default: diff --git a/plex_authentication.py b/plex_authentication.py index 36e9006..c25cf7a 100644 --- a/plex_authentication.py +++ b/plex_authentication.py @@ -7,7 +7,6 @@ from shared.plex import getServerToken from werkzeug.middleware.proxy_fix import ProxyFix from werkzeug.middleware.dispatcher import DispatcherMiddleware -from werkzeug.exceptions import NotFound # instantiate the app app = Flask(__name__) @@ -71,7 +70,7 @@ def createResponse(data, statusCode): return response app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1, x_prefix=1) -app.wsgi_app = DispatcherMiddleware(NotFound(), {'/auth': app.wsgi_app}) +app.wsgi_app = DispatcherMiddleware(app.wsgi_app, {'/auth': app.wsgi_app}) if __name__ == '__main__': app.run('127.0.0.1', 12598) \ No newline at end of file diff --git a/plex_request_nginx.conf b/plex_request_nginx.conf index 68da935..9a67183 100644 --- a/plex_request_nginx.conf +++ b/plex_request_nginx.conf @@ -130,7 +130,7 @@ server { } location /auth { - proxy_pass http://plex_authentication:8000; + proxy_pass http://unix:/app/sockets/plex_authentication.sock; } location /web/index.html { @@ -182,20 +182,20 @@ server { } location = /library/all { - proxy_pass http://plex_request:8000; + proxy_pass http://unix:/app/sockets/plex_request.sock; # access_log logs/plex.access.log; } location ~ ^/library/metadata/[^/]+/children$ { - proxy_pass http://plex_request:8000; + proxy_pass http://unix:/app/sockets/plex_request.sock; # access_log logs/plex.access.log; } location /library/request/ { - proxy_pass http://plex_request:8000; - + proxy_pass http://unix:/app/sockets/plex_request.sock; + # access_log logs/plex.access.log; } } diff --git a/sockets/.gitignore b/sockets/.gitignore new file mode 100644 index 0000000..86d0cb2 --- /dev/null +++ b/sockets/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file From 88140a01d5502884332939e1ebb1dfd2eeb8af54 Mon Sep 17 00:00:00 2001 From: westsurname <155189104+westsurname@users.noreply.github.com> Date: Tue, 6 Aug 2024 22:06:38 -0400 Subject: [PATCH 9/9] Fix --- plex_request_nginx.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plex_request_nginx.conf b/plex_request_nginx.conf index 9a67183..9c47dda 100644 --- a/plex_request_nginx.conf +++ b/plex_request_nginx.conf @@ -136,7 +136,7 @@ server { location /web/index.html { proxy_pass ${plex_server_host}/web/index.html; - access_log logs/plex.access.log; + # access_log logs/plex.access.log; sub_filter '' '