From 3ada914040ed0ff82528234051fddfe3a5f97c4e Mon Sep 17 00:00:00 2001 From: Nico Schottelius Date: Sun, 18 Nov 2018 14:33:30 +0100 Subject: [PATCH] Be python friendly, don't use dashes --- README.md | 255 +++++++++++------- ungleichotp/ungleichotp.py | 16 ++ .../manage.py | 0 .../restapp/__init__.py | 0 .../restapp/admin.py | 0 .../restapp/apps.py | 0 .../restapp/migrations/__init__.py | 0 .../restapp/models.py | 0 .../restapp/tests.py | 0 .../restapp/views.py | 0 .../ungleichotpclient/__init__.py | 0 .../ungleichotpclient/settings.py | 2 + .../ungleichotpclient/urls.py | 0 .../ungleichotpclient/wsgi.py | 0 {ungleichotp => ungleichotpserver}/manage.py | 2 +- .../otpauth/__init__.py | 0 .../otpauth/admin.py | 0 .../otpauth/apps.py | 0 .../otpauth/migrations/0001_initial.py | 0 .../otpauth/migrations/__init__.py | 0 .../otpauth/models.py | 0 .../otpauth/serializer.py | 3 + .../otpauth/tests.py | 0 .../otpauth/views.py | 2 +- .../ungleichotpserver}/__init__.py | 0 .../ungleichotpserver}/settings.py | 4 +- .../ungleichotpserver}/urls.py | 0 .../ungleichotpserver}/wsgi.py | 0 28 files changed, 177 insertions(+), 107 deletions(-) create mode 100644 ungleichotp/ungleichotp.py rename {ungleichotp-client => ungleichotpclient}/manage.py (100%) rename {ungleichotp-client => ungleichotpclient}/restapp/__init__.py (100%) rename {ungleichotp-client => ungleichotpclient}/restapp/admin.py (100%) rename {ungleichotp-client => ungleichotpclient}/restapp/apps.py (100%) rename {ungleichotp-client => ungleichotpclient}/restapp/migrations/__init__.py (100%) rename {ungleichotp-client => ungleichotpclient}/restapp/models.py (100%) rename {ungleichotp-client => ungleichotpclient}/restapp/tests.py (100%) rename {ungleichotp-client => ungleichotpclient}/restapp/views.py (100%) rename {ungleichotp-client => ungleichotpclient}/ungleichotpclient/__init__.py (100%) rename {ungleichotp-client => ungleichotpclient}/ungleichotpclient/settings.py (98%) rename {ungleichotp-client => ungleichotpclient}/ungleichotpclient/urls.py (100%) rename {ungleichotp-client => ungleichotpclient}/ungleichotpclient/wsgi.py (100%) rename {ungleichotp => ungleichotpserver}/manage.py (96%) rename {ungleichotp => ungleichotpserver}/otpauth/__init__.py (100%) rename {ungleichotp => ungleichotpserver}/otpauth/admin.py (100%) rename {ungleichotp => ungleichotpserver}/otpauth/apps.py (100%) rename {ungleichotp => ungleichotpserver}/otpauth/migrations/0001_initial.py (100%) rename {ungleichotp => ungleichotpserver}/otpauth/migrations/__init__.py (100%) rename {ungleichotp => ungleichotpserver}/otpauth/models.py (100%) rename {ungleichotp => ungleichotpserver}/otpauth/serializer.py (95%) rename {ungleichotp => ungleichotpserver}/otpauth/tests.py (100%) rename {ungleichotp => ungleichotpserver}/otpauth/views.py (99%) rename {ungleichotp/ungleichotp => ungleichotpserver/ungleichotpserver}/__init__.py (100%) rename {ungleichotp/ungleichotp => ungleichotpserver/ungleichotpserver}/settings.py (97%) rename {ungleichotp/ungleichotp => ungleichotpserver/ungleichotpserver}/urls.py (100%) rename {ungleichotp/ungleichotp => ungleichotpserver/ungleichotpserver}/wsgi.py (100%) diff --git a/README.md b/README.md index d9ea2ef..c12726c 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,28 @@ -# ungleich-otp +# ungleichotp # ungleich-otp is a full blown authentication and authorisation service made for micro services. -The basic idea is that every micro service has a (long term) seed and -creates time based tokens (See python pyotp, RFC4226, RFC6238). +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. + +ungleichotp has been created and is maintained by [ungleich](https://ungleich.ch/). + +Related documentation: + +* [Python pyotp](https://pyotp.readthedocs.io/) +* [RFC6238, TOTP](https://tools.ietf.org/html/rfc6238) +* [RFC4120, Kerberos](https://tools.ietf.org/html/rfc4120) + +## Overview + +This repository contains three components: + +* ungleichotp-server: the reference implementation of the ungleichotp server +* ungleichotp-client: a sample implementation of an ungleichotp client + ## Setup instructions ## @@ -54,84 +72,14 @@ them to verify a token of somebody else. | all other realms | NO ACCESS | -## Usage: REST ## -- Use an existing token to connect to the service -- All REST based messages: JSON - - -### POST: /ungleichotp/verify - -Request JSON object: - -``` -{ - version: "1", - name: "your-name", - realm: "your-realm", - token: "current time based token", - verifyname: "name that wants to be authenticated", - verifyrealm: "realm that wants to be authenticated", - verifytoken: "token that wants to be authenticated", -} -``` - -Response JSON object: - -Either HTTP 200 with -``` -{ - status: "OK", -} -``` - -OR return code 403: - -* If token for authenticating is wrong, you get - -``` -{"detail":"Incorrect authentication credentials."} -``` - -* If token that is being verified is wrong, you get - -``` -{"detail":"You do not have permission to perform this action."} -``` - -### GET, POST, ... /ungleichotp/ - -Standard django rest framework behaviour for updating / listing -objects. - - -## Usage: OTP - -The seeds that you receive can be used for TOTP to authenticate your -apps. - - -## Database - -The database saves a list of appuuids with their seeds and the user -assignments as well as whether the appuuid might use the BUS interface. - -Fields: - -- appuuid (a random UUID) -- appname (name chosen by the user) -- username (who this appuuid belongs to) -- seed (a random base32 string) -- trusted (boolean, whether app is allowed to use the BUS and the - verify method) - - -## Environment / Configuration +## Environment / Configuration (unfinished) - POSTGRES_USERNAME -- SECRET_KEY -- random +- SECRET_KEY -- random (?) -## Random notes / stuff + +## Random notes / stuff (unfinished) django.db.backends.postgresql django.contrib.admin @@ -177,32 +125,6 @@ Don’t forget to point AUTH_USER_MODEL to it. Do this before creating any migra -## TODOs - -- [x] serialize / input request -- [x] Make seed read only -- [x] Implement registering of new entries -- [x] OTPSerializer: allow to read seed for admin -- [x] Implement deleting entry -- [x] Include verify in ModelSerializer -- [x] Maybe we map name+realm == User (?) - - name == name@realm - - password is used for admin login (?) - - seed - - custom auth method -- [n] try to fake username for django based on name+realm (?) -- [n] maybe overwrite get_username() (?) -- [x] Use Custom authentication - needs to have a user! -- [x] Implement creating new "User" - - by POST / Model based -- [x] -- [ ] Add tests for verify -- [ ] Add tests for authentication -- [ ] Add proper documentation -- [ ] move totp constants into settings -- [ ] move field lengths into settings -- [ ] make settings adjustable by environment (?) -- [ ] Remove hard coded JSON (?) ### To document @@ -210,6 +132,133 @@ Don’t forget to point AUTH_USER_MODEL to it. Do this before creating any migra * Login via username password interactively * Login via name/realm/token rest +## The library (ungleichotp) + +## The server (ungleichotp-server) + +## The sample client (ungleichotp-client) + +The included client application is a Django application that makes use +of an ungleichotp-server to authenticate requests. + +### Usage + +* Ensure that the ungleichotp-server is running and reachable + + +## Integrating ungleichotp + +### In Django + + +### In other frameworks + +In general, you will need to implement the following into your app for +resources that need to have an authenticated user: + +#### Retrieve name, realm and token from the request + +This is application specific. In the sample Django rest framework, we +use JSON to retrieve this values: + +``` +{ + "name": "info@ungleich.ch", + "token": "947732", + "realm": "ungleich-admin", + "otherdata": "..." +} + +``` +#### Send name, realm and token from the request to the ungleichotp-server + +Post a JSON object to /ungleichotp/verify that contains the following +elements: + +Request JSON object: + +``` +{ + version: "1", + name: "your-name", + realm: "your-realm", + token: "current time based token", + verifyname: "name that wants to be authenticated", + verifyrealm: "realm that wants to be authenticated", + verifytoken: "token that wants to be authenticated", +} +``` + +Response JSON object: + +Either HTTP 200 with +``` +{ + status: "OK", +} +``` + +OR return code 403: + +* If token for authenticating is wrong, you get + +``` +{"detail":"Incorrect authentication credentials."} +``` + +* If token that is being verified is wrong, you get + +``` +{"detail":"You do not have permission to perform this action."} +``` + +#### Authorize the request + +From the ungleichotp-server, you get a validated information that a +name on a realm authenticated successfully. The associated permissions +("authorization") is application specific and needs to be decided by +your application. + + +## TODOs + +- [x] (server) Serialize / input request +- [x] (server) Make seed read only +- [x] (server) Implement registering of new entries +- [x] (server) OTPSerializer: allow to read seed for admin +- [x] (server) Implement deleting entry +- [x] (server) Include verify in ModelSerializer +- [x] (server) Map name+realm == User (?) + - name == name@realm + - password is used for admin login (?) + - seed + - custom auth method +- [n] (server) Try to fake username for django based on name+realm (?) + - No need +- [n] (server) maybe overwrite get_username() + - No need +- [x] (server) Use Custom authentication - needs to have a user! +- [x] (server) Implement creating new "User" by POST / Model based +- [n] (server) Remove hard coded JSON in /verify (no - good enough for the moment) +- [ ] (security) Ensure that only the right realms can verify +- [ ] (security) Ensure that only the right realms can manage +- [ ] (server) Add tests for verify +- [ ] (server) Add tests for authentication +- [ ] (doc) Add proper documentation +- [ ] (server) move totp constants into settings +- [ ] (server) move field lengths into settings +- [ ] (server, client) Make settings adjustable by environment - k8s/docker compatible +- [ ] (server, client) Read DB from outside (?) (fallback to sqlite) +- [ ] (library) Write a "client library" that can use ungleichotp +- [ ] (library) extract generic parts from server +- [ ] (library) upload to pypi +- [ ] (client) Bootstrap Django + DRF (including an object for CRUD) +- [ ] (client) Add custom authentication / remote auth +- [ ] (server) Document how admin vs. rest works +- [ ] (server) Fully rename server from ungleichotp to ungleichotpserver + + + ## Changelog ### 0.6, 2018-11-18 diff --git a/ungleichotp/ungleichotp.py b/ungleichotp/ungleichotp.py new file mode 100644 index 0000000..c417176 --- /dev/null +++ b/ungleichotp/ungleichotp.py @@ -0,0 +1,16 @@ +from django.contrib.auth.models import User +from rest_framework import authentication +from rest_framework import exceptions + +class ExampleAuthentication(authentication.BaseAuthentication): + def authenticate(self, request): + username = request.META.get('X_USERNAME') + if not username: + return None + + try: + user = User.objects.get(username=username) + except User.DoesNotExist: + raise exceptions.AuthenticationFailed('No such user') + + return (user, None) diff --git a/ungleichotp-client/manage.py b/ungleichotpclient/manage.py similarity index 100% rename from ungleichotp-client/manage.py rename to ungleichotpclient/manage.py diff --git a/ungleichotp-client/restapp/__init__.py b/ungleichotpclient/restapp/__init__.py similarity index 100% rename from ungleichotp-client/restapp/__init__.py rename to ungleichotpclient/restapp/__init__.py diff --git a/ungleichotp-client/restapp/admin.py b/ungleichotpclient/restapp/admin.py similarity index 100% rename from ungleichotp-client/restapp/admin.py rename to ungleichotpclient/restapp/admin.py diff --git a/ungleichotp-client/restapp/apps.py b/ungleichotpclient/restapp/apps.py similarity index 100% rename from ungleichotp-client/restapp/apps.py rename to ungleichotpclient/restapp/apps.py diff --git a/ungleichotp-client/restapp/migrations/__init__.py b/ungleichotpclient/restapp/migrations/__init__.py similarity index 100% rename from ungleichotp-client/restapp/migrations/__init__.py rename to ungleichotpclient/restapp/migrations/__init__.py diff --git a/ungleichotp-client/restapp/models.py b/ungleichotpclient/restapp/models.py similarity index 100% rename from ungleichotp-client/restapp/models.py rename to ungleichotpclient/restapp/models.py diff --git a/ungleichotp-client/restapp/tests.py b/ungleichotpclient/restapp/tests.py similarity index 100% rename from ungleichotp-client/restapp/tests.py rename to ungleichotpclient/restapp/tests.py diff --git a/ungleichotp-client/restapp/views.py b/ungleichotpclient/restapp/views.py similarity index 100% rename from ungleichotp-client/restapp/views.py rename to ungleichotpclient/restapp/views.py diff --git a/ungleichotp-client/ungleichotpclient/__init__.py b/ungleichotpclient/ungleichotpclient/__init__.py similarity index 100% rename from ungleichotp-client/ungleichotpclient/__init__.py rename to ungleichotpclient/ungleichotpclient/__init__.py diff --git a/ungleichotp-client/ungleichotpclient/settings.py b/ungleichotpclient/ungleichotpclient/settings.py similarity index 98% rename from ungleichotp-client/ungleichotpclient/settings.py rename to ungleichotpclient/ungleichotpclient/settings.py index ec84f42..51266e6 100644 --- a/ungleichotp-client/ungleichotpclient/settings.py +++ b/ungleichotpclient/ungleichotpclient/settings.py @@ -37,6 +37,8 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'rest_framework', + 'restapp' ] MIDDLEWARE = [ diff --git a/ungleichotp-client/ungleichotpclient/urls.py b/ungleichotpclient/ungleichotpclient/urls.py similarity index 100% rename from ungleichotp-client/ungleichotpclient/urls.py rename to ungleichotpclient/ungleichotpclient/urls.py diff --git a/ungleichotp-client/ungleichotpclient/wsgi.py b/ungleichotpclient/ungleichotpclient/wsgi.py similarity index 100% rename from ungleichotp-client/ungleichotpclient/wsgi.py rename to ungleichotpclient/ungleichotpclient/wsgi.py diff --git a/ungleichotp/manage.py b/ungleichotpserver/manage.py similarity index 96% rename from ungleichotp/manage.py rename to ungleichotpserver/manage.py index e3f72aa..e52987c 100755 --- a/ungleichotp/manage.py +++ b/ungleichotpserver/manage.py @@ -3,7 +3,7 @@ import os import sys if __name__ == '__main__': - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ungleichotp.settings') + os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'ungleichotpserver.settings') try: from django.core.management import execute_from_command_line except ImportError as exc: diff --git a/ungleichotp/otpauth/__init__.py b/ungleichotpserver/otpauth/__init__.py similarity index 100% rename from ungleichotp/otpauth/__init__.py rename to ungleichotpserver/otpauth/__init__.py diff --git a/ungleichotp/otpauth/admin.py b/ungleichotpserver/otpauth/admin.py similarity index 100% rename from ungleichotp/otpauth/admin.py rename to ungleichotpserver/otpauth/admin.py diff --git a/ungleichotp/otpauth/apps.py b/ungleichotpserver/otpauth/apps.py similarity index 100% rename from ungleichotp/otpauth/apps.py rename to ungleichotpserver/otpauth/apps.py diff --git a/ungleichotp/otpauth/migrations/0001_initial.py b/ungleichotpserver/otpauth/migrations/0001_initial.py similarity index 100% rename from ungleichotp/otpauth/migrations/0001_initial.py rename to ungleichotpserver/otpauth/migrations/0001_initial.py diff --git a/ungleichotp/otpauth/migrations/__init__.py b/ungleichotpserver/otpauth/migrations/__init__.py similarity index 100% rename from ungleichotp/otpauth/migrations/__init__.py rename to ungleichotpserver/otpauth/migrations/__init__.py diff --git a/ungleichotp/otpauth/models.py b/ungleichotpserver/otpauth/models.py similarity index 100% rename from ungleichotp/otpauth/models.py rename to ungleichotpserver/otpauth/models.py diff --git a/ungleichotp/otpauth/serializer.py b/ungleichotpserver/otpauth/serializer.py similarity index 95% rename from ungleichotp/otpauth/serializer.py rename to ungleichotpserver/otpauth/serializer.py index dc5f131..05bd0f5 100644 --- a/ungleichotp/otpauth/serializer.py +++ b/ungleichotpserver/otpauth/serializer.py @@ -3,6 +3,7 @@ from otpauth.models import OTPSeed import pyotp import otpauth +# For accessing / modifying the data class OTPSerializer(serializers.ModelSerializer): class Meta: model = OTPSeed @@ -13,6 +14,7 @@ class OTPSerializer(serializers.ModelSerializer): validated_data['seed'] = pyotp.random_base32() return OTPSeed.objects.create(**validated_data) +# For parsing authentication class TokenSerializer(serializers.Serializer): name = serializers.CharField(max_length=128) token = serializers.CharField(max_length=128) @@ -42,6 +44,7 @@ class TokenSerializer(serializers.Serializer): return (db_instance, token_in) +# For verifying a token class VerifySerializer(TokenSerializer): verifyname = serializers.CharField(max_length=128) verifytoken = serializers.CharField(max_length=128) diff --git a/ungleichotp/otpauth/tests.py b/ungleichotpserver/otpauth/tests.py similarity index 100% rename from ungleichotp/otpauth/tests.py rename to ungleichotpserver/otpauth/tests.py diff --git a/ungleichotp/otpauth/views.py b/ungleichotpserver/otpauth/views.py similarity index 99% rename from ungleichotp/otpauth/views.py rename to ungleichotpserver/otpauth/views.py index 234903b..bcd5a6c 100644 --- a/ungleichotp/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/ungleichotp/ungleichotp/__init__.py b/ungleichotpserver/ungleichotpserver/__init__.py similarity index 100% rename from ungleichotp/ungleichotp/__init__.py rename to ungleichotpserver/ungleichotpserver/__init__.py diff --git a/ungleichotp/ungleichotp/settings.py b/ungleichotpserver/ungleichotpserver/settings.py similarity index 97% rename from ungleichotp/ungleichotp/settings.py rename to ungleichotpserver/ungleichotpserver/settings.py index 491d324..b2235f4 100644 --- a/ungleichotp/ungleichotp/settings.py +++ b/ungleichotpserver/ungleichotpserver/settings.py @@ -51,7 +51,7 @@ MIDDLEWARE = [ 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] -ROOT_URLCONF = 'ungleichotp.urls' +ROOT_URLCONF = 'ungleichotpserver.urls' TEMPLATES = [ { @@ -69,7 +69,7 @@ TEMPLATES = [ }, ] -WSGI_APPLICATION = 'ungleichotp.wsgi.application' +WSGI_APPLICATION = 'ungleichotpserver.wsgi.application' # Database diff --git a/ungleichotp/ungleichotp/urls.py b/ungleichotpserver/ungleichotpserver/urls.py similarity index 100% rename from ungleichotp/ungleichotp/urls.py rename to ungleichotpserver/ungleichotpserver/urls.py diff --git a/ungleichotp/ungleichotp/wsgi.py b/ungleichotpserver/ungleichotpserver/wsgi.py similarity index 100% rename from ungleichotp/ungleichotp/wsgi.py rename to ungleichotpserver/ungleichotpserver/wsgi.py