Fix auth!
ungleich-admin can do anything, but verify ungleich-auth can only verify rest cannot login
This commit is contained in:
parent
0eb09c31d8
commit
e0a65fd81c
5 changed files with 65 additions and 70 deletions
18
README.md
18
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
|
||||
|
|
|
@ -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")
|
||||
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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
"""
|
||||
|
||||
|
|
Loading…
Reference in a new issue