add back developer temporarily for migration
This commit is contained in:
parent
4969b7b371
commit
a6bd62b1a4
18 changed files with 670 additions and 0 deletions
|
@ -305,6 +305,7 @@ INSTALLED_APPS += [
|
|||
"catalog.apps.CatalogConfig",
|
||||
"journal.apps.JournalConfig",
|
||||
"social.apps.SocialConfig",
|
||||
"developer.apps.DeveloperConfig",
|
||||
"takahe.apps.TakaheConfig",
|
||||
"legacy.apps.LegacyConfig",
|
||||
]
|
||||
|
@ -312,6 +313,9 @@ INSTALLED_APPS += [
|
|||
for app in env("NEODB_EXTRA_APPS"):
|
||||
INSTALLED_APPS.append(app)
|
||||
|
||||
INSTALLED_APPS += [ # we may override templates in these 3rd party apps
|
||||
"oauth2_provider",
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
"django.middleware.security.SecurityMiddleware",
|
||||
|
|
0
developer/__init__.py
Normal file
0
developer/__init__.py
Normal file
3
developer/admin.py
Normal file
3
developer/admin.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
6
developer/apps.py
Normal file
6
developer/apps.py
Normal file
|
@ -0,0 +1,6 @@
|
|||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class DeveloperConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "developer"
|
128
developer/migrations/0001_initial.py
Normal file
128
developer/migrations/0001_initial.py
Normal file
|
@ -0,0 +1,128 @@
|
|||
# Generated by Django 3.2.19 on 2023-06-28 05:09
|
||||
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
import markdownx.models
|
||||
import oauth2_provider.generators
|
||||
import oauth2_provider.models
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
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,
|
||||
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,
|
||||
},
|
||||
),
|
||||
]
|
26
developer/migrations/0002_alter_application_user.py
Normal file
26
developer/migrations/0002_alter_application_user.py
Normal file
|
@ -0,0 +1,26 @@
|
|||
# Generated by Django 4.2.3 on 2023-07-06 22:53
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
("developer", "0001_initial"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="application",
|
||||
name="user",
|
||||
field=models.ForeignKey(
|
||||
blank=True,
|
||||
null=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="%(app_label)s_%(class)s",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
),
|
||||
),
|
||||
]
|
19
developer/migrations/0003_alter_application_redirect_uris.py
Normal file
19
developer/migrations/0003_alter_application_redirect_uris.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
# Generated by Django 4.2.3 on 2023-07-13 04:02
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("developer", "0002_alter_application_user"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="application",
|
||||
name="redirect_uris",
|
||||
field=models.TextField(
|
||||
help_text="Allowed URIs list, space separated, at least one URI is required"
|
||||
),
|
||||
),
|
||||
]
|
0
developer/migrations/__init__.py
Normal file
0
developer/migrations/__init__.py
Normal file
33
developer/models.py
Normal file
33
developer/models.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
from django.core.validators import RegexValidator
|
||||
from django.db import models
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from markdownx.models import MarkdownxField
|
||||
from oauth2_provider.models import AbstractApplication
|
||||
|
||||
from journal.models.renderers import render_md
|
||||
|
||||
|
||||
class Application(AbstractApplication):
|
||||
name = models.CharField(
|
||||
max_length=255,
|
||||
blank=False,
|
||||
validators=[
|
||||
RegexValidator(
|
||||
regex=r"^\w[\w_\-. ]*\w$",
|
||||
message=_(
|
||||
"minimum two characters, words and -_. only, no special characters"
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
description = MarkdownxField(default="", blank=True)
|
||||
url = models.URLField(null=True, blank=True)
|
||||
is_official = models.BooleanField(default=False)
|
||||
unique_together = [["user", "name"]]
|
||||
redirect_uris = models.TextField(
|
||||
blank=False,
|
||||
help_text=_("Allowed URIs list, space separated, at least one URI is required"),
|
||||
)
|
||||
|
||||
def description_html(self):
|
||||
return render_md(self.description)
|
112
developer/templates/console.html
Normal file
112
developer/templates/console.html
Normal file
|
@ -0,0 +1,112 @@
|
|||
{% load i18n %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<link rel="stylesheet"
|
||||
href="{{ cdn_url }}/npm/swagger-ui-dist@5.13.0/swagger-ui.min.css">
|
||||
<title>{{ api.title }} Developer Console</title>
|
||||
{% include "common_libs.html" %}
|
||||
<style type="text/css">
|
||||
.information-container, #operations-tag-default {
|
||||
display: none;
|
||||
}
|
||||
.scheme-container {
|
||||
margin: 0 !important;
|
||||
background: none !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
button svg {
|
||||
fill: var(--pico-primary);
|
||||
}
|
||||
#swagger-ui>div>div>.wrapper {
|
||||
background-color: white;
|
||||
padding: 0;
|
||||
}
|
||||
#swagger-ui>div>div>.wrapper button {
|
||||
background-color: unset;
|
||||
}
|
||||
#swagger-ui>div>div>.wrapper button.btn.execute {
|
||||
background-color: #4990e2;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{% include "_header.html" %}
|
||||
<main class="container">
|
||||
<h3>
|
||||
Developer Console | <a href="{% url 'oauth2_provider:list' %}">{% trans "Your applications" %}</a>
|
||||
</h3>
|
||||
<p class="empty">
|
||||
By using our APIs, you agree to our <a href="/pages/terms">Terms of Service</a>.
|
||||
</p>
|
||||
<details {% if token %}open{% endif %}>
|
||||
<summary>
|
||||
<a>Test Access Token</a>
|
||||
</summary>
|
||||
<form method="post" role="group">
|
||||
{% csrf_token %}
|
||||
<input type="text"
|
||||
readonly
|
||||
value="{{ token | default:'Once generated, token will only be shown once here, previous tokens will be revoked.' }}">
|
||||
<input type="submit" value="Generate" />
|
||||
</form>
|
||||
<p>
|
||||
Click <code>Authorize</code> button below, input your token there to invoke APIs with your account, which is required for APIs like <code>/api/me</code>
|
||||
<br>
|
||||
Or use it in command line, like
|
||||
<code>curl -H "Authorization: Bearer YOUR_TOKEN" {{ site_url }}/api/me</code>
|
||||
</p>
|
||||
</details>
|
||||
<br>
|
||||
<details>
|
||||
<summary>
|
||||
<a>How to authorize</a>
|
||||
</summary>
|
||||
0. Create an application (you must have at least one URL included in the Redirect URIs field, e.g. <code>https://example.org/callback</code>)
|
||||
<br>
|
||||
1. Guide your user to open this URL
|
||||
<input type="text"
|
||||
value="{{ site_url }}/auth/oauth/authorize/?response_type=code&client_id=CLIENT_ID&redirect_uri=https://example.org/callback"
|
||||
readonly>
|
||||
2. Once authorizated by user, it will redirect to <code>https://example.org/callback</code> with a <code>code</code> parameter:
|
||||
<input type="text"
|
||||
value="https://example.org/callback?code=AUTH_CODE"
|
||||
readonly>
|
||||
3. Obtain access token with the following POST request:
|
||||
<textarea readonly rows="7">
|
||||
curl -X POST {{ site_url }}/auth/oauth/token/ \
|
||||
-H "Content-Type: application/x-www-form-urlencoded" \
|
||||
-d "client_id=CLIENT_ID" \
|
||||
-d "client_secret=CLIENT_SECRET" \
|
||||
-d "code=AUTH_CODE" \
|
||||
-d "redirect_uri=https://example.org/callback" \
|
||||
-d "grant_type=authorization_code"</textarea>
|
||||
and access token will be returned in the response:
|
||||
<textarea readonly rows="1">{"access_token": "ACCESS_TOKEN", "expires_in": 31536000, "token_type": "Bearer", "scope": "read write", "refresh_token": "REFRESH_TOKEN"}</textarea>
|
||||
4. Use the access token to access protected endpoints like <code>/api/me</code>
|
||||
<textarea readonly rows="1">curl -H "Authorization: Bearer ACCESS_TOKEN" -X GET http://localhost:8000/api/me</textarea>
|
||||
and response will be returned accordingly:
|
||||
<textarea readonly rows="1">{"url": "https://neodb.social/users/xxx@yyy.zzz/", "external_acct": "xxx@yyy.zzz", "display_name": "XYZ", "avatar": "https://yyy.zzz/xxx.gif"}</textarea>
|
||||
more endpoints can be found in API Documentation below.
|
||||
</details>
|
||||
<div id="swagger-ui" data-theme="light"></div>
|
||||
<script src="{{ cdn_url }}/npm/swagger-ui-dist@5.13.0/swagger-ui-bundle.min.js"></script>
|
||||
<script>
|
||||
const ui = SwaggerUIBundle({
|
||||
url: '{{ openapi_json_url }}',
|
||||
dom_id: '#swagger-ui',
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIBundle.SwaggerUIStandalonePreset
|
||||
],
|
||||
layout: "BaseLayout",
|
||||
{% if api.csrf and csrf_token %}
|
||||
requestInterceptor: (req) => {req.headers['X-CSRFToken'] = "{{csrf_token}}"; return req;},
|
||||
{% endif %}
|
||||
deepLinking: true
|
||||
})
|
||||
</script>
|
||||
</main>
|
||||
{% include "_footer.html" %}
|
||||
</body>
|
||||
</html>
|
45
developer/templates/oauth2_provider/application_detail.html
Normal file
45
developer/templates/oauth2_provider/application_detail.html
Normal file
|
@ -0,0 +1,45 @@
|
|||
{% 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 "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>
|
||||
{% if not application.redirect_uris %}WARNING: no redirect uris have been set, authorization may not work.{% endif %}
|
||||
</p>
|
||||
<textarea readonly>{{ application.redirect_uris }}</textarea>
|
||||
</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<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 %}
|
39
developer/templates/oauth2_provider/application_form.html
Normal file
39
developer/templates/oauth2_provider/application_form.html
Normal file
|
@ -0,0 +1,39 @@
|
|||
{% extends "oauth2_provider/base.html" %}
|
||||
{% load i18n %}
|
||||
{% block content %}
|
||||
<form method="post"
|
||||
action="{% block app-form-action-url %}{% url 'oauth2_provider:update' application.id %}{% endblock app-form-action-url %}">
|
||||
<h3>
|
||||
{% block app-form-title %}
|
||||
{% trans "Edit application" %} {{ application.name }}
|
||||
{% endblock app-form-title %}
|
||||
</h3>
|
||||
{% csrf_token %}
|
||||
<fieldset>
|
||||
{% for field in form %}
|
||||
<label {% if field.errors %}aria-invalid="true"{% endif %}>
|
||||
{{ field.label }}
|
||||
{% if field.name == 'client_secret' %}
|
||||
<small><b>(please save it properly as it will NOT be editable or shown again)</b></small>
|
||||
{% elif field.name == 'description' %}
|
||||
<small>(markdown syntax supported)</small>
|
||||
{% endif %}
|
||||
{{ field }}
|
||||
{% for error in field.errors %}<small>{{ error }}</small>{% endfor %}
|
||||
</label>
|
||||
{% endfor %}
|
||||
</fieldset>
|
||||
<div class="control-group {% if form.non_field_errors %}error{% endif %}">
|
||||
{% for error in form.non_field_errors %}<span class="help-inline">{{ error }}</span>{% endfor %}
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<div class="controls">
|
||||
<a class="btn"
|
||||
href="{% block app-form-back-url %}{% url "oauth2_provider:detail" application.id %}{% endblock app-form-back-url %}">
|
||||
{% trans "Go Back" %}
|
||||
</a>
|
||||
<button type="submit" class="btn btn-primary">{% trans "Save" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
27
developer/templates/oauth2_provider/application_list.html
Normal file
27
developer/templates/oauth2_provider/application_list.html
Normal file
|
@ -0,0 +1,27 @@
|
|||
{% extends "oauth2_provider/base.html" %}
|
||||
{% load i18n %}
|
||||
{% block content %}
|
||||
<h3>
|
||||
<a href="{% url 'oauth2_provider:developer' %}">Developer Console</a> | {% trans "Your applications" %}
|
||||
</h3>
|
||||
<div class="block-center">
|
||||
{% if not request.user.mastodon_acct %}
|
||||
<p>
|
||||
Please <a href="{% url 'users:info' %}">connect to a Fediverse identity</a> before creating an application.
|
||||
</p>
|
||||
{% elif applications %}
|
||||
<ul>
|
||||
{% for application in applications %}
|
||||
<li>
|
||||
<a href="{{ application.get_absolute_url }}">{{ application.name }}</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<a class="btn btn-success" href="{% url "oauth2_provider:register" %}">{% trans "New Application" %}</a>
|
||||
{% else %}
|
||||
<p>
|
||||
{% trans "No applications defined" %}. <a href="{% url 'oauth2_provider:register' %}">{% trans "Click here" %}</a> {% trans "if you want to register a new one" %}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock content %}
|
41
developer/templates/oauth2_provider/authorize.html
Normal file
41
developer/templates/oauth2_provider/authorize.html
Normal file
|
@ -0,0 +1,41 @@
|
|||
{% extends "oauth2_provider/base.html" %}
|
||||
{% load i18n %}
|
||||
{% block content %}
|
||||
<article>
|
||||
{% if not error %}
|
||||
<header>
|
||||
<h4>授权 {{ application.name }} 访问你的帐户吗?</h4>
|
||||
</header>
|
||||
<form id="authorizationForm" method="post">
|
||||
{% csrf_token %}
|
||||
{% if not application.is_official %}
|
||||
<p>
|
||||
<b>{{ application.name }}</b> 是由 <a href="{{ application.user.identity.url }}">@{{ application.user.identity.handle }}</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 %}
|
||||
<p>授权后这个应用将能读写你帐户的全部数据。</p>
|
||||
{% comment %} <p>{% trans "Application requires the following permissions" %}</p>
|
||||
<ul>
|
||||
{% for scope in scopes_descriptions %}<li>{{ scope }}</li>{% endfor %}
|
||||
</ul> {% endcomment %}
|
||||
{{ form.errors }}
|
||||
{{ form.non_field_errors }}
|
||||
<footer>
|
||||
<div class="grid">
|
||||
<input type="submit" class="primary" name="allow" value="确认授权" />
|
||||
<input type="submit" class="secondary" value="取消" />
|
||||
</div>
|
||||
</footer>
|
||||
</form>
|
||||
{% else %}
|
||||
<h2>Error: {{ error.error }}</h2>
|
||||
<p>{{ error.description }}</p>
|
||||
{% endif %}
|
||||
</article>
|
||||
{% endblock %}
|
25
developer/templates/oauth2_provider/base.html
Normal file
25
developer/templates/oauth2_provider/base.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
{% load i18n %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
{% block title %}
|
||||
{% endblock title %}
|
||||
<title>{{ site_name }}</title>
|
||||
{% include "common_libs.html" %}
|
||||
<style>
|
||||
.controls input {
|
||||
width: unset;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{% include "_header.html" %}
|
||||
<main class="container">
|
||||
{% block content %}
|
||||
{% endblock content %}
|
||||
</main>
|
||||
{% include "_footer.html" %}
|
||||
</body>
|
||||
</html>
|
3
developer/tests.py
Normal file
3
developer/tests.py
Normal file
|
@ -0,0 +1,3 @@
|
|||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
69
developer/urls.py
Normal file
69
developer/urls.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
from django.contrib import admin
|
||||
from django.contrib.auth import views as auth_views
|
||||
from django.urls import include, path, re_path
|
||||
from oauth2_provider import views as oauth2_views
|
||||
from oauth2_provider.views import oidc as oidc_views
|
||||
|
||||
from .views import *
|
||||
|
||||
_urlpatterns = [
|
||||
re_path(
|
||||
r"^auth/oauth/authorize/$",
|
||||
oauth2_views.AuthorizationView.as_view(),
|
||||
name="authorize",
|
||||
),
|
||||
re_path(r"^oauth/token/$", oauth2_views.TokenView.as_view(), name="token"),
|
||||
re_path(
|
||||
r"^auth/oauth/revoke_token/$",
|
||||
oauth2_views.RevokeTokenView.as_view(),
|
||||
name="revoke-token",
|
||||
),
|
||||
re_path(
|
||||
r"^auth/oauth/introspect/$",
|
||||
oauth2_views.IntrospectTokenView.as_view(),
|
||||
name="introspect",
|
||||
),
|
||||
re_path(
|
||||
r"^auth/oauth/authorized_tokens/$",
|
||||
oauth2_views.AuthorizedTokensListView.as_view(),
|
||||
name="authorized-token-list",
|
||||
),
|
||||
re_path(
|
||||
r"^auth/oauth/authorized_tokens/(?P<pk>[\w-]+)/delete/$",
|
||||
oauth2_views.AuthorizedTokenDeleteView.as_view(),
|
||||
name="authorized-token-delete",
|
||||
),
|
||||
]
|
||||
|
||||
_urlpatterns += [
|
||||
path("developer/", console, name="developer"),
|
||||
re_path(
|
||||
r"^developer/applications/$",
|
||||
oauth2_views.ApplicationList.as_view(),
|
||||
name="list",
|
||||
),
|
||||
re_path(
|
||||
r"^developer/applications/register/$",
|
||||
ApplicationRegistration.as_view(),
|
||||
name="register",
|
||||
),
|
||||
re_path(
|
||||
r"^developer/applications/(?P<pk>[\w-]+)/$",
|
||||
oauth2_views.ApplicationDetail.as_view(),
|
||||
name="detail",
|
||||
),
|
||||
re_path(
|
||||
r"^developer/applications/(?P<pk>[\w-]+)/delete/$",
|
||||
oauth2_views.ApplicationDelete.as_view(),
|
||||
name="delete",
|
||||
),
|
||||
re_path(
|
||||
r"^developer/applications/(?P<pk>[\w-]+)/update/$",
|
||||
ApplicationUpdate.as_view(),
|
||||
name="update",
|
||||
),
|
||||
]
|
||||
|
||||
urlpatterns = [
|
||||
path("", include((_urlpatterns, "oauth2_provider"))),
|
||||
]
|
90
developer/views.py
Normal file
90
developer/views.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
from dateutil.relativedelta import relativedelta
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.forms.models import modelform_factory
|
||||
from django.shortcuts import render
|
||||
from django.urls import reverse
|
||||
from django.utils import timezone
|
||||
from loguru import logger
|
||||
from oauth2_provider.forms import AllowForm
|
||||
from oauth2_provider.generators import generate_client_id, generate_client_secret
|
||||
from oauth2_provider.models import AccessToken, RefreshToken, get_application_model
|
||||
from oauth2_provider.settings import oauth2_settings
|
||||
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 oauthlib.common import generate_token
|
||||
|
||||
from common.api import api
|
||||
|
||||
from .models import Application
|
||||
|
||||
|
||||
class ApplicationRegistration(BaseApplicationRegistration):
|
||||
def get_form_class(self):
|
||||
return modelform_factory(
|
||||
Application,
|
||||
fields=(
|
||||
"name",
|
||||
"url",
|
||||
"description",
|
||||
"client_secret",
|
||||
"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(), # type:ignore
|
||||
fields=(
|
||||
"name",
|
||||
"url",
|
||||
"description",
|
||||
# "client_secret",
|
||||
"redirect_uris",
|
||||
# "post_logout_redirect_uris",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
def console(request):
|
||||
token = None
|
||||
if request.method == "POST":
|
||||
user = request.user
|
||||
app = Application.objects.filter(
|
||||
client_id=settings.DEVELOPER_CONSOLE_APPLICATION_CLIENT_ID
|
||||
).first()
|
||||
if app:
|
||||
for token in AccessToken.objects.filter(user=user, application=app):
|
||||
token.revoke()
|
||||
token = generate_token()
|
||||
AccessToken.objects.create(
|
||||
user=user,
|
||||
application=app,
|
||||
scope="read write",
|
||||
expires=timezone.now() + relativedelta(days=365),
|
||||
token=token,
|
||||
)
|
||||
else:
|
||||
token = "Configuration error, contact admin"
|
||||
context = {
|
||||
"api": api,
|
||||
"token": token,
|
||||
"openapi_json_url": reverse(f"{api.urls_namespace}:openapi-json"),
|
||||
}
|
||||
return render(request, "console.html", context)
|
Loading…
Add table
Reference in a new issue