add authentication
This commit is contained in:
@@ -20,3 +20,6 @@ DRF_SECRET_KEY=
|
|||||||
DRF_DEBUG=
|
DRF_DEBUG=
|
||||||
ALLOWED_HOSTS=
|
ALLOWED_HOSTS=
|
||||||
CSRF_TRUSTED_ORIGINS=
|
CSRF_TRUSTED_ORIGINS=
|
||||||
|
|
||||||
|
OIDC_RP_CLIENT_ID=
|
||||||
|
OIDC_WELLKNOWN=
|
||||||
@@ -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 marbas /app/marbas
|
||||||
COPY --chown=appuser:appuser sde /app/sde
|
COPY --chown=appuser:appuser sde /app/sde
|
||||||
COPY --chown=appuser:appuser api /app/api
|
COPY --chown=appuser:appuser api /app/api
|
||||||
|
COPY --chown=appuser:appuser authentication /app/authentication
|
||||||
|
|
||||||
USER appuser
|
USER appuser
|
||||||
CMD ["uvicorn", "marbas.asgi:application", "--host", "0.0.0.0", "--port", "8000"]
|
CMD ["uvicorn", "marbas.asgi:application", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
0
authentication/__init__.py
Normal file
0
authentication/__init__.py
Normal file
3
authentication/admin.py
Normal file
3
authentication/admin.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
authentication/apps.py
Normal file
6
authentication/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class AuthConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "authentication"
|
||||||
57
authentication/backends.py
Normal file
57
authentication/backends.py
Normal 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
3
authentication/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
9
authentication/urls.py
Normal file
9
authentication/urls.py
Normal 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)),
|
||||||
|
]
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
version: '3'
|
|
||||||
services:
|
services:
|
||||||
migrations:
|
migrations:
|
||||||
image: marbas:local
|
image: marbas:local
|
||||||
@@ -12,6 +11,7 @@ services:
|
|||||||
- ./marbas:/app/marbas
|
- ./marbas:/app/marbas
|
||||||
- ./api:/app/api
|
- ./api:/app/api
|
||||||
- ./sde:/app/sde
|
- ./sde:/app/sde
|
||||||
|
- ./authentication:/app/authentication
|
||||||
- ./manage.py:/app/manage.py
|
- ./manage.py:/app/manage.py
|
||||||
command: sh -c "python manage.py makemigrations && python manage.py migrate"
|
command: sh -c "python manage.py makemigrations && python manage.py migrate"
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -29,6 +29,7 @@ services:
|
|||||||
- ./marbas:/app/marbas
|
- ./marbas:/app/marbas
|
||||||
- ./api:/app/api
|
- ./api:/app/api
|
||||||
- ./sde:/app/sde
|
- ./sde:/app/sde
|
||||||
|
- ./authentication:/app/authentication
|
||||||
- ./manage.py:/app/manage.py
|
- ./manage.py:/app/manage.py
|
||||||
- ./static_eve:/app/static_eve
|
- ./static_eve:/app/static_eve
|
||||||
command: ["uvicorn", "marbas.asgi:application", "--host", "0.0.0.0", "--port", "8000", "--reload"]
|
command: ["uvicorn", "marbas.asgi:application", "--host", "0.0.0.0", "--port", "8000", "--reload"]
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ https://docs.djangoproject.com/en/4.2/ref/settings/
|
|||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import os
|
import os
|
||||||
|
import requests
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
@@ -42,21 +43,27 @@ REST_FRAMEWORK = {
|
|||||||
'rest_framework.parsers.JSONParser',
|
'rest_framework.parsers.JSONParser',
|
||||||
],
|
],
|
||||||
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
|
'DEFAULT_FILTER_BACKENDS': ['django_filters.rest_framework.DjangoFilterBackend'],
|
||||||
|
'DEFAULT_AUTHENTICATION_CLASSES': [
|
||||||
|
'mozilla_django_oidc.contrib.drf.OIDCAuthentication',
|
||||||
|
# 'rest_framework.authentication.SessionAuthentication',
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
|
'authentication',
|
||||||
'api',
|
'api',
|
||||||
'sde',
|
'sde',
|
||||||
'esi',
|
'esi',
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
|
'mozilla_django_oidc',
|
||||||
'django.contrib.contenttypes',
|
'django.contrib.contenttypes',
|
||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
'django_filters',
|
'django_filters',
|
||||||
'health_check',
|
'health_check',
|
||||||
'rest_framework'
|
'rest_framework',
|
||||||
]
|
]
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
@@ -133,6 +140,12 @@ AUTH_PASSWORD_VALIDATORS = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
AUTHENTICATION_BACKENDS = [
|
||||||
|
'authentication.backends.CustomOIDCBackend',
|
||||||
|
'authentication.backends.EveAuthBackend',
|
||||||
|
'django.contrib.auth.backends.ModelBackend',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
# Internationalization
|
||||||
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
# 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_SSO_CALLBACK_URL = os.getenv("ESI_CALLBACK_URL")
|
||||||
ESI_USER_AGENT = os.getenv("ESI_USER_AGENT")
|
ESI_USER_AGENT = os.getenv("ESI_USER_AGENT")
|
||||||
ESI_USER_CONTACT_EMAIL = 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"]
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ from django.views.generic import TemplateView
|
|||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('api/', include("api.urls")),
|
path('api/', include("api.urls")),
|
||||||
path('sde/', include("sde.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('sso/', include('esi.urls', namespace='esi')),
|
||||||
path('openapi/', get_schema_view(
|
path('openapi/', get_schema_view(
|
||||||
title="marbas",
|
title="marbas",
|
||||||
|
|||||||
@@ -10,3 +10,4 @@ uritemplate
|
|||||||
inflection
|
inflection
|
||||||
django-esi
|
django-esi
|
||||||
django-health-check
|
django-health-check
|
||||||
|
mozilla-django-oidc
|
||||||
Reference in New Issue
Block a user