Skip to content
Snippets Groups Projects
Verified Commit 142db3d0 authored by Nik | Klampfradler's avatar Nik | Klampfradler
Browse files

Merge branch 'master' into 534-default-admins-setting-broken

parents 32fd3f1a 77734ee6
No related branches found
No related tags found
1 merge request!764Resolve "Default ADMINS setting broken"
Pipeline #39202 failed
......@@ -14,6 +14,7 @@ Added
* Provide an ``ExtensiblePolymorphicModel`` to support the features of extensible models for polymorphic models and vice-versa.
* Implement optional Sentry integration for error and performance tracing.
* Option to limit allowed scopes per application, including mixin to enforce that limit on OAuth resource views
* Support trusted OAuth applications that leave out the authorisation screen.
Changed
......@@ -28,6 +29,12 @@ Fixed
* Fix default admin contacts
Credits
~~~~~~~
* We welcome new contributor 🐧 Jonathan Krüger!
* We welcome new contributor 🐭 Lukas Weichelt!
`2.0`_ - 2021-10-29
-------------------
......
......@@ -31,6 +31,7 @@ from .registries import (
person_preferences_registry,
site_preferences_registry,
)
from .util.auth_helpers import AppScopes
from .util.core_helpers import get_site_preferences
......@@ -594,6 +595,12 @@ class ListActionForm(ActionForm):
class OAuthApplicationForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields["allowed_scopes"].widget = forms.SelectMultiple(
choices=list(AppScopes().get_all_scopes().items())
)
class Meta:
model = OAuthApplication
fields = (
......@@ -601,6 +608,7 @@ class OAuthApplicationForm(forms.ModelForm):
"client_id",
"client_secret",
"client_type",
"allowed_scopes",
"redirect_uris",
"skip_authorization",
)
# Generated by Django 3.2.8 on 2021-11-05 10:37
import django.contrib.postgres.fields
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0025_oauth_align_user_fk'),
]
operations = [
migrations.AddField(
model_name='oauthapplication',
name='allowed_scopes',
field=django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=32), blank=True, null=True, size=None, verbose_name='Allowed scopes that clients can request'),
),
]
......@@ -9,6 +9,7 @@ from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group as DjangoGroup
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.contrib.postgres.fields import ArrayField
from django.contrib.sites.models import Site
from django.core.exceptions import ValidationError
from django.core.validators import MaxValueValidator
......@@ -1107,6 +1108,14 @@ class OAuthApplication(AbstractApplication):
max_length=32, choices=AbstractApplication.GRANT_TYPES, blank=True, null=True
)
# Optional list of alloewd scopes
allowed_scopes = ArrayField(
models.CharField(max_length=32),
verbose_name=_("Allowed scopes that clients can request"),
null=True,
blank=True,
)
def allows_grant_type(self, *grant_types: set[str]) -> bool:
allowed_grants = get_site_preferences()["auth__oauth_allowed_grants"]
......
......@@ -46,6 +46,14 @@
{{ application.client_type }}
</td>
</tr>
<tr>
<th>
{% trans "Allowed scopes" %}
</th>
<td>
{{ application.allowed_scopes|join:", " }}
</td>
</tr>
<tr>
<th>
{% trans "Redirect URIs" %}
......@@ -54,6 +62,14 @@
{{ application.redirect_uris }}
</td>
</tr>
<tr>
<th>
{% trans "Skip Authorisation" %}
</th>
<td>
<i class="material-icons">{{ application.skip_authorization|yesno:"check,close" }}</i>
</td>
</tr>
</tbody>
</table>
{% endblock %}
......@@ -10,6 +10,10 @@ from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from oauth2_provider.models import AbstractApplication
from oauth2_provider.oauth2_validators import OAuth2Validator
from oauth2_provider.scopes import BaseScopes
from oauth2_provider.views.mixins import (
ClientProtectedResourceMixin as _ClientProtectedResourceMixin,
)
from oauthlib.common import Request as OauthlibRequest
from .apps import AppConfig
from .core_helpers import get_site_preferences, has_person
......@@ -106,6 +110,9 @@ class AppScopes(BaseScopes):
scopes = []
for app in AppConfig.__subclasses__():
scopes += app.get_available_scopes()
# Filter by allowed scopes of requesting application
if application and application.allowed_scopes:
scopes = list(filter(lambda scope: scope in application.allowed_scopes, scopes))
return scopes
def get_default_scopes(
......@@ -118,4 +125,43 @@ class AppScopes(BaseScopes):
scopes = []
for app in AppConfig.__subclasses__():
scopes += app.get_default_scopes()
# Filter by allowed scopes of requesting application
if application and application.allowed_scopes:
scopes = list(filter(lambda scope: scope in application.allowed_scopes, scopes))
return scopes
class ClientProtectedResourceMixin(_ClientProtectedResourceMixin):
"""Mixin for protecting resources with client authentication as mentioned in rfc:`3.2.1`.
This involves authenticating with any of: HTTP Basic Auth, Client Credentials and
Access token in that order. Breaks off after first validation.
This sub-class extends the functionality of Django OAuth Toolkit's mixin with support
for AlekSIS's `allowed_scopes` feature. For applications that have configured allowed
scopes, the required scopes for the view are checked to be a subset of the application's
allowed scopes (best to be combined with ScopedResourceMixin).
"""
def authenticate_client(self, request: HttpRequest) -> bool:
"""Return a boolean representing if client is authenticated with client credentials.
If the view has configured required scopes, they are verified against the application's
allowed scopes.
"""
# Build an OAuth request so we can handle client information
core = self.get_oauthlib_core()
uri, http_method, body, headers = core._extract_params(request)
oauth_request = OauthlibRequest(uri, http_method, body, headers)
# Verify general authentication of the client
if not core.server.request_validator.authenticate_client(oauth_request):
# Client credentials were invalid
return False
# Verify scopes of configured application
# The OAuth request was enriched with a reference to the Application when using the
# validator above.
required_scopes = set(self.get_scopes() or [])
allowed_scopes = set(AppScopes().get_available_scopes(oauth_request.client) or [])
return required_scopes.issubset(allowed_scopes)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment