diff --git a/README.md b/README.md index 31ba2e8..b1b4fb3 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ made for micro services. The basic idea is that every micro service has a (long term) triple constisting of (name, realm, seed) and creates time based tokens. -It basically revamps Kerberos in a simple way into the web area. +This basically revamps Kerberos in a simple way into the web area. ungleichotp has been created and is maintained by [ungleich](https://ungleich.ch/). @@ -24,6 +24,17 @@ This repository contains three components: * ungleichotp-client: a sample implementation of an ungleichotp client +### Overview client ### + +An application that lets you enter the triple (realm, name, token) and +responds with OK / NOTOK. + +How to use: + + + + + ## Setup instructions ## This is a standard django project and thus can be easily setup using diff --git a/ungleichotp/ungleichotp.py b/ungleichotp/ungleichotp.py index 2e482d3..6ffaa31 100644 --- a/ungleichotp/ungleichotp.py +++ b/ungleichotp/ungleichotp.py @@ -1,64 +1,75 @@ -from django.contrib.auth.models import User -from rest_framework import authentication, exceptions, serializers - -import urllib.request import pyotp import json +import urllib.request +import urllib.error -# For parsing -class TokenSerializer(serializers.Serializer): - name = serializers.CharField(max_length=128) - token = serializers.CharField(max_length=128) - realm = serializers.CharField(max_length=128) - +class UngleichOTPClient(object): token_name = 'token' name_name = 'name' realm_name = 'realm' - def __init__(self, name, realm, seed, serverurl, *args, **kwargs): + def __init__(self, name, realm, seed, serverurl): self.name = name self.realm = realm self.seed = seed self.serverurl = serverurl - super(serializers.Serializer, self).__init__(*args, **kwargs) - - def save(self): + def verify(self, name, realm, token): to_send = {} # Client credentials to be verified - to_send['verifytoken'] = self.validated_data.get(self.token_name) - to_send['verifyname'] = self.validated_data.get(self.name_name) - to_send['verifyrealm'] = self.validated_data.get(self.real_name) + to_send['verifyname'] = name + to_send['verifyrealm'] = realm + to_send['verifytoken'] = token # Our credentials - to_send['token'] = pyotp.TOTP(self.seed) + to_send['token'] = pyotp.TOTP(self.seed).now() to_send['name'] = self.name to_send['realm'] = self.realm data = json.dumps(to_send).encode("utf-8") - req = urllib.request.Request(url=serverurl, + req = urllib.request.Request(url=self.serverurl, data=data, headers={'Content-Type': 'application/json'}, method='POST') f = urllib.request.urlopen(req) - if not f.status == 200: - raise exceptions.AuthenticationFailed() + if f.status == 200: + return True - return True + return False -class OTPAuthentication(authentication.BaseAuthentication): - def authenticate(self, request): - serializer = TokenSerializer(data=request.data) +if __name__ == '__main__': + import argparse + import os + import sys - if serializer.is_valid(): - print("trying to save... {}".format(serializer)) - user, token = serializer.save() - else: - raise exceptions.AuthenticationFailed() + parser = argparse.ArgumentParser(description='ungleichotp-client') + parser.add_argument('-n', '--name', help="Name (for verification)", required=True) + parser.add_argument('-r', '--realm', help="Realm (for verification)", required=True) + parser.add_argument('-t', '--token', help="Token (for verification)", required=True) - return (user, token) + args = parser.parse_args(sys.argv[1:]) + + + UNGLEICHOTP={} + for env in ['UNGLEICHOTPREALM', 'UNGLEICHOTPNAME', 'UNGLEICHOTPSEED', 'UNGLEICHOTPSERVER' ]: + if not env in os.environ: + raise Exception("Required environment variable missing: {}".format(env)) + + client = UngleichOTPClient(os.environ['UNGLEICHOTPNAME'], + os.environ['UNGLEICHOTPREALM'], + os.environ['UNGLEICHOTPSEED'], + os.environ['UNGLEICHOTPSERVER']) + + + + try: + client.verify(args.name, args.realm, args.token) + except urllib.error.HTTPError as e: + print("Failed to verify: {}".format(e)) + + print("Verify ok") diff --git a/ungleichotpclient/README.md b/ungleichotpclient/README.md new file mode 100644 index 0000000..6843b82 --- /dev/null +++ b/ungleichotpclient/README.md @@ -0,0 +1,28 @@ +## Run + +Use your favorite uwsgi configuration or + +``` +UNGLEICHOTPSERVER=https://your-server-address/ +UNGLEICHOTPREALM=admin \ +UNGLEICHOTPNAME=sampleapp \ +UNGLEICHOTPSEED=CEKXVG3235PO2HDW \ + python manage.py runserver +``` + +This triple will be used to connect to the + +## Use ## + +curl + +def index(request): + answer = {} + answer['token'] = pyotp.TOTP(settings.UNGLEICHOTP['UNGLEICHOTPSEED']).now() + answer['name'] = settings.UNGLEICHOTP['UNGLEICHOTPNAME'] + answer['realm'] = settings.UNGLEICHOTP['UNGLEICHOTPREALM'] + + return HttpResponse(json.dumps(answer)) + +elif request.method == 'POST': + do_something_else() diff --git a/ungleichotpserver/otpauth/admin.py b/ungleichotpserver/otpauth/admin.py index acdf348..83cdcd4 100644 --- a/ungleichotpserver/otpauth/admin.py +++ b/ungleichotpserver/otpauth/admin.py @@ -8,5 +8,4 @@ from .models import OTPSeed from django.contrib import admin from django.contrib.auth.admin import UserAdmin -# admin.site.register(OTPSeed, UserAdmin) admin.site.register(OTPSeed) diff --git a/ungleichotpserver/otpauth/views.py b/ungleichotpserver/otpauth/views.py index bcd5a6c..234903b 100644 --- a/ungleichotpserver/otpauth/views.py +++ b/ungleichotpserver/otpauth/views.py @@ -9,7 +9,7 @@ from django.http import JsonResponse from otpauth.serializer import VerifySerializer, OTPSerializer from otpauth.models import OTPSeed -# + class OTPVerifyViewSet(viewsets.ModelViewSet): serializer_class = OTPSerializer queryset = OTPSeed.objects.all() diff --git a/ungleichotpserver/ungleichotpserver/urls.py b/ungleichotpserver/ungleichotpserver/urls.py index 5af3b02..e14b6f9 100644 --- a/ungleichotpserver/ungleichotpserver/urls.py +++ b/ungleichotpserver/ungleichotpserver/urls.py @@ -1,19 +1,3 @@ -"""ungleichotp URL Configuration - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/2.1/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: path('', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.urls import include, path - 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) -""" - from django.contrib import admin from django.urls import path from django.conf.urls import url, include