add authentication

This commit is contained in:
2024-05-17 16:14:02 +02:00
parent cb6ae7c06d
commit 4c39fed29b
12 changed files with 111 additions and 4 deletions

View File

@@ -19,4 +19,7 @@ POSTGRES_DB=
DRF_SECRET_KEY=
DRF_DEBUG=
ALLOWED_HOSTS=
CSRF_TRUSTED_ORIGINS=
CSRF_TRUSTED_ORIGINS=
OIDC_RP_CLIENT_ID=
OIDC_WELLKNOWN=

View File

@@ -12,6 +12,7 @@ RUN python -m pip install --no-cache-dir --upgrade -r requirements.txt
COPY --chown=appuser:appuser marbas /app/marbas
COPY --chown=appuser:appuser sde /app/sde
COPY --chown=appuser:appuser api /app/api
COPY --chown=appuser:appuser authentication /app/authentication
USER appuser
CMD ["uvicorn", "marbas.asgi:application", "--host", "0.0.0.0", "--port", "8000"]

View File

3
authentication/admin.py Normal file
View File

@@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
authentication/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class AuthConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "authentication"

View File

@@ -0,0 +1,57 @@
from functools import wraps
from django.core.cache import cache
from django.http import JsonResponse
from django.contrib.auth import authenticate
from django.contrib.auth.models import User
from django.contrib.auth.backends import ModelBackend
from rest_framework.exceptions import AuthenticationFailed
from mozilla_django_oidc.auth import OIDCAuthenticationBackend
from mozilla_django_oidc.contrib.drf import OIDCAuthentication
class CustomOIDCBackend(OIDCAuthenticationBackend):
def get_username(self, claims):
if 'preferred_username' in claims and not User.objects.filter(username=claims['preferred_username']).exists():
print(claims['preferred_username'])
return claims['preferred_username']
return super().get_username(claims)
def authenticate(self, request, **kwargs):
"""Hack to use the same auth as DRF"""
back = OIDCAuthentication()
try:
u, tok = back.authenticate(request)
except AuthenticationFailed:
u = None
return u
def get_userinfo(self, access_token, id_token, payload):
userinfo = cache.get(f'userinfo-{access_token}')
if userinfo is None:
print("no cache found for userinfo-{access_token} yet.")
userinfo = super().get_userinfo(access_token, id_token, payload)
if userinfo:
cache.set(f'userinfo-{access_token}', userinfo, timeout=60*60*24)
return userinfo
def update_user(self, user, claims): # TODO: update groups?
return super().update_user(user, claims)
def create_user(self, claims): # TODO: add groups?
return super().create_user(claims)
def login_required(func):
@wraps(func)
def wrapper(request, *args, **kwargs):
if request.META.get("HTTP_AUTHORIZATION", "").startswith("Bearer"):
if not hasattr(request, "user") or request.user.is_anonymous:
user = authenticate(request=request)
if not user:
return JsonResponse({"error": "Unauthorized"}, status=401)
request.user = request._cached_user = user
return func(request, *args, **kwargs)
return wrapper

3
authentication/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

9
authentication/urls.py Normal file
View File

@@ -0,0 +1,9 @@
from . import views
from django.urls import include, path
from rest_framework import routers
router = routers.DefaultRouter()
urlpatterns = [
path('', include(router.urls)),
]

View File

@@ -1,4 +1,3 @@
version: '3'
services:
migrations:
image: marbas:local
@@ -12,6 +11,7 @@ services:
- ./marbas:/app/marbas
- ./api:/app/api
- ./sde:/app/sde
- ./authentication:/app/authentication
- ./manage.py:/app/manage.py
command: sh -c "python manage.py makemigrations && python manage.py migrate"
depends_on:
@@ -29,6 +29,7 @@ services:
- ./marbas:/app/marbas
- ./api:/app/api
- ./sde:/app/sde
- ./authentication:/app/authentication
- ./manage.py:/app/manage.py
- ./static_eve:/app/static_eve
command: ["uvicorn", "marbas.asgi:application", "--host", "0.0.0.0", "--port", "8000", "--reload"]

View File

@@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/4.2/ref/settings/
from pathlib import Path
import os
import requests
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
@@ -42,21 +43,27 @@ REST_FRAMEWORK = {
'rest_framework.parsers.JSONParser',
],
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
'DEFAULT_AUTHENTICATION_CLASSES': [
'mozilla_django_oidc.contrib.drf.OIDCAuthentication',
# 'rest_framework.authentication.SessionAuthentication',
],
}
INSTALLED_APPS = [
'authentication',
'api',
'sde',
'esi',
'django.contrib.admin',
'django.contrib.auth',
'mozilla_django_oidc',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_filters',
'health_check',
'rest_framework'
'rest_framework',
]
MIDDLEWARE = [
@@ -133,6 +140,12 @@ AUTH_PASSWORD_VALIDATORS = [
},
]
AUTHENTICATION_BACKENDS = [
'authentication.backends.CustomOIDCBackend',
'authentication.backends.EveAuthBackend',
'django.contrib.auth.backends.ModelBackend',
]
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
@@ -161,3 +174,13 @@ ESI_SSO_CLIENT_SECRET = os.getenv("ESI_SECRET_KEY")
ESI_SSO_CALLBACK_URL = os.getenv("ESI_CALLBACK_URL")
ESI_USER_AGENT = os.getenv("ESI_USER_AGENT")
ESI_USER_CONTACT_EMAIL = os.getenv("ESI_USER_AGENT")
ESI_SCOPES = ['publicData', 'esi-calendar.respond_calendar_events.v1', 'esi-calendar.read_calendar_events.v1', 'esi-location.read_location.v1', 'esi-location.read_ship_type.v1', 'esi-mail.organize_mail.v1', 'esi-mail.read_mail.v1', 'esi-mail.send_mail.v1', 'esi-skills.read_skills.v1', 'esi-skills.read_skillqueue.v1', 'esi-wallet.read_character_wallet.v1', 'esi-wallet.read_corporation_wallet.v1', 'esi-search.search_structures.v1', 'esi-clones.read_clones.v1', 'esi-characters.read_contacts.v1', 'esi-universe.read_structures.v1', 'esi-bookmarks.read_character_bookmarks.v1', 'esi-killmails.read_killmails.v1', 'esi-corporations.read_corporation_membership.v1', 'esi-assets.read_assets.v1', 'esi-planets.manage_planets.v1', 'esi-fleets.read_fleet.v1', 'esi-fleets.write_fleet.v1', 'esi-ui.open_window.v1', 'esi-ui.write_waypoint.v1', 'esi-characters.write_contacts.v1', 'esi-fittings.read_fittings.v1', 'esi-fittings.write_fittings.v1', 'esi-markets.structure_markets.v1', 'esi-corporations.read_structures.v1', 'esi-characters.read_loyalty.v1', 'esi-characters.read_opportunities.v1', 'esi-characters.read_chat_channels.v1', 'esi-characters.read_medals.v1', 'esi-characters.read_standings.v1', 'esi-characters.read_agents_research.v1', 'esi-industry.read_character_jobs.v1', 'esi-markets.read_character_orders.v1', 'esi-characters.read_blueprints.v1', 'esi-characters.read_corporation_roles.v1', 'esi-location.read_online.v1', 'esi-contracts.read_character_contracts.v1', 'esi-clones.read_implants.v1', 'esi-characters.read_fatigue.v1', 'esi-killmails.read_corporation_killmails.v1', 'esi-corporations.track_members.v1', 'esi-wallet.read_corporation_wallets.v1', 'esi-characters.read_notifications.v1', 'esi-corporations.read_divisions.v1', 'esi-corporations.read_contacts.v1', 'esi-assets.read_corporation_assets.v1', 'esi-corporations.read_titles.v1', 'esi-corporations.read_blueprints.v1', 'esi-bookmarks.read_corporation_bookmarks.v1', 'esi-contracts.read_corporation_contracts.v1', 'esi-corporations.read_standings.v1', 'esi-corporations.read_starbases.v1', 'esi-industry.read_corporation_jobs.v1', 'esi-markets.read_corporation_orders.v1', 'esi-corporations.read_container_logs.v1', 'esi-industry.read_character_mining.v1', 'esi-industry.read_corporation_mining.v1', 'esi-planets.read_customs_offices.v1', 'esi-corporations.read_facilities.v1', 'esi-corporations.read_medals.v1', 'esi-characters.read_titles.v1', 'esi-alliances.read_contacts.v1', 'esi-characters.read_fw_stats.v1', 'esi-corporations.read_fw_stats.v1', 'esi-characterstats.read.v1']
OIDC_RP_CLIENT_ID = os.getenv("OIDC_RP_CLIENT_ID")
OIDC_RP_CLIENT_SECRET = ""
if WN := os.getenv("OIDC_WELLKNOWN"):
oauth_conf = requests.get(WN).json()
OIDC_OP_AUTHORIZATION_ENDPOINT = oauth_conf["authorization_endpoint"]
OIDC_OP_TOKEN_ENDPOINT = oauth_conf["token_endpoint"]
OIDC_OP_USER_ENDPOINT = oauth_conf["userinfo_endpoint"]

View File

@@ -22,7 +22,7 @@ from django.views.generic import TemplateView
urlpatterns = [
path('api/', include("api.urls")),
path('sde/', include("sde.urls")),
path('api-auth/', include('rest_framework.urls', namespace='rest_framework')),
path('auth/', include("authentication.urls")),
path('sso/', include('esi.urls', namespace='esi')),
path('openapi/', get_schema_view(
title="marbas",

View File

@@ -10,3 +10,4 @@ uritemplate
inflection
django-esi
django-health-check
mozilla-django-oidc