From 22af6e1ebe6ee9e0cb56ca9201273f6df5232a22 Mon Sep 17 00:00:00 2001 From: Jelle van der Waa Date: Sun, 12 Mar 2023 17:37:53 +0100 Subject: [PATCH] devel: rest api token auth Implement a simple token authentication header for various "restish" endpoints we might want for adoption/disowning of packages. --- devel/forms.py | 2 +- devel/models.py | 44 ++++++++++++++++++++++++++++++++++++ devel/views.py | 8 ++++++- settings.py | 5 ++++ templates/devel/profile.html | 15 ++++++++++++ 5 files changed, 72 insertions(+), 2 deletions(-) diff --git a/devel/forms.py b/devel/forms.py index e9653216..efb2a055 100644 --- a/devel/forms.py +++ b/devel/forms.py @@ -42,7 +42,7 @@ def clean_pgp_key(self): class Meta: model = UserProfile - exclude = ('allowed_repos', 'user', 'repos_auth_token') + exclude = ('allowed_repos', 'user', 'repos_auth_token', 'api_token') widgets = { 'yob': NumberInput(attrs={'min': 1950, 'max': date.today().year - 10}), } diff --git a/devel/models.py b/devel/models.py index 11fea4b4..0402f2e1 100644 --- a/devel/models.py +++ b/devel/models.py @@ -7,6 +7,8 @@ from django.contrib.auth.models import User, Group from django_countries.fields import CountryField from django.core.validators import MinValueValidator, MaxValueValidator +from django.core.exceptions import ImproperlyConfigured +from django.utils.deprecation import MiddlewareMixin from .fields import PGPKeyField from main.utils import make_choice, set_created_field @@ -14,6 +16,46 @@ from planet.models import Feed +class AuthTokenBackend(object): + def authenticate(self, request, username=None, password=None): + if 'X-Archweb-Token' in request.headers: + try: + profile = UserProfile.objects.get(api_token=request.headers['X-Archweb-Token']) + except UserProfile.DoesNotExist: + return None + + print(profile) + return profile.user + return None + +class AuthTokenMiddleware(MiddlewareMixin): + header = "X-Archweb-Token" + + def process_request(self, request): + print("process", request) + # AuthenticationMiddleware is required so that request.user exists. + if not hasattr(request, "user"): + raise ImproperlyConfigured( + "The Django token user auth middleware requires the" + " authentication middleware to be installed. Edit your" + " MIDDLEWARE setting to insert" + " 'django.contrib.auth.middleware.AuthenticationMiddleware'" + " before the RemoteUserMiddleware class." + ) + try: + token = request.headers[self.header] + print(token) + except KeyError: + return + + try: + profile = UserProfile.objects.get(api_token=token) + except UserProfile.DoesNotExist: + return None + + request.user = profile.user + + class UserProfile(models.Model): latin_name = models.CharField( max_length=255, null=True, blank=True, help_text="Latin-form name; used only for non-Latin full names") @@ -56,6 +98,7 @@ class UserProfile(models.Model): rebuilderd_updates = models.BooleanField( default=False, help_text='Receive reproducible build package updates') repos_auth_token = models.CharField(max_length=32, null=True, blank=True) + api_token = models.CharField(max_length=32, null=True, blank=True) last_modified = models.DateTimeField(editable=False) class Meta: @@ -198,6 +241,7 @@ def delete_user_model(sender, **kwargs): return userprofile.repos_auth_token = '' + userprofile.api_token = '' Feed.objects.filter(website_rss=userprofile.website_rss).delete() diff --git a/devel/views.py b/devel/views.py index 0ea68638..4a401e90 100644 --- a/devel/views.py +++ b/devel/views.py @@ -228,6 +228,11 @@ def tier0_mirror_auth(request): def change_profile(request): profile, _ = UserProfile.objects.get_or_create(user=request.user) if request.POST: + # Generate token + if request.POST.get('api_token'): + profile.api_token = generate_repo_auth_token() + profile.save() + form = ProfileForm(request.POST) profile_form = UserProfileForm(request.POST, request.FILES, @@ -257,7 +262,8 @@ def change_profile(request): profile_form = UserProfileForm(instance=profile) return render(request, 'devel/profile.html', {'form': form, - 'profile_form': profile_form}) + 'profile_form': profile_form, + 'profile': profile}) @login_required diff --git a/settings.py b/settings.py index 977a6746..483f3734 100644 --- a/settings.py +++ b/settings.py @@ -48,11 +48,16 @@ # Set django's User stuff to use our profile model AUTH_PROFILE_MODULE = 'devel.UserProfile' +AUTHENTICATION_BACKENDS = ( + 'django.contrib.auth.backends.ModelBackend', +) + MIDDLEWARE = ( 'django.middleware.common.CommonMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'devel.models.AuthTokenMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.security.SecurityMiddleware', diff --git a/templates/devel/profile.html b/templates/devel/profile.html index 31ac0485..cd94b597 100644 --- a/templates/devel/profile.html +++ b/templates/devel/profile.html @@ -23,5 +23,20 @@

Developer Profile

+
{% csrf_token %} +

API token

+

Token for completing todolist items with for example, rebuild-todo

+ {% if profile.api_token is None %} +
+ +
+

+ {% else %} +
+ + {{ profile.api_token }} +
+ {% endif %} +
{% endblock %}