sources/oauth: improve UI with prefilled urls (when customizable) and hiding provider type
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
parent
00a666856d
commit
b4f738492d
|
@ -74,6 +74,8 @@ class SourceViewSet(
|
||||||
for subclass in all_subclasses(self.queryset.model):
|
for subclass in all_subclasses(self.queryset.model):
|
||||||
subclass: Source
|
subclass: Source
|
||||||
component = ""
|
component = ""
|
||||||
|
if len(subclass.__subclasses__()) > 0:
|
||||||
|
continue
|
||||||
if subclass._meta.abstract:
|
if subclass._meta.abstract:
|
||||||
component = subclass.__bases__[0]().component
|
component = subclass.__bases__[0]().component
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
"""OAuth Source Serializer"""
|
"""OAuth Source Serializer"""
|
||||||
from django.urls.base import reverse_lazy
|
from django.urls.base import reverse_lazy
|
||||||
from drf_spectacular.utils import extend_schema, extend_schema_field
|
from drf_spectacular.types import OpenApiTypes
|
||||||
|
from drf_spectacular.utils import OpenApiParameter, extend_schema, extend_schema_field
|
||||||
from rest_framework.decorators import action
|
from rest_framework.decorators import action
|
||||||
from rest_framework.fields import BooleanField, CharField, SerializerMethodField
|
from rest_framework.fields import BooleanField, CharField, SerializerMethodField
|
||||||
from rest_framework.request import Request
|
from rest_framework.request import Request
|
||||||
|
@ -12,7 +13,7 @@ from authentik.core.api.sources import SourceSerializer
|
||||||
from authentik.core.api.used_by import UsedByMixin
|
from authentik.core.api.used_by import UsedByMixin
|
||||||
from authentik.core.api.utils import PassiveSerializer
|
from authentik.core.api.utils import PassiveSerializer
|
||||||
from authentik.sources.oauth.models import OAuthSource
|
from authentik.sources.oauth.models import OAuthSource
|
||||||
from authentik.sources.oauth.types.manager import MANAGER
|
from authentik.sources.oauth.types.manager import MANAGER, SourceType
|
||||||
|
|
||||||
|
|
||||||
class SourceTypeSerializer(PassiveSerializer):
|
class SourceTypeSerializer(PassiveSerializer):
|
||||||
|
@ -100,11 +101,26 @@ class OAuthSourceViewSet(UsedByMixin, ModelViewSet):
|
||||||
]
|
]
|
||||||
ordering = ["name"]
|
ordering = ["name"]
|
||||||
|
|
||||||
@extend_schema(responses={200: SourceTypeSerializer(many=True)})
|
@extend_schema(
|
||||||
|
responses={200: SourceTypeSerializer(many=True)},
|
||||||
|
parameters=[
|
||||||
|
OpenApiParameter(
|
||||||
|
name="name",
|
||||||
|
location=OpenApiParameter.QUERY,
|
||||||
|
type=OpenApiTypes.STR,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
@action(detail=False, pagination_class=None, filter_backends=[])
|
@action(detail=False, pagination_class=None, filter_backends=[])
|
||||||
def source_types(self, request: Request) -> Response:
|
def source_types(self, request: Request) -> Response:
|
||||||
"""Get all creatable source types"""
|
"""Get all creatable source types. If ?name is set, only returns the type for <name>.
|
||||||
|
If <name> isn't found, returns the default type."""
|
||||||
data = []
|
data = []
|
||||||
for source_type in MANAGER.get():
|
if "name" in request.query_params:
|
||||||
data.append(SourceTypeSerializer(source_type).data)
|
source_type = MANAGER.find_type(request.query_params.get("name"))
|
||||||
|
if source_type.__class__ != SourceType:
|
||||||
|
data.append(SourceTypeSerializer(source_type).data)
|
||||||
|
else:
|
||||||
|
for source_type in MANAGER.get():
|
||||||
|
data.append(SourceTypeSerializer(source_type).data)
|
||||||
return Response(data)
|
return Response(data)
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
# Generated by Django 3.2.5 on 2021-08-21 13:41
|
||||||
|
from django.apps.registry import Apps
|
||||||
|
from django.db import migrations
|
||||||
|
from django.db.backends.base.schema import BaseDatabaseSchemaEditor
|
||||||
|
|
||||||
|
|
||||||
|
def update_provider_types(apps: Apps, schema_editor: BaseDatabaseSchemaEditor):
|
||||||
|
OAuthSource = apps.get_model("authentik_sources_oauth", "oauthsource")
|
||||||
|
|
||||||
|
db_alias = schema_editor.connection.alias
|
||||||
|
|
||||||
|
for source in OAuthSource.objects.using(db_alias).all():
|
||||||
|
changed = False
|
||||||
|
if source.provider_type == "azure-ad":
|
||||||
|
source.provider_type = "azuread"
|
||||||
|
changed = True
|
||||||
|
if source.provider_type == "openid-connect":
|
||||||
|
source.provider_type = "openidconnect"
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
if changed:
|
||||||
|
source.save()
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("authentik_sources_oauth", "0004_auto_20210417_1900"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(update_provider_types),
|
||||||
|
]
|
|
@ -75,7 +75,7 @@ class AzureADType(SourceType):
|
||||||
callback_view = AzureADOAuthCallback
|
callback_view = AzureADOAuthCallback
|
||||||
redirect_view = AzureADOAuthRedirect
|
redirect_view = AzureADOAuthRedirect
|
||||||
name = "Azure AD"
|
name = "Azure AD"
|
||||||
slug = "azure-ad"
|
slug = "azuread"
|
||||||
|
|
||||||
urls_customizable = True
|
urls_customizable = True
|
||||||
|
|
||||||
|
|
|
@ -40,6 +40,6 @@ class OpenIDConnectType(SourceType):
|
||||||
callback_view = OpenIDConnectOAuth2Callback
|
callback_view = OpenIDConnectOAuth2Callback
|
||||||
redirect_view = OpenIDConnectOAuthRedirect
|
redirect_view = OpenIDConnectOAuthRedirect
|
||||||
name = "OpenID Connect"
|
name = "OpenID Connect"
|
||||||
slug = "openid-connect"
|
slug = "openidconnect"
|
||||||
|
|
||||||
urls_customizable = True
|
urls_customizable = True
|
||||||
|
|
|
@ -13178,7 +13178,14 @@ paths:
|
||||||
/api/v2beta/sources/oauth/source_types/:
|
/api/v2beta/sources/oauth/source_types/:
|
||||||
get:
|
get:
|
||||||
operationId: sources_oauth_source_types_list
|
operationId: sources_oauth_source_types_list
|
||||||
description: Get all creatable source types
|
description: |-
|
||||||
|
Get all creatable source types. If ?name is set, only returns the type for <name>.
|
||||||
|
If <name> isn't found, returns the default type.
|
||||||
|
parameters:
|
||||||
|
- in: query
|
||||||
|
name: name
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
tags:
|
tags:
|
||||||
- sources
|
- sources
|
||||||
security:
|
security:
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
UserMatchingModeEnum,
|
UserMatchingModeEnum,
|
||||||
OAuthSourceRequest,
|
OAuthSourceRequest,
|
||||||
FlowsInstancesListDesignationEnum,
|
FlowsInstancesListDesignationEnum,
|
||||||
|
SourceType,
|
||||||
} from "authentik-api";
|
} from "authentik-api";
|
||||||
import { t } from "@lingui/macro";
|
import { t } from "@lingui/macro";
|
||||||
import { customElement, property } from "lit-element";
|
import { customElement, property } from "lit-element";
|
||||||
|
@ -25,19 +26,28 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
|
||||||
slug: pk,
|
slug: pk,
|
||||||
})
|
})
|
||||||
.then((source) => {
|
.then((source) => {
|
||||||
this.showUrlOptions = first(source.type?.urlsCustomizable, false);
|
this.providerType = source.type;
|
||||||
return source;
|
return source;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_modelName?: string;
|
||||||
|
|
||||||
@property()
|
@property()
|
||||||
modelName?: string;
|
set modelName(v: string | undefined) {
|
||||||
|
this._modelName = v;
|
||||||
|
new SourcesApi(DEFAULT_CONFIG).sourcesOauthSourceTypesList({
|
||||||
|
name: v?.replace("oauthsource", ""),
|
||||||
|
}).then((type) => {
|
||||||
|
this.providerType = type[0];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
get modelName(): string|undefined {
|
||||||
|
return this._modelName;
|
||||||
|
}
|
||||||
|
|
||||||
@property({ type: Boolean })
|
@property({ attribute: false })
|
||||||
showUrlOptions = false;
|
providerType?: SourceType;
|
||||||
|
|
||||||
@property({ type: Boolean })
|
|
||||||
showRequestTokenURL = false;
|
|
||||||
|
|
||||||
getSuccessMessage(): string {
|
getSuccessMessage(): string {
|
||||||
if (this.instance) {
|
if (this.instance) {
|
||||||
|
@ -61,7 +71,7 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
|
||||||
};
|
};
|
||||||
|
|
||||||
renderUrlOptions(): TemplateResult {
|
renderUrlOptions(): TemplateResult {
|
||||||
if (!this.showUrlOptions) {
|
if (!this.providerType?.urlsCustomizable) {
|
||||||
return html``;
|
return html``;
|
||||||
}
|
}
|
||||||
return html` <ak-form-group>
|
return html` <ak-form-group>
|
||||||
|
@ -74,7 +84,7 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value="${first(this.instance?.authorizationUrl, "")}"
|
value="${first(this.instance?.authorizationUrl, this.providerType.authorizationUrl)}"
|
||||||
class="pf-c-form-control"
|
class="pf-c-form-control"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
@ -89,7 +99,7 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value="${first(this.instance?.accessTokenUrl, "")}"
|
value="${first(this.instance?.accessTokenUrl, this.providerType.accessTokenUrl)}"
|
||||||
class="pf-c-form-control"
|
class="pf-c-form-control"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
@ -104,7 +114,7 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value="${first(this.instance?.profileUrl, "")}"
|
value="${first(this.instance?.profileUrl, this.providerType.profileUrl)}"
|
||||||
class="pf-c-form-control"
|
class="pf-c-form-control"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
@ -112,7 +122,7 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
|
||||||
${t`URL used by authentik to get user information.`}
|
${t`URL used by authentik to get user information.`}
|
||||||
</p>
|
</p>
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
${this.showRequestTokenURL
|
${this.providerType.requestTokenUrl
|
||||||
? html`<ak-form-element-horizontal
|
? html`<ak-form-element-horizontal
|
||||||
label=${t`Request token URL`}
|
label=${t`Request token URL`}
|
||||||
name="requestTokenUrl"
|
name="requestTokenUrl"
|
||||||
|
@ -226,54 +236,6 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
|
||||||
>
|
>
|
||||||
<input type="text" value="" class="pf-c-form-control" required />
|
<input type="text" value="" class="pf-c-form-control" required />
|
||||||
</ak-form-element-horizontal>
|
</ak-form-element-horizontal>
|
||||||
<ak-form-element-horizontal label=${t`Provider type`} name="providerType">
|
|
||||||
<select
|
|
||||||
class="pf-c-form-control"
|
|
||||||
@change=${(ev: Event) => {
|
|
||||||
const el = ev.target as HTMLSelectElement;
|
|
||||||
const selected = el.selectedOptions[0];
|
|
||||||
this.showUrlOptions = "data-urls-custom" in selected.attributes;
|
|
||||||
this.showRequestTokenURL =
|
|
||||||
"data-request-token" in selected.attributes;
|
|
||||||
if (!this.instance) {
|
|
||||||
this.instance = {} as OAuthSource;
|
|
||||||
}
|
|
||||||
this.instance.providerType = selected.value;
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
${until(
|
|
||||||
new SourcesApi(DEFAULT_CONFIG)
|
|
||||||
.sourcesOauthSourceTypesList()
|
|
||||||
.then((types) => {
|
|
||||||
return types.map((type) => {
|
|
||||||
let selected =
|
|
||||||
this.instance?.providerType === type.slug;
|
|
||||||
const modelSlug = this.modelName
|
|
||||||
?.replace("oauthsource", "")
|
|
||||||
.replace("-", "");
|
|
||||||
const typeSlug = type.slug.replace("-", "");
|
|
||||||
if (!this.instance?.pk) {
|
|
||||||
if (modelSlug === typeSlug) {
|
|
||||||
selected = true;
|
|
||||||
this.showUrlOptions = type.urlsCustomizable;
|
|
||||||
this.showRequestTokenURL =
|
|
||||||
type.requestTokenUrl !== null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return html`<option
|
|
||||||
?data-urls-custom=${type.urlsCustomizable}
|
|
||||||
?data-request-token=${type.requestTokenUrl}
|
|
||||||
value=${type.slug}
|
|
||||||
?selected=${selected}
|
|
||||||
>
|
|
||||||
${type.name}
|
|
||||||
</option>`;
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
html`<option>${t`Loading...`}</option>`,
|
|
||||||
)}
|
|
||||||
</select>
|
|
||||||
</ak-form-element-horizontal>
|
|
||||||
</div>
|
</div>
|
||||||
</ak-form-group>
|
</ak-form-group>
|
||||||
${this.renderUrlOptions()}
|
${this.renderUrlOptions()}
|
||||||
|
|
Reference in New Issue