api: add set_*_url method for Application and Flow to set icon/background to URL

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-05-20 17:01:08 +02:00
parent 92106ca4bf
commit be5a6c0310
5 changed files with 226 additions and 37 deletions

View File

@ -13,7 +13,7 @@ from drf_spectacular.utils import (
inline_serializer, inline_serializer,
) )
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import FileField, SerializerMethodField from rest_framework.fields import CharField, FileField, SerializerMethodField
from rest_framework.parsers import MultiPartParser from rest_framework.parsers import MultiPartParser
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
@ -44,6 +44,16 @@ class ApplicationSerializer(ModelSerializer):
launch_url = SerializerMethodField() launch_url = SerializerMethodField()
provider_obj = ProviderSerializer(source="get_provider", required=False) provider_obj = ProviderSerializer(source="get_provider", required=False)
meta_icon = SerializerMethodField()
def get_meta_icon(self, instance: Application) -> Optional[str]:
"""When meta_icon was set to a URL, return the name as-is"""
if not instance.meta_icon:
return None
if instance.meta_icon.name.startswith("http"):
return instance.meta_icon.name
return instance.meta_icon.url
def get_launch_url(self, instance: Application) -> Optional[str]: def get_launch_url(self, instance: Application) -> Optional[str]:
"""Get generated launch URL""" """Get generated launch URL"""
return instance.get_launch_url() return instance.get_launch_url()
@ -190,6 +200,31 @@ class ApplicationViewSet(ModelViewSet):
app.save() app.save()
return Response({}) return Response({})
@permission_required("authentik_core.change_application")
@extend_schema(
request=inline_serializer("SetIconURL", fields={"url": CharField()}),
responses={
200: OpenApiResponse(description="Success"),
400: OpenApiResponse(description="Bad request"),
},
)
@action(
detail=True,
pagination_class=None,
filter_backends=[],
methods=["POST"],
)
# pylint: disable=unused-argument
def set_icon_url(self, request: Request, slug: str):
"""Set application icon (as URL)"""
app: Application = self.get_object()
url = request.data.get("url", None)
if not url:
return HttpResponseBadRequest()
app.meta_icon = url
app.save()
return Response({})
@permission_required( @permission_required(
"authentik_core.view_application", ["authentik_events.view_event"] "authentik_core.view_application", ["authentik_events.view_event"]
) )

View File

@ -1,5 +1,6 @@
"""Flow API Views""" """Flow API Views"""
from dataclasses import dataclass from dataclasses import dataclass
from typing import Optional
from django.core.cache import cache from django.core.cache import cache
from django.db.models import Model from django.db.models import Model
@ -42,6 +43,16 @@ class FlowSerializer(ModelSerializer):
cache_count = SerializerMethodField() cache_count = SerializerMethodField()
background = SerializerMethodField()
def get_background(self, instance: Flow) -> Optional[str]:
"""When background was set to a URL, return the name as-is"""
if not instance.background:
return None
if instance.background.name.startswith("http"):
return instance.background.name
return instance.background.url
def get_cache_count(self, flow: Flow) -> int: def get_cache_count(self, flow: Flow) -> int:
"""Get count of cached flows""" """Get count of cached flows"""
return len(cache.keys(f"{cache_key(flow)}*")) return len(cache.keys(f"{cache_key(flow)}*"))
@ -284,12 +295,37 @@ class FlowViewSet(ModelViewSet):
# pylint: disable=unused-argument # pylint: disable=unused-argument
def set_background(self, request: Request, slug: str): def set_background(self, request: Request, slug: str):
"""Set Flow background""" """Set Flow background"""
app: Flow = self.get_object() flow: Flow = self.get_object()
icon = request.FILES.get("file", None) icon = request.FILES.get("file", None)
if not icon: if not icon:
return HttpResponseBadRequest() return HttpResponseBadRequest()
app.background = icon flow.background = icon
app.save() flow.save()
return Response({})
@permission_required("authentik_core.change_application")
@extend_schema(
request=inline_serializer("SetIconURL", fields={"url": CharField()}),
responses={
200: OpenApiResponse(description="Success"),
400: OpenApiResponse(description="Bad request"),
},
)
@action(
detail=True,
pagination_class=None,
filter_backends=[],
methods=["POST"],
)
# pylint: disable=unused-argument
def set_background_url(self, request: Request, slug: str):
"""Set Flow background (as URL)"""
flow: Flow = self.get_object()
url = request.data.get("url", None)
if not url:
return HttpResponseBadRequest()
flow.background = url
flow.save()
return Response({}) return Response({})
@extend_schema( @extend_schema(

View File

@ -1292,6 +1292,41 @@ paths:
description: Bad request description: Bad request
'403': '403':
$ref: '#/components/schemas/GenericError' $ref: '#/components/schemas/GenericError'
/api/v2beta/core/applications/{slug}/set_icon_url/:
post:
operationId: core_applications_set_icon_url_create
description: Set application icon (as URL)
parameters:
- in: path
name: slug
schema:
type: string
description: Internal application name, used in URLs.
required: true
tags:
- core
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SetIconURLRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/SetIconURLRequest'
multipart/form-data:
schema:
$ref: '#/components/schemas/SetIconURLRequest'
required: true
security:
- authentik: []
- cookieAuth: []
responses:
'200':
description: Success
'400':
description: Bad request
'403':
$ref: '#/components/schemas/GenericError'
/api/v2beta/core/groups/: /api/v2beta/core/groups/:
get: get:
operationId: core_groups_list operationId: core_groups_list
@ -3972,6 +4007,41 @@ paths:
description: Bad request description: Bad request
'403': '403':
$ref: '#/components/schemas/GenericError' $ref: '#/components/schemas/GenericError'
/api/v2beta/flows/instances/{slug}/set_background_url/:
post:
operationId: flows_instances_set_background_url_create
description: Set Flow background (as URL)
parameters:
- in: path
name: slug
schema:
type: string
description: Visible in the URL.
required: true
tags:
- flows
requestBody:
content:
application/json:
schema:
$ref: '#/components/schemas/SetIconURLRequest'
application/x-www-form-urlencoded:
schema:
$ref: '#/components/schemas/SetIconURLRequest'
multipart/form-data:
schema:
$ref: '#/components/schemas/SetIconURLRequest'
required: true
security:
- authentik: []
- cookieAuth: []
responses:
'200':
description: Success
'400':
description: Bad request
'403':
$ref: '#/components/schemas/GenericError'
/api/v2beta/flows/instances/cache_clear/: /api/v2beta/flows/instances/cache_clear/:
post: post:
operationId: flows_instances_cache_clear_create operationId: flows_instances_cache_clear_create
@ -23142,6 +23212,13 @@ components:
format: binary format: binary
required: required:
- file - file
SetIconURLRequest:
type: object
properties:
url:
type: string
required:
- url
SeverityEnum: SeverityEnum:
enum: enum:
- notice - notice

View File

@ -1,8 +1,8 @@
import { CoreApi, Application, ProvidersApi, Provider, PolicyEngineMode } from "authentik-api"; import { CoreApi, Application, ProvidersApi, Provider, PolicyEngineMode, CapabilitiesEnum } from "authentik-api";
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { CSSResult, customElement, property } from "lit-element"; import { CSSResult, customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html"; import { html, TemplateResult } from "lit-html";
import { DEFAULT_CONFIG } from "../../api/Config"; import { config, DEFAULT_CONFIG } from "../../api/Config";
import { until } from "lit-html/directives/until"; import { until } from "lit-html/directives/until";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
import "../../elements/buttons/Dropdown"; import "../../elements/buttons/Dropdown";
@ -13,6 +13,7 @@ import "../../elements/forms/HorizontalFormElement";
import "../../elements/forms/FormGroup"; import "../../elements/forms/FormGroup";
import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css"; import PFDropdown from "@patternfly/patternfly/components/Dropdown/dropdown.css";
import { ModelForm } from "../../elements/forms/ModelForm"; import { ModelForm } from "../../elements/forms/ModelForm";
import { first } from "../../utils";
@customElement("ak-application-form") @customElement("ak-application-form")
export class ApplicationForm extends ModelForm<Application, string> { export class ApplicationForm extends ModelForm<Application, string> {
@ -50,16 +51,28 @@ export class ApplicationForm extends ModelForm<Application, string> {
applicationRequest: data applicationRequest: data
}); });
} }
const icon = this.getFormFile(); return config().then((c) => {
if (icon) { if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
return writeOp.then(app => { const icon = this.getFormFile();
return new CoreApi(DEFAULT_CONFIG).coreApplicationsSetIconCreate({ if (icon) {
slug: app.slug, return writeOp.then(app => {
file: icon return new CoreApi(DEFAULT_CONFIG).coreApplicationsSetIconCreate({
slug: app.slug,
file: icon
});
});
}
} else {
return writeOp.then(app => {
return new CoreApi(DEFAULT_CONFIG).coreApplicationsSetIconUrlCreate({
slug: app.slug,
setIconURLRequest: {
url: data.metaIcon || "",
}
});
}); });
}); }
} });
return writeOp;
}; };
groupProviders(providers: Provider[]): TemplateResult { groupProviders(providers: Provider[]): TemplateResult {
@ -164,11 +177,18 @@ export class ApplicationForm extends ModelForm<Application, string> {
<input type="text" value="${ifDefined(this.instance?.metaLaunchUrl)}" class="pf-c-form-control"> <input type="text" value="${ifDefined(this.instance?.metaLaunchUrl)}" class="pf-c-form-control">
<p class="pf-c-form__helper-text">${t`If left empty, authentik will try to extract the launch URL based on the selected provider.`}</p> <p class="pf-c-form__helper-text">${t`If left empty, authentik will try to extract the launch URL based on the selected provider.`}</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal ${until(config().then((c) => {
label=${t`Icon`} let type = "text";
name="metaIcon"> if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
<input type="file" value="${ifDefined(this.instance?.metaIcon)}" class="pf-c-form-control"> type = "file";
</ak-form-element-horizontal> }
return html`<ak-form-element-horizontal
label=${t`Icon`}
name="metaIcon">
<!-- @ts-ignore -->
<input type=${type} value="${first(this.instance?.metaIcon, "")}" class="pf-c-form-control">
</ak-form-element-horizontal>`;
}))}
<ak-form-element-horizontal <ak-form-element-horizontal
label=${t`Description`} label=${t`Description`}
name="metaDescription"> name="metaDescription">

View File

@ -1,11 +1,13 @@
import { Flow, FlowDesignationEnum, PolicyEngineMode, FlowsApi } from "authentik-api"; import { Flow, FlowDesignationEnum, PolicyEngineMode, FlowsApi, CapabilitiesEnum } from "authentik-api";
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { customElement } from "lit-element"; import { customElement } from "lit-element";
import { html, TemplateResult } from "lit-html"; import { html, TemplateResult } from "lit-html";
import { DEFAULT_CONFIG } from "../../api/Config"; import { config, DEFAULT_CONFIG } from "../../api/Config";
import { ifDefined } from "lit-html/directives/if-defined"; import { ifDefined } from "lit-html/directives/if-defined";
import "../../elements/forms/HorizontalFormElement"; import "../../elements/forms/HorizontalFormElement";
import { ModelForm } from "../../elements/forms/ModelForm"; import { ModelForm } from "../../elements/forms/ModelForm";
import { until } from "lit-html/directives/until";
import { first } from "../../utils";
@customElement("ak-flow-form") @customElement("ak-flow-form")
export class FlowForm extends ModelForm<Flow, string> { export class FlowForm extends ModelForm<Flow, string> {
@ -36,16 +38,28 @@ export class FlowForm extends ModelForm<Flow, string> {
flowRequest: data flowRequest: data
}); });
} }
const background = this.getFormFile(); return config().then((c) => {
if (background) { if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
return writeOp.then(flow => { const icon = this.getFormFile();
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesSetBackgroundCreate({ if (icon) {
slug: flow.slug, return writeOp.then(app => {
file: background return new FlowsApi(DEFAULT_CONFIG).flowsInstancesSetBackgroundCreate({
slug: app.slug,
file: icon
});
});
}
} else {
return writeOp.then(app => {
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesSetBackgroundUrlCreate({
slug: app.slug,
setIconURLRequest: {
url: data.background || "",
}
});
}); });
}); }
} });
return writeOp;
}; };
renderDesignations(): TemplateResult { renderDesignations(): TemplateResult {
@ -119,12 +133,19 @@ export class FlowForm extends ModelForm<Flow, string> {
</select> </select>
<p class="pf-c-form__helper-text">${t`Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik.`}</p> <p class="pf-c-form__helper-text">${t`Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik.`}</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal ${until(config().then((c) => {
label=${t`Background`} let type = "text";
name="background"> if (c.capabilities.includes(CapabilitiesEnum.CanSaveMedia)) {
<input type="file" value="${ifDefined(this.instance?.background)}" class="pf-c-form-control"> type = "file";
<p class="pf-c-form__helper-text">${t`Background shown during execution.`}</p> }
</ak-form-element-horizontal> return html`<ak-form-element-horizontal
label=${t`Background`}
name="background">
<!-- @ts-ignore -->
<input type=${type} value="${first(this.instance?.background, "/static/dist/assets/images/flow_background.jpg")}" class="pf-c-form-control">
<p class="pf-c-form__helper-text">${t`Background shown during execution.`}</p>
</ak-form-element-horizontal>`;
}))}
</form>`; </form>`;
} }