mirror of
https://github.com/goauthentik/authentik.git
synced 2026-06-17 19:09:11 +03:00
web/admin: prevent file upload attempt when backend not managed (#18646)
* web/admin: prevent file upload attempt when backend not managed Signed-off-by: Jens Langhammer <jens@goauthentik.io> * wip * fixup * rework Signed-off-by: Jens Langhammer <jens@goauthentik.io> * format Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add check for reports Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix delete table for data exports missing details Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io> Co-authored-by: Dominic R <dominic@sdko.org>
This commit is contained in:
@@ -31,6 +31,7 @@ class Capabilities(models.TextChoices):
|
||||
"""Define capabilities which influence which APIs can/should be used"""
|
||||
|
||||
CAN_SAVE_MEDIA = "can_save_media"
|
||||
CAN_SAVE_REPORTS = "can_save_reports"
|
||||
CAN_GEO_IP = "can_geo_ip"
|
||||
CAN_ASN = "can_asn"
|
||||
CAN_IMPERSONATE = "can_impersonate"
|
||||
@@ -70,6 +71,8 @@ class ConfigView(APIView):
|
||||
caps = []
|
||||
if get_file_manager(FileUsage.MEDIA).manageable:
|
||||
caps.append(Capabilities.CAN_SAVE_MEDIA)
|
||||
if get_file_manager(FileUsage.REPORTS).manageable:
|
||||
caps.append(Capabilities.CAN_SAVE_REPORTS)
|
||||
for processor in get_context_processors():
|
||||
if cap := processor.capability():
|
||||
caps.append(cap)
|
||||
|
||||
@@ -34792,6 +34792,7 @@ components:
|
||||
CapabilitiesEnum:
|
||||
enum:
|
||||
- can_save_media
|
||||
- can_save_reports
|
||||
- can_geo_ip
|
||||
- can_asn
|
||||
- can_impersonate
|
||||
|
||||
@@ -56,9 +56,16 @@ export class DataExportListPage extends TablePage<DataExport> {
|
||||
|
||||
renderToolbarSelected(): TemplateResult {
|
||||
const disabled = this.selectedElements.length < 1;
|
||||
return html` <ak-forms-delete-bulk
|
||||
return html`<ak-forms-delete-bulk
|
||||
objectLabel=${msg("Data export(s)")}
|
||||
.objects=${this.selectedElements}
|
||||
.metadata=${(item: DataExport) => {
|
||||
return [
|
||||
{ key: msg("Data type"), value: item.contentType.verboseNamePlural },
|
||||
{ key: msg("Requested by"), value: item.requestedBy.username },
|
||||
{ key: msg("Creation date"), value: Timestamp(item.requestedOn) },
|
||||
];
|
||||
}}
|
||||
.delete=${(item: DataExport) => {
|
||||
return new ReportsApi(DEFAULT_CONFIG).reportsExportsDestroy({
|
||||
id: item.id,
|
||||
|
||||
@@ -3,17 +3,20 @@ import "#elements/buttons/SpinnerButton/index";
|
||||
import "#elements/forms/DeleteBulkForm";
|
||||
import "#elements/forms/ModalForm";
|
||||
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
import "#elements/EmptyState";
|
||||
|
||||
import { DEFAULT_CONFIG } from "#common/api/config";
|
||||
import { docLink } from "#common/global";
|
||||
|
||||
import { WithCapabilitiesConfig } from "#elements/mixins/capabilities";
|
||||
import { PaginatedResponse, TableColumn } from "#elements/table/Table";
|
||||
import { TablePage } from "#elements/table/TablePage";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
import { AdminApi, AdminFileListUsageEnum } from "@goauthentik/api";
|
||||
import { AdminApi, AdminFileListUsageEnum, CapabilitiesEnum } from "@goauthentik/api";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { html, TemplateResult } from "lit";
|
||||
import { html, nothing, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
export interface FileItem {
|
||||
@@ -25,7 +28,7 @@ export interface FileItem {
|
||||
export type FileListOrderKey = "name" | "mimeType";
|
||||
|
||||
@customElement("ak-files-list")
|
||||
export class FileListPage extends TablePage<FileItem> {
|
||||
export class FileListPage extends WithCapabilitiesConfig(TablePage<FileItem>) {
|
||||
public override checkbox = true;
|
||||
public override clearOnRefresh = true;
|
||||
|
||||
@@ -67,7 +70,10 @@ export class FileListPage extends TablePage<FileItem> {
|
||||
[msg("Actions"), null, msg("Row Actions")],
|
||||
];
|
||||
|
||||
renderToolbarSelected(): TemplateResult {
|
||||
renderToolbarSelected() {
|
||||
if (!this.can(CapabilitiesEnum.CanSaveMedia)) {
|
||||
return nothing;
|
||||
}
|
||||
const disabled = !this.selectedElements.length;
|
||||
const count = this.selectedElements.length;
|
||||
return html`<ak-forms-delete-bulk
|
||||
@@ -116,7 +122,32 @@ export class FileListPage extends TablePage<FileItem> {
|
||||
];
|
||||
}
|
||||
|
||||
protected renderObjectCreate(): TemplateResult {
|
||||
protected renderEmpty(inner?: TemplateResult) {
|
||||
if (this.can(CapabilitiesEnum.CanSaveMedia)) {
|
||||
return super.renderEmpty(inner);
|
||||
}
|
||||
return super.renderEmpty(
|
||||
html`<ak-empty-state icon=${this.pageIcon}
|
||||
><span>${msg("Configured file backend does not support file management.")}</span>
|
||||
<div slot="body">
|
||||
${msg("Please ensure the data folder is mounted or S3 storage is configured.")}
|
||||
</div>
|
||||
<div slot="primary">
|
||||
<a
|
||||
target="_blank"
|
||||
class="pf-c-button pf-m-secondary"
|
||||
href=${docLink("/install-config/configuration/#storage-settings")}
|
||||
>${msg("Learn more")}</a
|
||||
>
|
||||
</div>
|
||||
</ak-empty-state>`,
|
||||
);
|
||||
}
|
||||
|
||||
protected renderObjectCreate() {
|
||||
if (!this.can(CapabilitiesEnum.CanSaveMedia)) {
|
||||
return nothing;
|
||||
}
|
||||
return html`
|
||||
<ak-forms-modal>
|
||||
<span slot="submit">${msg("Upload")}</span>
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import "#elements/forms/ConfirmationForm";
|
||||
import "@patternfly/elements/pf-tooltip/pf-tooltip.js";
|
||||
|
||||
import { parseAPIResponseError } from "#common/errors/network";
|
||||
import { docLink } from "#common/global";
|
||||
|
||||
import { AKElement } from "#elements/Base";
|
||||
import { showAPIErrorMessage } from "#elements/messages/MessageContainer";
|
||||
import { WithBrandConfig } from "#elements/mixins/branding";
|
||||
import { CapabilitiesEnum, WithCapabilitiesConfig } from "#elements/mixins/capabilities";
|
||||
import { WithLicenseSummary } from "#elements/mixins/license";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
@@ -19,7 +22,9 @@ import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
||||
|
||||
@customElement("ak-reports-export-button")
|
||||
export class ExportButton extends WithBrandConfig(WithLicenseSummary(AKElement)) {
|
||||
export class ExportButton extends WithCapabilitiesConfig(
|
||||
WithBrandConfig(WithLicenseSummary(AKElement)),
|
||||
) {
|
||||
static styles: CSSResult[] = [PFButton, PFContent, PFDescriptionList];
|
||||
|
||||
@property({ attribute: false })
|
||||
@@ -61,6 +66,37 @@ export class ExportButton extends WithBrandConfig(WithLicenseSummary(AKElement))
|
||||
});
|
||||
};
|
||||
|
||||
renderBody() {
|
||||
if (!this.can(CapabilitiesEnum.CanSaveReports)) {
|
||||
return html`<p>
|
||||
${msg(
|
||||
"Data exports are not available as storage for reports is not configured.",
|
||||
)}
|
||||
</p>
|
||||
<a href=${docLink("install-config/configuration/#storage-settings")}
|
||||
>${msg("Learn more")}</a
|
||||
>`;
|
||||
}
|
||||
return html`<p>
|
||||
${msg(
|
||||
str`${this.brand.brandingTitle} will collect all objects with the specified parameters:`,
|
||||
)}
|
||||
</p>
|
||||
<br />
|
||||
${renderDescriptionList(
|
||||
Object.keys(this.params)
|
||||
.filter((key) => {
|
||||
if (key === "page" || key === "pageSize") return false;
|
||||
|
||||
return !!this.params[key];
|
||||
})
|
||||
.map((key): DescriptionPair => {
|
||||
return [key, html`<pre>${this.params[key]}</pre>`];
|
||||
}),
|
||||
{ horizontal: true, compact: true },
|
||||
)}`;
|
||||
}
|
||||
|
||||
render(): SlottedTemplateResult {
|
||||
if (!this.hasEnterpriseLicense) {
|
||||
return nothing;
|
||||
@@ -76,28 +112,10 @@ export class ExportButton extends WithBrandConfig(WithLicenseSummary(AKElement))
|
||||
}}
|
||||
action=${msg("Start export")}
|
||||
actionLevel="pf-m-primary"
|
||||
?non-submittable=${!this.can(CapabilitiesEnum.CanSaveReports)}
|
||||
>
|
||||
<span slot="header">${msg("Export data")}</span>
|
||||
<div slot="body">
|
||||
<p>
|
||||
${msg(
|
||||
str`${this.brand.brandingTitle} will collect all objects with the specified parameters:`,
|
||||
)}
|
||||
</p>
|
||||
<br />
|
||||
${renderDescriptionList(
|
||||
Object.keys(this.params)
|
||||
.filter((key) => {
|
||||
if (key === "page" || key === "pageSize") return false;
|
||||
|
||||
return !!this.params[key];
|
||||
})
|
||||
.map((key): DescriptionPair => {
|
||||
return [key, html`<pre>${this.params[key]}</pre>`];
|
||||
}),
|
||||
{ horizontal: true, compact: true },
|
||||
)}
|
||||
</div>
|
||||
<div slot="body">${this.renderBody()}</div>
|
||||
<button slot="trigger" class="pf-c-button pf-m-secondary" type="button">
|
||||
${msg("Export")}
|
||||
</button>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { ModalButton } from "#elements/buttons/ModalButton";
|
||||
import { showMessage } from "#elements/messages/MessageContainer";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { html, TemplateResult } from "lit";
|
||||
import { html, nothing, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
@customElement("ak-forms-confirm")
|
||||
@@ -18,6 +18,9 @@ export class ConfirmationForm extends ModalButton {
|
||||
@property()
|
||||
errorMessage!: string;
|
||||
|
||||
@property({ type: Boolean, attribute: "non-submittable" })
|
||||
nonSubmittable = false;
|
||||
|
||||
@property()
|
||||
action!: string;
|
||||
|
||||
@@ -75,14 +78,16 @@ export class ConfirmationForm extends ModalButton {
|
||||
</form>
|
||||
</section>
|
||||
<footer class="pf-c-modal-box__footer">
|
||||
<ak-spinner-button
|
||||
.callAction=${() => {
|
||||
return this.confirm();
|
||||
}}
|
||||
class=${this.actionLevel}
|
||||
>
|
||||
${this.action} </ak-spinner-button
|
||||
>
|
||||
${this.nonSubmittable
|
||||
? nothing
|
||||
: html`<ak-spinner-button
|
||||
.callAction=${() => {
|
||||
return this.confirm();
|
||||
}}
|
||||
class=${this.actionLevel}
|
||||
>
|
||||
${this.action} </ak-spinner-button
|
||||
> `}
|
||||
<ak-spinner-button
|
||||
.callAction=${async () => {
|
||||
this.open = false;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import "#elements/EmptyState";
|
||||
|
||||
import { updateURLParams } from "#elements/router/RouteMatch";
|
||||
import { Table } from "#elements/table/Table";
|
||||
import { SlottedTemplateResult } from "#elements/types";
|
||||
|
||||
Reference in New Issue
Block a user