This commit is contained in:
Your Name 2023-06-28 01:24:49 -04:00 committed by Henri Dickson
parent 8ba8c4490e
commit d27cf87496
8 changed files with 222 additions and 30 deletions

View file

@ -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"]

View file

@ -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,
},
),
]

View file

@ -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)

View file

@ -0,0 +1,52 @@
{% extends "oauth2_provider/base.html" %}
{% load i18n %}
{% block content %}
<div class="block-center">
<h3 class="block-center-heading">{{ application.name }}</h3>
<ul class="unstyled">
<li>
<p>
<b>{% trans "Client ID" %}</b>
</p>
<input class="input-block-level"
type="text"
value="{{ application.client_id }}"
readonly>
</li>
<li>
<p>
<b>{% trans "Client Secret" %}</b>
</p>
<input class="input-block-level"
type="text"
value="{{ application.client_secret }}"
readonly>
</li>
<li>
<p>
<b>{% trans "URL" %}</b>
</p>
<p>{{ application.url | default:"" | urlize }}</p>
</li>
<li>
<p>
<b>{% trans "Description" %}</b>
</p>
<p>{{ application.description_html|safe }}</p>
</li>
<li>
<p>
<b>{% trans "Redirect Uris" %}</b>
</p>
<textarea class="input-block-level" readonly>{{ application.redirect_uris }}</textarea>
</li>
</ul>
<div class="btn-toolbar">
<a class="btn" href="{% url "oauth2_provider:list" %}">{% trans "Go Back" %}</a>
<a class="btn btn-primary"
href="{% url "oauth2_provider:update" application.id %}">{% trans "Edit" %}</a>
<a class="btn btn-danger"
href="{% url "oauth2_provider:delete" application.id %}">{% trans "Delete" %}</a>
</div>
</div>
{% endblock content %}

View file

@ -10,10 +10,12 @@
{% csrf_token %}
{% if not application.is_official %}
<p>
<b>{{ application.name }}</b><a href="{% url 'journal:user_profile' application.user.mastodon_username %}">{{ application.user.mastodon_username }}</a> 创建和维护的应用程序, 并非来自{{ site_name }}官方。
<b>{{ application.name }}</b> 是由 <a href="{% url 'journal:user_profile' application.user.mastodon_username %}">{{ application.user.mastodon_username }}</a> 创建和维护的应用程序。
{{ site_name }}无法保证其安全性和有效性,请自行验证确认后再授权。
</p>
{% endif %}
{% if application.url %}应用网址: {{ application.url | urlize }}{% endif %}
<p>{{ application.description_html|safe }}</p>
{% for field in form %}
{% if field.is_hidden %}{{ field }}{% endif %}
{% endfor %}

View file

@ -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<pk>[\w-]+)/update/$",
oauth2_views.ApplicationUpdate.as_view(),
ApplicationUpdate.as_view(),
name="update",
),
]

View file

@ -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

View file

@ -144,6 +144,15 @@
}
</script>
</section>
<hr>
<section>
<details>
<summary>{% trans '应用管理' %}</summary>
<p>
<a href="{% url 'oauth2_provider:authorized-token-list' %}">查看已授权的应用程序</a>
</p>
</details>
</section>
</article>
</div>
{% include "_sidebar.html" with show_profile=1 %}