From 95b5b173d6d7d5be988e252f4d674bd4a534f741 Mon Sep 17 00:00:00 2001 From: William Colmenares Date: Sat, 1 Jun 2019 08:03:59 -0400 Subject: [PATCH 01/14] added minimal flask app --- flaskapp/__init__.py | 0 flaskapp/app.py | 66 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 flaskapp/__init__.py create mode 100644 flaskapp/app.py diff --git a/flaskapp/__init__.py b/flaskapp/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/flaskapp/app.py b/flaskapp/app.py new file mode 100644 index 0000000..aaf142f --- /dev/null +++ b/flaskapp/app.py @@ -0,0 +1,66 @@ +from flask import Flask, request, jsonify, json +from flask_restful import Resource, Api +import requests +from decouple import config +from pyotp import TOTP + +app = Flask(__name__) +api = Api(app) + + +def check_otp(name, realm, token): + data = { + "auth_name": config('AUTH_NAME', ''), + "auth_token": TOTP(config('AUTH_SEED', '')).now(), + "auth_realm": config('AUTH_REALM', ''), + "name": name, + "realm": realm, + "token": token + } + response = requests.post( + "https://{OTP_SERVER}{OTP_VERIFY_ENDPOINT}".format( + OTP_SERVER=config('OTP_SERVER', ''), + OTP_VERIFY_ENDPOINT=config('OTP_VERIFY_ENDPOINT', '/ungleichotp/verify/') + ), + data=data + ) + return response.status_code + + +class MainView(Resource): + def get(self): + return jsonify({'Detail': 'This view is open to users'}) + + +class ProtectedView(Resource): + def post(self): + data = request.get_json() + if data is not None: + try: + user = data['name'] + realm = data['realm'] + token = data['token'] + assert(realm == config('REALM_ALLOWED')) + code = check_otp(user, realm, token) + assert(code == 200) + except KeyError or AssertionError: + response = app.response_class(response=json.dumps({'Message': 'Invalid data'}), + status=400, + mimetype='application/json') + return response + + response = app.response_class(response=json.dumps({'data sent': data}), + status=200, + mimetype='application/json') + return response + else: + return app.response_class(response=json.dumps({'Message': 'invalid request'}), + status=400, + mimetype='application/json') + + +api.add_resource(MainView, '/') +api.add_resource(ProtectedView, '/protected') + +if __name__ == '__main__': + app.run(host='0.0.0.0') From 71ee739fc4c10182bf9aee9ddcad7db32a774dc2 Mon Sep 17 00:00:00 2001 From: William Colmenares Date: Sat, 1 Jun 2019 08:04:42 -0400 Subject: [PATCH 02/14] added requests and flask to requirements --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b347564..9f2dfbb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,8 @@ pyotp>=2.2.6 django>=2.1.2 djangorestframework python-decouple>=3.1 - +flask +requests # DB psycopg2 From fdafe569fbc74a2591345fb367db57973aad564d Mon Sep 17 00:00:00 2001 From: William Colmenares Date: Sat, 1 Jun 2019 08:05:11 -0400 Subject: [PATCH 03/14] Include flask in the wsgi file --- ungleichotpserver/wsgi.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/ungleichotpserver/wsgi.py b/ungleichotpserver/wsgi.py index 1eed050..d8e0092 100644 --- a/ungleichotpserver/wsgi.py +++ b/ungleichotpserver/wsgi.py @@ -8,9 +8,14 @@ https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/ """ import os - from django.core.wsgi import get_wsgi_application +from flaskapp.app import app + +if __name__ == "__main__": + app.run() os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ungleichotpserver.settings') application = get_wsgi_application() + + From d4d82ae1c34bb2a51a280d915731e7355c529211 Mon Sep 17 00:00:00 2001 From: William Colmenares Date: Sun, 2 Jun 2019 09:00:18 -0400 Subject: [PATCH 04/14] new wsgi conf --- flaskapp/app.py | 2 +- flaskapp/wsgi.py | 4 ++++ ungleichotpserver/wsgi.py | 4 ---- 3 files changed, 5 insertions(+), 5 deletions(-) create mode 100644 flaskapp/wsgi.py diff --git a/flaskapp/app.py b/flaskapp/app.py index aaf142f..19846b0 100644 --- a/flaskapp/app.py +++ b/flaskapp/app.py @@ -63,4 +63,4 @@ api.add_resource(MainView, '/') api.add_resource(ProtectedView, '/protected') if __name__ == '__main__': - app.run(host='0.0.0.0') + app.run() diff --git a/flaskapp/wsgi.py b/flaskapp/wsgi.py new file mode 100644 index 0000000..86ea110 --- /dev/null +++ b/flaskapp/wsgi.py @@ -0,0 +1,4 @@ +from flaskapp.app import app + +if __name__ == "__main__": + app.run() diff --git a/ungleichotpserver/wsgi.py b/ungleichotpserver/wsgi.py index d8e0092..8fc5f7e 100644 --- a/ungleichotpserver/wsgi.py +++ b/ungleichotpserver/wsgi.py @@ -9,10 +9,6 @@ https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/ import os from django.core.wsgi import get_wsgi_application -from flaskapp.app import app - -if __name__ == "__main__": - app.run() os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ungleichotpserver.settings') From 170c7727e484f1b0c12cf9eddf77c544b80828c9 Mon Sep 17 00:00:00 2001 From: William Colmenares Date: Wed, 5 Jun 2019 06:02:14 -0400 Subject: [PATCH 05/14] clean up catch error --- flaskapp/app.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/flaskapp/app.py b/flaskapp/app.py index 19846b0..9e01226 100644 --- a/flaskapp/app.py +++ b/flaskapp/app.py @@ -43,7 +43,7 @@ class ProtectedView(Resource): assert(realm == config('REALM_ALLOWED')) code = check_otp(user, realm, token) assert(code == 200) - except KeyError or AssertionError: + except (KeyError, AssertionError) as e: response = app.response_class(response=json.dumps({'Message': 'Invalid data'}), status=400, mimetype='application/json') @@ -63,4 +63,4 @@ api.add_resource(MainView, '/') api.add_resource(ProtectedView, '/protected') if __name__ == '__main__': - app.run() + app.run(host='::') From b9ecd502454f9bbceb4aafc23c03c880f93b1d59 Mon Sep 17 00:00:00 2001 From: William Colmenares Date: Wed, 5 Jun 2019 06:02:41 -0400 Subject: [PATCH 06/14] incude flask_restful --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 9f2dfbb..f467678 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,6 +3,7 @@ django>=2.1.2 djangorestframework python-decouple>=3.1 flask +flask_restful requests # DB psycopg2 From 2f2d0c592e1dd3e1c2d6e6414b8af41057776d4b Mon Sep 17 00:00:00 2001 From: Ahmed Bilal <49-ahmedbilal@users.noreply.code.ungleich.ch> Date: Fri, 7 Jun 2019 20:06:17 +0200 Subject: [PATCH 07/14] Update README.md --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index d847eae..978d8f2 100644 --- a/README.md +++ b/README.md @@ -118,12 +118,12 @@ Request JSON object: ``` { - name: "your-name", - realm: "your-realm", - token: "current time based token", - verifyname: "name that wants to be authenticated", - verifyrealm: "realm that wants to be authenticated", - verifytoken: "token that wants to be authenticated", + auth_name: "your-name", + auth_realm: "your-realm", + auth_token: "current time based token", + name: "name that wants to be authenticated", + realm: "realm that wants to be authenticated", + token: "token that wants to be authenticated" } ``` From 0301e1a7e829deaf7076928b0b89c5ba18659c73 Mon Sep 17 00:00:00 2001 From: Ahmed Bilal <49-ahmedbilal@users.noreply.code.ungleich.ch> Date: Sat, 8 Jun 2019 09:36:55 +0200 Subject: [PATCH 08/14] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 978d8f2..37f3a16 100644 --- a/README.md +++ b/README.md @@ -118,8 +118,8 @@ Request JSON object: ``` { - auth_name: "your-name", - auth_realm: "your-realm", + auth_name: "auth-name", + auth_realm: "auth-realm", auth_token: "current time based token", name: "name that wants to be authenticated", realm: "realm that wants to be authenticated", From 3f37fe4826d66eabebd1d8716bddbaef1ba1c48f Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Sep 2019 15:10:16 +0530 Subject: [PATCH 09/14] Set username for non-superusers only --- otpauth/models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/otpauth/models.py b/otpauth/models.py index 6e1898e..6e000d3 100644 --- a/otpauth/models.py +++ b/otpauth/models.py @@ -21,7 +21,11 @@ class OTPSeed(AbstractUser): """ inject username to ensure it stays unique / is setup at all """ - self.username = "{}@{}".format(self.name, self.realm) + if not self.is_superuser: + self.username = "{}@{}".format(self.name, self.realm) + else: + self.name = self.username + super().save(*args, **kwargs) def __str__(self): From 273a1acf015309081313fa06f06a734968089403 Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Sep 2019 15:16:07 +0530 Subject: [PATCH 10/14] Set ALLOWED_HOSTS from .env --- .env.sample | 3 ++- ungleichotpserver/settings.py | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.env.sample b/.env.sample index 2059120..ae056d5 100644 --- a/.env.sample +++ b/.env.sample @@ -1,3 +1,4 @@ SECRET_KEY=ldskjflkdsnejnjsdnf DEBUG=False -ENABLE_DEBUG_LOG=True \ No newline at end of file +ENABLE_DEBUG_LOG=True +ALLOWED_HOSTS=localhost,.ungleich.ch \ No newline at end of file diff --git a/ungleichotpserver/settings.py b/ungleichotpserver/settings.py index 2be3f0f..99cb9bb 100644 --- a/ungleichotpserver/settings.py +++ b/ungleichotpserver/settings.py @@ -132,9 +132,7 @@ DEBUG_DATABASES = { } DEBUG = config('DEBUG', False, cast=bool) -ALLOWED_HOSTS = [ - ".ungleich.ch" -] +ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost', cast=Csv) DATABASES = { 'default': { From 9703eb6538dc8cc3863f1e043838a63cbcc94d8f Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Sep 2019 15:26:10 +0530 Subject: [PATCH 11/14] Set name, realm and seed for superusers also --- otpauth/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/otpauth/models.py b/otpauth/models.py index 6e000d3..81ae71f 100644 --- a/otpauth/models.py +++ b/otpauth/models.py @@ -4,6 +4,7 @@ from rest_framework import exceptions from rest_framework import authentication import json import logging +import pyotp logger = logging.getLogger(__name__) @@ -25,6 +26,8 @@ class OTPSeed(AbstractUser): self.username = "{}@{}".format(self.name, self.realm) else: self.name = self.username + self.realm = "ungleich-admin" + self.seed = pyotp.random_base32() super().save(*args, **kwargs) From 1efe6ee0786ed0fd08330f0e651d9d485059e70f Mon Sep 17 00:00:00 2001 From: PCoder Date: Thu, 26 Sep 2019 15:45:54 +0530 Subject: [PATCH 12/14] Add missing parenthesis --- ungleichotpserver/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ungleichotpserver/settings.py b/ungleichotpserver/settings.py index 99cb9bb..72befd5 100644 --- a/ungleichotpserver/settings.py +++ b/ungleichotpserver/settings.py @@ -132,7 +132,7 @@ DEBUG_DATABASES = { } DEBUG = config('DEBUG', False, cast=bool) -ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost', cast=Csv) +ALLOWED_HOSTS = config('ALLOWED_HOSTS', default='localhost', cast=Csv()) DATABASES = { 'default': { From ae3c156df79bf75c243f1d15c0fb913e53cdd4c5 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 2 Sep 2020 20:48:34 +0530 Subject: [PATCH 13/14] Update Django from 2.1.4 to 2.2.16 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index b347564..dfadfef 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ pyotp>=2.2.6 -django>=2.1.2 +django==2.2.16 djangorestframework python-decouple>=3.1 From 6ba71545a186b3145f4361033a8f835896721e66 Mon Sep 17 00:00:00 2001 From: PCoder Date: Wed, 3 Nov 2021 19:47:32 +0530 Subject: [PATCH 14/14] Fix psycopg2 bug Internal Server Error: /admin/login/ Traceback (most recent call last): File "/home/app/pyvenv/lib/python3.9/site-packages/django/core/handlers/exception.py", line 34, in inner response = get_response(request) File "/home/app/pyvenv/lib/python3.9/site-packages/django/core/handlers/base.py", line 115, in _get_response response = self.process_exception_by_middleware(e, request) File "/home/app/pyvenv/lib/python3.9/site-packages/django/core/handlers/base.py", line 113, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) File "/home/app/pyvenv/lib/python3.9/site-packages/django/views/decorators/cache.py", line 44, in _wrapped_view_func response = view_func(request, *args, **kwargs) File "/home/app/pyvenv/lib/python3.9/site-packages/django/contrib/admin/sites.py", line 399, in login return LoginView.as_view(**defaults)(request) File "/home/app/pyvenv/lib/python3.9/site-packages/django/views/generic/base.py", line 71, in view return self.dispatch(request, *args, **kwargs) File "/home/app/pyvenv/lib/python3.9/site-packages/django/utils/decorators.py", line 45, in _wrapper return bound_method(*args, **kwargs) File "/home/app/pyvenv/lib/python3.9/site-packages/django/views/decorators/debug.py", line 76, in sensitive_post_parameters_wrapper return view(request, *args, **kwargs) File "/home/app/pyvenv/lib/python3.9/site-packages/django/utils/decorators.py", line 45, in _wrapper return bound_method(*args, **kwargs) File "/home/app/pyvenv/lib/python3.9/site-packages/django/utils/decorators.py", line 142, in _wrapped_view response = view_func(request, *args, **kwargs) File "/home/app/pyvenv/lib/python3.9/site-packages/django/utils/decorators.py", line 45, in _wrapper return bound_method(*args, **kwargs) File "/home/app/pyvenv/lib/python3.9/site-packages/django/views/decorators/cache.py", line 44, in _wrapped_view_func response = view_func(request, *args, **kwargs) File "/home/app/pyvenv/lib/python3.9/site-packages/django/contrib/auth/views.py", line 61, in dispatch return super().dispatch(request, *args, **kwargs) File "/home/app/pyvenv/lib/python3.9/site-packages/django/views/generic/base.py", line 97, in dispatch return handler(request, *args, **kwargs) File "/home/app/pyvenv/lib/python3.9/site-packages/django/views/generic/edit.py", line 141, in post if form.is_valid(): File "/home/app/pyvenv/lib/python3.9/site-packages/django/forms/forms.py", line 185, in is_valid return self.is_bound and not self.errors File "/home/app/pyvenv/lib/python3.9/site-packages/django/forms/forms.py", line 180, in errors self.full_clean() File "/home/app/pyvenv/lib/python3.9/site-packages/django/forms/forms.py", line 382, in full_clean self._clean_form() File "/home/app/pyvenv/lib/python3.9/site-packages/django/forms/forms.py", line 409, in _clean_form cleaned_data = self.clean() File "/home/app/pyvenv/lib/python3.9/site-packages/django/contrib/auth/forms.py", line 205, in clean self.user_cache = authenticate(self.request, username=username, password=password) File "/home/app/pyvenv/lib/python3.9/site-packages/django/contrib/auth/__init__.py", line 73, in authenticate user = backend.authenticate(request, **credentials) File "/home/app/pyvenv/lib/python3.9/site-packages/django/contrib/auth/backends.py", line 20, in authenticate user = UserModel._default_manager.get_by_natural_key(username) File "/home/app/pyvenv/lib/python3.9/site-packages/django/contrib/auth/base_user.py", line 44, in get_by_natural_key return self.get(**{self.model.USERNAME_FIELD: username}) File "/home/app/pyvenv/lib/python3.9/site-packages/django/db/models/manager.py", line 82, in manager_method return getattr(self.get_queryset(), name)(*args, **kwargs) File "/home/app/pyvenv/lib/python3.9/site-packages/django/db/models/query.py", line 402, in get num = len(clone) File "/home/app/pyvenv/lib/python3.9/site-packages/django/db/models/query.py", line 256, in __len__ self._fetch_all() File "/home/app/pyvenv/lib/python3.9/site-packages/django/db/models/query.py", line 1242, in _fetch_all self._result_cache = list(self._iterable_class(self)) File "/home/app/pyvenv/lib/python3.9/site-packages/django/db/models/query.py", line 55, in __iter__ results = compiler.execute_sql(chunked_fetch=self.chunked_fetch, chunk_size=self.chunk_size) File "/home/app/pyvenv/lib/python3.9/site-packages/django/db/models/sql/compiler.py", line 1175, in execute_sql return list(result) File "/home/app/pyvenv/lib/python3.9/site-packages/django/db/models/sql/compiler.py", line 1554, in cursor_iter for rows in iter((lambda: cursor.fetchmany(itersize)), sentinel): File "/home/app/pyvenv/lib/python3.9/site-packages/django/db/models/sql/compiler.py", line 1554, in for rows in iter((lambda: cursor.fetchmany(itersize)), sentinel): File "/home/app/pyvenv/lib/python3.9/site-packages/django/db/utils.py", line 96, in inner return func(*args, **kwargs) File "/home/app/pyvenv/lib/python3.9/site-packages/django/db/backends/postgresql/utils.py", line 6, in utc_tzinfo_factory raise AssertionError("database connection isn't set to UTC") See: https://stackoverflow.com/a/68025007 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index dfadfef..04fe0b0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ djangorestframework python-decouple>=3.1 # DB -psycopg2 +psycopg2>=2.8,<2.9 # Recommended markdown