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
8bd256a1d7
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.
|
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 ##
|
## Server Setup instructions ##
|
||||||
|
|
||||||
This is a standard django project and thus can be easily setup using
|
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('--name')
|
||||||
parser.add_argument('--realm')
|
parser.add_argument('--realm')
|
||||||
parser.add_argument('--token')
|
parser.add_argument('--token')
|
||||||
|
parser.add_argument('--seed')
|
||||||
|
|
||||||
# How to authenticate against ungleich-otp
|
# How to authenticate against ungleich-otp
|
||||||
parser.add_argument('--auth-name', required=True)
|
parser.add_argument('--auth-name', required=True)
|
||||||
|
@ -34,6 +35,8 @@ class Command(BaseCommand):
|
||||||
'delete': 'DELETE',
|
'delete': 'DELETE',
|
||||||
'list': 'GET' }
|
'list': 'GET' }
|
||||||
|
|
||||||
|
method = 'POST'
|
||||||
|
|
||||||
if not options['auth_token']:
|
if not options['auth_token']:
|
||||||
if not options['auth_seed']:
|
if not options['auth_seed']:
|
||||||
print("Either token or seed are required")
|
print("Either token or seed are required")
|
||||||
|
@ -48,14 +51,20 @@ class Command(BaseCommand):
|
||||||
to_send['auth_name'] = options['auth_name']
|
to_send['auth_name'] = options['auth_name']
|
||||||
to_send['auth_realm'] = options['auth_realm']
|
to_send['auth_realm'] = options['auth_realm']
|
||||||
|
|
||||||
|
if options['command'] in ["list", "get"]:
|
||||||
|
method = 'GET'
|
||||||
|
|
||||||
if options['command'] in ["create", "verify"]:
|
if options['command'] in ["create", "verify"]:
|
||||||
if not options['name'] or not options['realm']:
|
if not options['name'] or not options['realm']:
|
||||||
print("Need to specify --name and --realm")
|
print("Need to specify --name and --realm")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
if options['command'] == "verify" and not options['token']:
|
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)
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
options['token'] = pyotp.TOTP(options['seed']).now()
|
||||||
|
|
||||||
|
|
||||||
# Client credentials to be verified
|
# Client credentials to be verified
|
||||||
|
@ -64,28 +73,25 @@ class Command(BaseCommand):
|
||||||
to_send['token'] = options['token']
|
to_send['token'] = options['token']
|
||||||
|
|
||||||
if options['command'] == "verify":
|
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
|
@staticmethod
|
||||||
def rest_send(serverurl, to_send):
|
def rest_send(serverurl, to_send, method='POST'):
|
||||||
data = json.dumps(to_send).encode("utf-8")
|
data = json.dumps(to_send).encode("utf-8")
|
||||||
|
|
||||||
req = urllib.request.Request(url=serverurl,
|
req = urllib.request.Request(url=serverurl,
|
||||||
data=data,
|
data=data,
|
||||||
headers={'Content-Type': 'application/json'},
|
headers={'Content-Type': 'application/json'},
|
||||||
method='POST')
|
method=method)
|
||||||
|
|
||||||
f = urllib.request.urlopen(req)
|
f = urllib.request.urlopen(req)
|
||||||
|
|
||||||
if f.status == 200:
|
if f.status == 200:
|
||||||
|
print("Response: {}: {}".format(f.msg, f.read()))
|
||||||
return True
|
return True
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -31,10 +31,15 @@ class OTPAuthentication(authentication.BaseAuthentication):
|
||||||
|
|
||||||
if serializer.is_valid():
|
if serializer.is_valid():
|
||||||
print("trying to save... {}".format(serializer))
|
print("trying to save... {}".format(serializer))
|
||||||
user, token = serializer.save()
|
instance, token = serializer.save()
|
||||||
else:
|
else:
|
||||||
print("Invalide serialize,")
|
print("Invalide serialize,")
|
||||||
raise exceptions.AuthenticationFailed()
|
raise exceptions.AuthenticationFailed()
|
||||||
|
|
||||||
print("AUTH DONE")
|
# not dealing with admin realm -> can only be auth [see serializer]
|
||||||
return (user, token)
|
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)
|
return OTPSeed.objects.create(**validated_data)
|
||||||
|
|
||||||
class TokenSerializer(serializers.Serializer):
|
class TokenSerializer(serializers.Serializer):
|
||||||
name = serializers.CharField(max_length=128)
|
""" This class is mainly / only used for authentication"""
|
||||||
realm = serializers.CharField(max_length=128)
|
|
||||||
|
|
||||||
auth_name = serializers.CharField(max_length=128)
|
auth_name = serializers.CharField(max_length=128)
|
||||||
auth_token = serializers.CharField(max_length=128)
|
auth_token = serializers.CharField(max_length=128)
|
||||||
auth_realm = serializers.CharField(max_length=128)
|
auth_realm = serializers.CharField(max_length=128)
|
||||||
|
|
||||||
def create(self, validated_data):
|
token_name = 'auth_token'
|
||||||
print("Trying to create")
|
name_name = 'auth_name'
|
||||||
validated_data['seed'] = pyotp.random_base32()
|
realm_name = 'auth_realm'
|
||||||
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
|
|
||||||
|
|
||||||
def save(self):
|
def save(self):
|
||||||
name_in = self.validated_data.get("name")
|
auth_token = self.validated_data.get(self.token_name)
|
||||||
realm_in = self.validated_data.get("realm")
|
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")
|
# only 2 special realms can login
|
||||||
auth_name = self.validated_data.get("auth_name")
|
if not auth_realm in ["ungleich-admin", "ungleich-auth" ]:
|
||||||
auth_realm = self.validated_data.get("auth_realm")
|
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
|
# 1. Verify that the connection might authenticate
|
||||||
try:
|
try:
|
||||||
|
@ -60,21 +51,18 @@ class TokenSerializer(serializers.Serializer):
|
||||||
|
|
||||||
return (db_instance, auth_token)
|
return (db_instance, auth_token)
|
||||||
|
|
||||||
# For verifying a token
|
# For verifying somebody else's token
|
||||||
class VerifySerializer(TokenSerializer):
|
class VerifySerializer(TokenSerializer):
|
||||||
verifyname = serializers.CharField(max_length=128)
|
name = serializers.CharField(max_length=128)
|
||||||
verifytoken = serializers.CharField(max_length=128)
|
token = serializers.CharField(max_length=128)
|
||||||
verifyrealm = serializers.CharField(max_length=128)
|
realm = serializers.CharField(max_length=128)
|
||||||
|
|
||||||
token_name = 'verifytoken'
|
token_name = 'token'
|
||||||
name_name = 'verifyname'
|
name_name = 'name'
|
||||||
realm_name = 'verifyrealm'
|
realm_name = 'realm'
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
auth_realm = self.validated_data.get("auth_realm")
|
||||||
|
|
||||||
# token_name = 'token'
|
if not auth_realm == "ungleich-auth":
|
||||||
# name_name = 'name'
|
raise exceptions.AuthenticationFailed()
|
||||||
# realm_name = 'realm'
|
|
||||||
|
|
||||||
# auth_token_name = 'auth_token'
|
|
||||||
# auth_name_name = 'auth_name'
|
|
||||||
# auth_realm_name = 'auth_realm'
|
|
||||||
|
|
|
@ -8,38 +8,16 @@ from django.http import JsonResponse
|
||||||
from otpauth.serializer import VerifySerializer, OTPSerializer, TokenSerializer
|
from otpauth.serializer import VerifySerializer, OTPSerializer, TokenSerializer
|
||||||
from otpauth.models import OTPSeed
|
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):
|
class OTPVerifyViewSet(viewsets.ModelViewSet):
|
||||||
serializer_class = OTPSerializer
|
serializer_class = OTPSerializer
|
||||||
queryset = OTPSeed.objects.all()
|
queryset = OTPSeed.objects.all()
|
||||||
|
|
||||||
|
|
||||||
@action(detail=False, methods=['post'])
|
@action(detail=False, methods=['post'])
|
||||||
def verify(self, request):
|
def verify(self, request):
|
||||||
"""the standard serializer above already verified that
|
"""the standard serializer above already verified that
|
||||||
(name, realm, token) is valid.
|
(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
|
if they also verify
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue