diff --git a/common/api.py b/common/api.py index ea37d559..09b58b64 100644 --- a/common/api.py +++ b/common/api.py @@ -18,7 +18,7 @@ class OAuthAccessTokenAuth(HttpBearer): _logger.debug("API auth: no access token or user not authenticated") return False request_scopes = [] - if request.method.upper() in ["GET", "HEAD", "OPTIONS"]: + if request.method in ["GET", "HEAD", "OPTIONS"]: request_scopes = ["read"] else: request_scopes = ["write"] diff --git a/developer/migrations/0001_initial.py b/developer/migrations/0001_initial.py index 45828108..eb0d70ca 100644 --- a/developer/migrations/0001_initial.py +++ b/developer/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 3.2.19 on 2023-06-28 02:44 +# Generated by Django 3.2.19 on 2023-06-28 05:09 from django.conf import settings import django.core.validators @@ -10,7 +10,6 @@ import oauth2_provider.models class Migration(migrations.Migration): - initial = True dependencies = [ @@ -19,27 +18,111 @@ class Migration(migrations.Migration): operations = [ migrations.CreateModel( - name='Application', + name="Application", fields=[ - ('id', models.BigAutoField(primary_key=True, serialize=False)), - ('client_id', models.CharField(db_index=True, default=oauth2_provider.generators.generate_client_id, max_length=100, unique=True)), - ('redirect_uris', models.TextField(blank=True, help_text='Allowed URIs list, space separated')), - ('post_logout_redirect_uris', models.TextField(blank=True, help_text='Allowed Post Logout URIs list, space separated')), - ('client_type', models.CharField(choices=[('confidential', 'Confidential'), ('public', 'Public')], max_length=32)), - ('authorization_grant_type', models.CharField(choices=[('authorization-code', 'Authorization code'), ('implicit', 'Implicit'), ('password', 'Resource owner password-based'), ('client-credentials', 'Client credentials'), ('openid-hybrid', 'OpenID connect hybrid')], max_length=32)), - ('client_secret', oauth2_provider.models.ClientSecretField(blank=True, db_index=True, default=oauth2_provider.generators.generate_client_secret, help_text='Hashed on Save. Copy it now if this is a new secret.', max_length=255)), - ('skip_authorization', models.BooleanField(default=False)), - ('created', models.DateTimeField(auto_now_add=True)), - ('updated', models.DateTimeField(auto_now=True)), - ('algorithm', models.CharField(blank=True, choices=[('', 'No OIDC support'), ('RS256', 'RSA with SHA-2 256'), ('HS256', 'HMAC with SHA-2 256')], default='', max_length=5)), - ('name', models.CharField(max_length=255, unique=True, validators=[django.core.validators.RegexValidator(message='至少两个字,不可包含普通文字和-_.以外的字符', regex='^\\w[\\w_\\-. ]*\\w$')])), - ('descrpition', markdownx.models.MarkdownxField(blank=True, default='')), - ('url', models.URLField(blank=True, null=True)), - ('is_official', models.BooleanField(default=False)), - ('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='developer_application', to=settings.AUTH_USER_MODEL)), + ("id", models.BigAutoField(primary_key=True, serialize=False)), + ( + "client_id", + models.CharField( + db_index=True, + default=oauth2_provider.generators.generate_client_id, + max_length=100, + unique=True, + ), + ), + ( + "redirect_uris", + models.TextField( + blank=True, help_text="Allowed URIs list, space separated" + ), + ), + ( + "post_logout_redirect_uris", + models.TextField( + blank=True, + help_text="Allowed Post Logout URIs list, space separated", + ), + ), + ( + "client_type", + models.CharField( + choices=[ + ("confidential", "Confidential"), + ("public", "Public"), + ], + max_length=32, + ), + ), + ( + "authorization_grant_type", + models.CharField( + choices=[ + ("authorization-code", "Authorization code"), + ("implicit", "Implicit"), + ("password", "Resource owner password-based"), + ("client-credentials", "Client credentials"), + ("openid-hybrid", "OpenID connect hybrid"), + ], + max_length=32, + ), + ), + ( + "client_secret", + oauth2_provider.models.ClientSecretField( + blank=True, + db_index=True, + default=oauth2_provider.generators.generate_client_secret, + help_text="Hashed on Save. Copy it now if this is a new secret.", + max_length=255, + ), + ), + ("skip_authorization", models.BooleanField(default=False)), + ("created", models.DateTimeField(auto_now_add=True)), + ("updated", models.DateTimeField(auto_now=True)), + ( + "algorithm", + models.CharField( + blank=True, + choices=[ + ("", "No OIDC support"), + ("RS256", "RSA with SHA-2 256"), + ("HS256", "HMAC with SHA-2 256"), + ], + default="", + max_length=5, + ), + ), + ( + "name", + models.CharField( + max_length=255, + validators=[ + django.core.validators.RegexValidator( + message="minimum two characters, words and -_. only, no special characters", + regex="^\\w[\\w_\\-. ]*\\w$", + ) + ], + ), + ), + ( + "description", + markdownx.models.MarkdownxField(blank=True, default=""), + ), + ("url", models.URLField(blank=True, null=True)), + ("is_official", models.BooleanField(default=False)), + ( + "user", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="developer_application", + to=settings.AUTH_USER_MODEL, + ), + ), ], options={ - 'abstract': False, + "abstract": False, }, ), ] diff --git a/developer/models.py b/developer/models.py index 4c595171..be11d5a5 100644 --- a/developer/models.py +++ b/developer/models.py @@ -1,7 +1,9 @@ from django.db import models from django.core.validators import RegexValidator +from django.utils.translation import gettext_lazy as _ from oauth2_provider.models import AbstractApplication from markdownx.models import MarkdownxField +from journal.renderers import render_md class Application(AbstractApplication): @@ -11,11 +13,16 @@ class Application(AbstractApplication): validators=[ RegexValidator( regex=r"^\w[\w_\-. ]*\w$", - message="至少两个字,不可包含普通文字和-_.以外的字符", + message=_( + "minimum two characters, words and -_. only, no special characters" + ), ), ], - unique=True, ) - descrpition = MarkdownxField(default="", blank=True) + description = MarkdownxField(default="", blank=True) url = models.URLField(null=True, blank=True) is_official = models.BooleanField(default=False) + unique_together = [["user", "name"]] + + def description_html(self): + return render_md(self.description) diff --git a/developer/templates/oauth2_provider/application_detail.html b/developer/templates/oauth2_provider/application_detail.html new file mode 100644 index 00000000..951e8511 --- /dev/null +++ b/developer/templates/oauth2_provider/application_detail.html @@ -0,0 +1,52 @@ +{% extends "oauth2_provider/base.html" %} +{% load i18n %} +{% block content %} +
+

{{ application.name }}

+ +
+ {% trans "Go Back" %} + {% trans "Edit" %} + {% trans "Delete" %} +
+
+{% endblock content %} diff --git a/developer/templates/oauth2_provider/authorize.html b/developer/templates/oauth2_provider/authorize.html index e9bccf22..bf0033b8 100644 --- a/developer/templates/oauth2_provider/authorize.html +++ b/developer/templates/oauth2_provider/authorize.html @@ -10,10 +10,12 @@ {% csrf_token %} {% if not application.is_official %}

- {{ application.name }}{{ application.user.mastodon_username }} 创建和维护的应用程序, 并非来自{{ site_name }}官方。 + {{ application.name }} 是由 {{ application.user.mastodon_username }} 创建和维护的应用程序。 + {{ site_name }}无法保证其安全性和有效性,请自行验证确认后再授权。

{% endif %} {% if application.url %}应用网址: {{ application.url | urlize }}{% endif %} +

{{ application.description_html|safe }}

{% for field in form %} {% if field.is_hidden %}{{ field }}{% endif %} {% endfor %} diff --git a/developer/urls.py b/developer/urls.py index 987e7412..89d93ab8 100644 --- a/developer/urls.py +++ b/developer/urls.py @@ -44,7 +44,7 @@ _urlpatterns += [ ), re_path( r"^developer/applications/register/$", - oauth2_views.ApplicationRegistration.as_view(), + ApplicationRegistration.as_view(), name="register", ), re_path( @@ -59,7 +59,7 @@ _urlpatterns += [ ), re_path( r"^developer/applications/(?P[\w-]+)/update/$", - oauth2_views.ApplicationUpdate.as_view(), + ApplicationUpdate.as_view(), name="update", ), ] diff --git a/developer/views.py b/developer/views.py index 3dce7800..b129e560 100644 --- a/developer/views.py +++ b/developer/views.py @@ -4,9 +4,11 @@ from django.contrib.auth.decorators import login_required from django.urls import reverse from oauth2_provider.forms import AllowForm from oauth2_provider.models import get_application_model -from oauth2_provider.views import ProtectedResourceView +from oauth2_provider.views import ApplicationRegistration as BaseApplicationRegistration +from oauth2_provider.views import ApplicationUpdate as BaseApplicationUpdate from oauth2_provider.views.base import AuthorizationView as BaseAuthorizationView from oauth2_provider.settings import oauth2_settings +from oauth2_provider.generators import generate_client_id, generate_client_secret from common.api import api from oauthlib.common import generate_token from oauth2_provider.models import AccessToken @@ -15,10 +17,47 @@ from dateutil.relativedelta import relativedelta from oauth2_provider.models import RefreshToken from django.conf import settings from .models import Application +from django.forms.models import modelform_factory +from .models import Application -class AuthorizationView(BaseAuthorizationView): - pass +class ApplicationRegistration(BaseApplicationRegistration): + def get_form_class(self): + return modelform_factory( + Application, + fields=( + "name", + "url", + "description", + "redirect_uris", + # "post_logout_redirect_uris", + ), + ) + + def form_valid(self, form): + form.instance.user = self.request.user + if not form.instance.id: + form.instance.client_id = generate_client_id() + form.instance.client_secret = generate_client_secret() + form.instance.client_type = Application.CLIENT_CONFIDENTIAL + form.instance.authorization_grant_type = ( + Application.GRANT_AUTHORIZATION_CODE + ) + return super().form_valid(form) + + +class ApplicationUpdate(BaseApplicationUpdate): + def get_form_class(self): + return modelform_factory( + get_application_model(), + fields=( + "name", + "url", + "description", + "redirect_uris", + # "post_logout_redirect_uris", + ), + ) @login_required diff --git a/users/templates/users/preferences.html b/users/templates/users/preferences.html index 1457a564..a049bf9e 100644 --- a/users/templates/users/preferences.html +++ b/users/templates/users/preferences.html @@ -144,6 +144,15 @@ } +
+
+
+ {% trans '应用管理' %} +

+ 查看已授权的应用程序 +

+
+
{% include "_sidebar.html" with show_profile=1 %}