diff --git a/.env.sample b/.env.sample new file mode 100644 index 0000000..2059120 --- /dev/null +++ b/.env.sample @@ -0,0 +1,3 @@ +SECRET_KEY=ldskjflkdsnejnjsdnf +DEBUG=False +ENABLE_DEBUG_LOG=True \ No newline at end of file diff --git a/.gitignore b/.gitignore index d2265c5..2e99c1e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +.idea/ venv/ db.sqlite3 aux/ +__pycache__/ +static/ diff --git a/logs/.gitignore b/logs/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/otpauth/models.py b/otpauth/models.py index de2ad3e..6e1898e 100644 --- a/otpauth/models.py +++ b/otpauth/models.py @@ -2,6 +2,10 @@ from django.db import models from django.contrib.auth.models import AbstractUser from rest_framework import exceptions from rest_framework import authentication +import json +import logging + +logger = logging.getLogger(__name__) class OTPSeed(AbstractUser): @@ -27,17 +31,20 @@ from otpauth.serializer import TokenSerializer class OTPAuthentication(authentication.BaseAuthentication): def authenticate(self, request): + logger.debug("in authenticate {}".format(json.dumps(request.data))) serializer = TokenSerializer(data=request.data) if serializer.is_valid(): instance, token = serializer.save() else: + logger.error("serializer is invalid") raise exceptions.AuthenticationFailed() # not dealing with admin realm -> can only be auth [see serializer] if not instance.realm == "ungleich-admin": if not request.path == "/ungleichotp/verify/": + logger.debug("request.path is not /ungleichotp/verify/") raise exceptions.AuthenticationFailed() - # print("AUTH DONE: {} - {}".format(request.path, instance)) + logger.debug("AUTH DONE: {} - {}".format(request.path, instance)) return (instance, token) diff --git a/otpauth/serializer.py b/otpauth/serializer.py index 21edb8f..691e5a5 100644 --- a/otpauth/serializer.py +++ b/otpauth/serializer.py @@ -1,8 +1,11 @@ +import logging import pyotp import otpauth from rest_framework import serializers, exceptions from otpauth.models import OTPSeed +logger = logging.getLogger(__name__) + # For accessing / modifying the data -- currently unused class OTPSerializer(serializers.ModelSerializer): class Meta: @@ -32,21 +35,33 @@ class TokenSerializer(serializers.Serializer): # only 2 special realms can login if not auth_realm in ["ungleich-admin", "ungleich-auth" ]: + logger.error("Auth-realm is neither ungleich-admin " + "nor ungleich-auth".format() + ) raise exceptions.AuthenticationFailed() - print("auth: [{}]{}@'{}' {} + {})".format(self.name_name, auth_name, auth_realm, auth_token, self.validated_data)) + logger.debug("auth: [{}]{}@'{}' {} + {})".format( + self.name_name, auth_name, auth_realm, + auth_token, self.validated_data + )) # 1. Verify that the connection might authenticate try: + logger.debug("Checking in db for name:{} & realm:{}".format( + auth_name, auth_realm + )) db_instance = otpauth.models.OTPSeed.objects.get(name=auth_name, realm=auth_realm) except (OTPSeed.MultipleObjectsReturned, OTPSeed.DoesNotExist): - print("does not exist") + logger.error("OTPSeed name: {}, realm: {} does not exist".format( + auth_name, auth_realm + )) raise exceptions.AuthenticationFailed() - + logger.debug("Found seed: {}".format(db_instance.seed)) totp = pyotp.TOTP(db_instance.seed) - print("calculated token = {}".format(totp.now())) + logger.debug("calculated token = {}".format(totp.now())) if not totp.verify(auth_token, valid_window=3): + logger.error("totp not verified") raise exceptions.AuthenticationFailed() return (db_instance, auth_token) @@ -65,6 +80,7 @@ class VerifySerializer(TokenSerializer): auth_realm = self.validated_data.get("auth_realm") if not auth_realm == "ungleich-auth": + logger.error("Auth-realm is not ungleich-auth") raise exceptions.AuthenticationFailed() # Do the authentication part diff --git a/otpauth/views.py b/otpauth/views.py index 10e7351..4134bc3 100644 --- a/otpauth/views.py +++ b/otpauth/views.py @@ -7,6 +7,11 @@ from rest_framework.response import Response from django.http import JsonResponse from otpauth.serializer import VerifySerializer, OTPSerializer, TokenSerializer from otpauth.models import OTPSeed +import json +import logging + +logger = logging.getLogger(__name__) + class OTPVerifyViewSet(viewsets.ModelViewSet): serializer_class = OTPSerializer @@ -20,7 +25,7 @@ class OTPVerifyViewSet(viewsets.ModelViewSet): Now we inspect the payload and return ok, if they also verify """ - + logger.debug("in verify {}".format(json.dumps(request.data))) serializer = VerifySerializer(data=request.data) if serializer.is_valid(): serializer.save() diff --git a/requirements.txt b/requirements.txt index 13085cd..b347564 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ pyotp>=2.2.6 django>=2.1.2 djangorestframework +python-decouple>=3.1 # DB psycopg2 diff --git a/ungleichotpserver/settings.py b/ungleichotpserver/settings.py index 6828a5a..2be3f0f 100644 --- a/ungleichotpserver/settings.py +++ b/ungleichotpserver/settings.py @@ -10,6 +10,8 @@ For the full list of settings and their values, see https://docs.djangoproject.com/en/2.1/ref/settings/ """ +from decouple import config, Csv + import os # Build paths inside the project like this: os.path.join(BASE_DIR, ...) @@ -20,7 +22,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'h^*!&u7yaac_6t02kk4de%$aagp6_j#+_wnw3@rqu6os0tlv#r' +SECRET_KEY = config('SECRET_KEY') # SECURITY WARNING: don't run with debug turned on in production! @@ -129,7 +131,7 @@ DEBUG_DATABASES = { } } -DEBUG = False +DEBUG = config('DEBUG', False, cast=bool) ALLOWED_HOSTS = [ ".ungleich.ch" ] @@ -146,6 +148,45 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) STATIC_ROOT = os.path.join(BASE_DIR, "static") STATIC_URL = '/static/' +LOGGING = { + 'disable_existing_loggers': False, + 'version': 1, + 'formatters': { + 'standard': { + 'format': '%(asctime)s %(levelname)s %(name)s: %(message)s' + } + }, + 'handlers': { + 'default': { + 'level': 'DEBUG', + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': 'logs/debug.log', + 'maxBytes': 1024*1024*5, + 'backupCount': 10, + 'formatter': 'standard', + }, + 'console': { + 'class': 'logging.StreamHandler', + }, + }, +} + +if config('ENABLE_DEBUG_LOG', cast=bool, default=False): + loggers_dict = {} + modules_to_log_list = config( + 'MODULES_TO_LOG', default='django', cast=Csv() + ) + for custom_module in modules_to_log_list: + logger_item = { + custom_module: { + 'handlers': ['default'], + 'level': 'DEBUG', + 'propagate': True + } + } + loggers_dict.update(logger_item) + + LOGGING['loggers'] = loggers_dict if "DEBUG" in os.environ: