# ungleich-otp 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). ## Setup instructions ## This is a standard django project and thus can be easily setup using ``` pip install -r requirements.txt ``` To bootstrap the application, you need your very first trusted seed to access the application. You can generate it using ``` to be filled in ``` After that, you can run the application using ``` python manage.py runserver ``` The usual instructions on how to setup an https proxy should be followed. ## Realms ## Access is granting/denied based on realms. There are two reserved realms, all other realms can be used by the users: ### Reserved realms Conceptually the realms "ungleich-admin" and "ungleich-auth" are reserved for higher priviliged applications. Usually there is only 1 entry in ungleich-admin that is used to bootstrap and manage ungleich-otp. All micro services that are trusted to authenticate another micro service should have an entry in the ungleich-auth realm, which allows them to verify a token of somebody else. | Name | Capabilities | |------------------+--------------------------------------------| | ungleich-admin | authenticate, create, delete, list, update | | ungleich-auth | authenticate | | 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 ``` { status: "OK", } ``` OR ``` { status: "FAIL", } ``` ### POST /register Register a new seed. Returns an app ID. Request JSON object: ``` { version: "1", appuuid: "your-app-uuid", token: "current time based token", username: "user this app belongs to", appname: "name of your web app" } ``` Response JSON object: ``` { status: "OK", appuuid: "UUID of your app", } ``` OR ``` { status: "FAIL", error: "Reason for failure" } ``` ### POST /app/register Register a new app. Returns an app ID. Request JSON object: ``` { version: "1", appuuid: "your-app-uuid", token: "current time based token", username: "user this app belongs to", appname: "name of your web app" } ``` Response JSON object: ``` { status: "OK", appuuid: "UUID of your app", } ``` OR ``` { status: "FAIL", error: "Reason for failure" } ``` ### GET /app List all registered apps for the current user. Request JSON object: ``` { version: "1", appuuid: "your-app-uuid", token: "current time based token" } ``` Response JSON object: ``` { status: "OK", apps: [ { name: "name of your web app" appuuid: "UUID of your app", }, { name: "name of your second web app" appuuid: "UUID of your second app", } ] } ``` ### GET /app/UUID Get seed for APP to be used as a token Request JSON object: ``` { version: "1", appuuid: "your-app-uuid", token: "current time based token" } ``` Response JSON object: ``` { status: "OK", seed: "seed of your app" } ``` ## 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 - POSTGRES_USERNAME - SECRET_KEY -- random ## Random notes / stuff django.db.backends.postgresql django.contrib.admin ``` DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'mydatabase', 'USER': 'mydatabaseuser', 'PASSWORD': 'mypassword', 'HOST': '127.0.0.1', 'PORT': '5432', } } ``` Custom auth ``` 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) ``` ## TODOs - [x] serialize / input request - [ ] Remove hard coded JSON - [ ] Implement registering of new entries - [ ] Use Custom authentication (?) - set User - [ ] Maybe we map name+realm == User (?)