From 8bd256a1d7011ecd01359b9a82b0e399eb3e580b Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Mon, 31 Dec 2018 00:46:29 +0100 Subject: [PATCH] Fix auth! ungleich-admin can do anything, but verify ungleich-auth can only verify rest cannot login --- README.md | 18 ++++++ .../management/commands/ungleichotpclient.py | 26 +++++---- otpauth/models.py | 11 +++- otpauth/serializer.py | 56 ++++++++----------- otpauth/views.py | 24 +------- 5 files changed, 65 insertions(+), 70 deletions(-) diff --git a/README.md b/README.md index d23260a..ce3f56b 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,24 @@ UNGLEICHOTPSERVER=http://localhost:8000/ungleichotp/verify/ \ The client requires pyotp. +## Sample 2018-12-30 + +create: +(venv) [23:07] line:ungleich-otp% python manage.py ungleichotpclient create --server-url http://localhost:8000/ungleichotp/ --auth-name info@ungleich.ch --auth-realm ungleich-admin --auth-seed PZKBPTHDGSLZBKIZ --name nico$(date +%s) --realm ungleich-admin + +verify: + +``` +(venv) [23:07] line:ungleich-otp% python manage.py ungleichotpclient verify --server-url http://localhost:8000/ungleichotp/ --auth-name info@ungleich.ch --auth-realm ungleich-admin --auth-seed PZKBPTHDGSLZBKIZ --name nico1546206660 --realm ungleich-admin --seed IXTARIU4H2F574M3 +``` + +list: + +``` +(venv) [23:14] line:ungleich-otp% python manage.py ungleichotpclient list --server-url http://localhost:8000/ungleichotp/ --auth-name info@ungleich.ch --auth-realm ungleich-admin --auth-seed PZKBPTHDGSLZBKIZ +``` + + ## Server Setup instructions ## This is a standard django project and thus can be easily setup using diff --git a/otpauth/management/commands/ungleichotpclient.py b/otpauth/management/commands/ungleichotpclient.py index c7f546a..1cd0465 100644 --- a/otpauth/management/commands/ungleichotpclient.py +++ b/otpauth/management/commands/ungleichotpclient.py @@ -17,6 +17,7 @@ class Command(BaseCommand): parser.add_argument('--name') parser.add_argument('--realm') parser.add_argument('--token') + parser.add_argument('--seed') # How to authenticate against ungleich-otp parser.add_argument('--auth-name', required=True) @@ -34,6 +35,8 @@ class Command(BaseCommand): 'delete': 'DELETE', 'list': 'GET' } + method = 'POST' + if not options['auth_token']: if not options['auth_seed']: print("Either token or seed are required") @@ -48,14 +51,20 @@ class Command(BaseCommand): to_send['auth_name'] = options['auth_name'] to_send['auth_realm'] = options['auth_realm'] + if options['command'] in ["list", "get"]: + method = 'GET' + if options['command'] in ["create", "verify"]: if not options['name'] or not options['realm']: print("Need to specify --name and --realm") sys.exit(1) if options['command'] == "verify" and not options['token']: - print("Need to specify --token for verify") - sys.exit(1) + if not options['seed']: + print("Need to specify --token or --seed for verify") + sys.exit(1) + else: + options['token'] = pyotp.TOTP(options['seed']).now() # Client credentials to be verified @@ -64,28 +73,25 @@ class Command(BaseCommand): to_send['token'] = options['token'] if options['command'] == "verify": - options['server_url'] = "{}/verify".format(options['server_url']) + options['server_url'] = "{}verify/".format(options['server_url']) - print("{} {} {}".format(args, options, to_send)) - self.rest_send(options['server_url'], to_send) - - # Logically: how can we create if we already send realm/name/token ? - # Need to use auth* (?) + self.rest_send(options['server_url'], to_send, method=method) @staticmethod - def rest_send(serverurl, to_send): + def rest_send(serverurl, to_send, method='POST'): data = json.dumps(to_send).encode("utf-8") req = urllib.request.Request(url=serverurl, data=data, headers={'Content-Type': 'application/json'}, - method='POST') + method=method) f = urllib.request.urlopen(req) if f.status == 200: + print("Response: {}: {}".format(f.msg, f.read())) return True return False diff --git a/otpauth/models.py b/otpauth/models.py index 0915443..4afcf4d 100644 --- a/otpauth/models.py +++ b/otpauth/models.py @@ -31,10 +31,15 @@ class OTPAuthentication(authentication.BaseAuthentication): if serializer.is_valid(): print("trying to save... {}".format(serializer)) - user, token = serializer.save() + instance, token = serializer.save() else: print("Invalide serialize,") raise exceptions.AuthenticationFailed() - print("AUTH DONE") - return (user, token) + # not dealing with admin realm -> can only be auth [see serializer] + if not instance.realm == "ungleich-admin": + if not request.path == "/ungleichotp/verify/": + raise exceptions.AuthenticationFailed() + + print("AUTH DONE: {} - {}".format(request.path, instance)) + return (instance, token) diff --git a/otpauth/serializer.py b/otpauth/serializer.py index bb9d6e0..4c0b089 100644 --- a/otpauth/serializer.py +++ b/otpauth/serializer.py @@ -15,35 +15,26 @@ class OTPSerializer(serializers.ModelSerializer): return OTPSeed.objects.create(**validated_data) class TokenSerializer(serializers.Serializer): - name = serializers.CharField(max_length=128) - realm = serializers.CharField(max_length=128) + """ This class is mainly / only used for authentication""" auth_name = serializers.CharField(max_length=128) auth_token = serializers.CharField(max_length=128) auth_realm = serializers.CharField(max_length=128) - def create(self, validated_data): - print("Trying to create") - validated_data['seed'] = pyotp.random_base32() - return OTPSeed.objects.create(**validated_data) - - def update(self, instance, validated_data): - print("Trying to update") - instance.name = validated_data.get('name', instance.name) - instance.realm = validated_data.get('realm', instance.realm) - instance.save() - - return instance + token_name = 'auth_token' + name_name = 'auth_name' + realm_name = 'auth_realm' def save(self): - name_in = self.validated_data.get("name") - realm_in = self.validated_data.get("realm") + auth_token = self.validated_data.get(self.token_name) + auth_name = self.validated_data.get(self.name_name) + auth_realm = self.validated_data.get(self.realm_name) - auth_token = self.validated_data.get("auth_token") - auth_name = self.validated_data.get("auth_name") - auth_realm = self.validated_data.get("auth_realm") + # only 2 special realms can login + if not auth_realm in ["ungleich-admin", "ungleich-auth" ]: + raise exceptions.AuthenticationFailed() - print("auth: {}@'{}' {} + {})".format(auth_name, auth_realm, auth_token, self.validated_data)) + print("auth: [{}]{}@'{}' {} + {})".format(self.name_name, auth_name, auth_realm, auth_token, self.validated_data)) # 1. Verify that the connection might authenticate try: @@ -60,21 +51,18 @@ class TokenSerializer(serializers.Serializer): return (db_instance, auth_token) -# For verifying a token +# For verifying somebody else's token class VerifySerializer(TokenSerializer): - verifyname = serializers.CharField(max_length=128) - verifytoken = serializers.CharField(max_length=128) - verifyrealm = serializers.CharField(max_length=128) + name = serializers.CharField(max_length=128) + token = serializers.CharField(max_length=128) + realm = serializers.CharField(max_length=128) - token_name = 'verifytoken' - name_name = 'verifyname' - realm_name = 'verifyrealm' + token_name = 'token' + name_name = 'name' + realm_name = 'realm' + def save(self): + auth_realm = self.validated_data.get("auth_realm") - # token_name = 'token' - # name_name = 'name' - # realm_name = 'realm' - - # auth_token_name = 'auth_token' - # auth_name_name = 'auth_name' - # auth_realm_name = 'auth_realm' + if not auth_realm == "ungleich-auth": + raise exceptions.AuthenticationFailed() diff --git a/otpauth/views.py b/otpauth/views.py index 46cd85a..10e7351 100644 --- a/otpauth/views.py +++ b/otpauth/views.py @@ -8,38 +8,16 @@ from django.http import JsonResponse from otpauth.serializer import VerifySerializer, OTPSerializer, TokenSerializer from otpauth.models import OTPSeed -# class OTPVerifyViewSet(viewsets.ModelViewSet): -# serializer_class = OTPSerializer -# queryset = OTPSeed.objects.all() - -# @action(detail=False, methods=['post']) -# def verify(self, request): -# """the standard serializer above already verified that -# (name, realm, token) is valid. - -# Now we inspect the verify-prefixed names and return ok, -# if they also verify -# """ - -# serializer = VerifySerializer(data=request.data) -# if serializer.is_valid(): -# serializer.save() -# return Response({'status': 'OK'}) - -# return JsonResponse(serializer.errors, status=400) - - class OTPVerifyViewSet(viewsets.ModelViewSet): serializer_class = OTPSerializer queryset = OTPSeed.objects.all() - @action(detail=False, methods=['post']) def verify(self, request): """the standard serializer above already verified that (name, realm, token) is valid. - Now we inspect the verify-prefixed names and return ok, + Now we inspect the payload and return ok, if they also verify """