Merge branch 'main' into web/add-htmltagmaps-to-activate-lit-analyzer

* main:
  core: bump setuptools from 69.5.1 to 70.0.0 (#10503)
  web: replace multi-select with dual-select for all propertyMapping invocations (#9359)
  web: enable custom-element-manifest and DOM/JS integration checking. (#10177)
This commit is contained in:
Ken Sternberg
2024-07-15 10:29:21 -07:00
19 changed files with 394 additions and 439 deletions

11
poetry.lock generated
View File

@ -4402,19 +4402,18 @@ test = ["pytest"]
[[package]] [[package]]
name = "setuptools" name = "setuptools"
version = "69.5.1" version = "70.0.0"
description = "Easily download, build, install, upgrade, and uninstall Python packages" description = "Easily download, build, install, upgrade, and uninstall Python packages"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.8"
files = [ files = [
{file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, {file = "setuptools-70.0.0-py3-none-any.whl", hash = "sha256:54faa7f2e8d2d11bcd2c07bed282eef1046b5c080d1c32add737d7b5817b1ad4"},
{file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, {file = "setuptools-70.0.0.tar.gz", hash = "sha256:f211a66637b8fa059bb28183da127d4e86396c991a942b028c6650d4319c3fd0"},
] ]
[package.extras] [package.extras]
docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"]
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
[[package]] [[package]]
name = "six" name = "six"

View File

@ -23,7 +23,7 @@
"quotes": ["error", "double", { "avoidEscape": true }], "quotes": ["error", "double", { "avoidEscape": true }],
"semi": ["error", "always"], "semi": ["error", "always"],
"@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/ban-ts-comment": "off",
"sonarjs/cognitive-complexity": ["error", 9], "sonarjs/cognitive-complexity": ["warn", 9],
"sonarjs/no-duplicate-string": "off", "sonarjs/no-duplicate-string": "off",
"sonarjs/no-nested-template-literals": "off" "sonarjs/no-nested-template-literals": "off"
} }

View File

@ -35,7 +35,7 @@ const eslintConfig = {
"quotes": ["error", "double", { avoidEscape: true }], "quotes": ["error", "double", { avoidEscape: true }],
"semi": ["error", "always"], "semi": ["error", "always"],
"@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/ban-ts-comment": "off",
"sonarjs/cognitive-complexity": ["error", 9], "sonarjs/cognitive-complexity": ["warn", 9],
"sonarjs/no-duplicate-string": "off", "sonarjs/no-duplicate-string": "off",
"sonarjs/no-nested-template-literals": "off", "sonarjs/no-nested-template-literals": "off",
}, },
@ -72,5 +72,6 @@ const formatter = await eslint.loadFormatter("stylish");
const resultText = formatter.format(results); const resultText = formatter.format(results);
const errors = results.reduce((acc, result) => acc + result.errorCount, 0); const errors = results.reduce((acc, result) => acc + result.errorCount, 0);
// eslint-disable-next-line no-console
console.log(resultText); console.log(resultText);
process.exit(errors > 1 ? 1 : 0); process.exit(errors > 1 ? 1 : 0);

View File

@ -3,11 +3,14 @@ import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search"; import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import { import {
clientTypeOptions, clientTypeOptions,
defaultScopes,
issuerModeOptions, issuerModeOptions,
redirectUriHelp, redirectUriHelp,
subjectModeOptions, subjectModeOptions,
} from "@goauthentik/admin/providers/oauth2/OAuth2ProviderForm"; } from "@goauthentik/admin/providers/oauth2/OAuth2ProviderForm";
import {
makeOAuth2PropertyMappingsSelector,
oauth2PropertyMappingsProvider,
} from "@goauthentik/admin/providers/oauth2/Oauth2PropertyMappings.js";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils"; import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-number-input"; import "@goauthentik/components/ak-number-input";
@ -15,6 +18,7 @@ import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-switch-input"; import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input"; import "@goauthentik/components/ak-text-input";
import "@goauthentik/components/ak-textarea-input"; import "@goauthentik/components/ak-textarea-input";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
@ -23,17 +27,8 @@ import { customElement, state } from "@lit/reactive-element/decorators.js";
import { html, nothing } from "lit"; import { html, nothing } from "lit";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { import { ClientTypeEnum, FlowsInstancesListDesignationEnum, SourcesApi } from "@goauthentik/api";
ClientTypeEnum, import { type OAuth2Provider, type PaginatedOAuthSourceList } from "@goauthentik/api";
FlowsInstancesListDesignationEnum,
PropertymappingsApi,
SourcesApi,
} from "@goauthentik/api";
import {
type OAuth2Provider,
type PaginatedOAuthSourceList,
type PaginatedScopeMappingList,
} from "@goauthentik/api";
import BaseProviderPanel from "../BaseProviderPanel"; import BaseProviderPanel from "../BaseProviderPanel";
@ -42,22 +37,11 @@ export class ApplicationWizardAuthenticationByOauth extends BaseProviderPanel {
@state() @state()
showClientSecret = true; showClientSecret = true;
@state()
propertyMappings?: PaginatedScopeMappingList;
@state() @state()
oauthSources?: PaginatedOAuthSourceList; oauthSources?: PaginatedOAuthSourceList;
constructor() { constructor() {
super(); super();
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsScopeList({
ordering: "scope_name",
})
.then((propertyMappings: PaginatedScopeMappingList) => {
this.propertyMappings = propertyMappings;
});
new SourcesApi(DEFAULT_CONFIG) new SourcesApi(DEFAULT_CONFIG)
.sourcesOauthList({ .sourcesOauthList({
ordering: "name", ordering: "name",
@ -222,36 +206,19 @@ export class ApplicationWizardAuthenticationByOauth extends BaseProviderPanel {
name="propertyMappings" name="propertyMappings"
.errorMessages=${errors?.propertyMappings ?? []} .errorMessages=${errors?.propertyMappings ?? []}
> >
<select class="pf-c-form-control" multiple> <ak-dual-select-dynamic-selected
${this.propertyMappings?.results.map((scope) => { .provider=${oauth2PropertyMappingsProvider}
let selected = false; .selector=${makeOAuth2PropertyMappingsSelector(
if (!provider?.propertyMappings) { provider?.propertyMappings,
selected = scope.managed )}
? defaultScopes.includes(scope.managed) available-label=${msg("Available Scopes")}
: false; selected-label=${msg("Selected Scopes")}
} else { ></ak-dual-select-dynamic-selected>
selected = Array.from(provider?.propertyMappings).some(
(su) => {
return su == scope.pk;
},
);
}
return html`<option
value=${ifDefined(scope.pk)}
?selected=${selected}
>
${scope.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
${msg( ${msg(
"Select which scopes can be used by the client. The client still has to specify the scope to access the data.", "Select which scopes can be used by the client. The client still has to specify the scope to access the data.",
)} )}
</p> </p>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-radio-input <ak-radio-input

View File

@ -1,10 +1,15 @@
import "@goauthentik/admin/applications/wizard/ak-wizard-title"; import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import {
makeProxyPropertyMappingsSelector,
proxyPropertyMappingsProvider,
} from "@goauthentik/admin/providers/proxy/ProxyProviderPropertyMappings.js";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-switch-input"; import "@goauthentik/components/ak-switch-input";
import "@goauthentik/components/ak-text-input"; import "@goauthentik/components/ak-text-input";
import "@goauthentik/components/ak-textarea-input"; import "@goauthentik/components/ak-textarea-input";
import "@goauthentik/components/ak-toggle-group"; import "@goauthentik/components/ak-toggle-group";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
@ -16,7 +21,6 @@ import {
FlowsInstancesListDesignationEnum, FlowsInstancesListDesignationEnum,
PaginatedOAuthSourceList, PaginatedOAuthSourceList,
PaginatedScopeMappingList, PaginatedScopeMappingList,
PropertymappingsApi,
ProxyMode, ProxyMode,
ProxyProvider, ProxyProvider,
SourcesApi, SourcesApi,
@ -29,12 +33,6 @@ type MaybeTemplateResult = TemplateResult | typeof nothing;
export class AkTypeProxyApplicationWizardPage extends BaseProviderPanel { export class AkTypeProxyApplicationWizardPage extends BaseProviderPanel {
constructor() { constructor() {
super(); super();
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsScopeList({ ordering: "scope_name" })
.then((propertyMappings: PaginatedScopeMappingList) => {
this.propertyMappings = propertyMappings;
});
new SourcesApi(DEFAULT_CONFIG) new SourcesApi(DEFAULT_CONFIG)
.sourcesOauthList({ .sourcesOauthList({
ordering: "name", ordering: "name",
@ -88,29 +86,8 @@ export class AkTypeProxyApplicationWizardPage extends BaseProviderPanel {
</ak-text-input>`; </ak-text-input>`;
} }
scopeMappingConfiguration(provider?: ProxyProvider) {
const propertyMappings = this.propertyMappings?.results ?? [];
const defaultScopes = () =>
propertyMappings
.filter((scope) => !(scope?.managed ?? "").startsWith("goauthentik.io/providers"))
.map((pm) => pm.pk);
const configuredScopes = (providerMappings: string[]) =>
propertyMappings.map((scope) => scope.pk).filter((pk) => providerMappings.includes(pk));
const scopeValues = provider?.propertyMappings
? configuredScopes(provider?.propertyMappings ?? [])
: defaultScopes();
const scopePairs = propertyMappings.map((scope) => [scope.pk, scope.name]);
return { scopePairs, scopeValues };
}
render() { render() {
const errors = this.wizard.errors.provider; const errors = this.wizard.errors.provider;
const { scopePairs, scopeValues } = this.scopeMappingConfiguration(this.instance);
return html` <ak-wizard-title>${msg("Configure Proxy Provider")}</ak-wizard-title> return html` <ak-wizard-title>${msg("Configure Proxy Provider")}</ak-wizard-title>
<form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}> <form class="pf-c-form pf-m-horizontal" @input=${this.handleChange}>
@ -179,24 +156,22 @@ export class AkTypeProxyApplicationWizardPage extends BaseProviderPanel {
certificate=${ifDefined(this.instance?.certificate ?? undefined)} certificate=${ifDefined(this.instance?.certificate ?? undefined)}
></ak-crypto-certificate-search> ></ak-crypto-certificate-search>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal
<ak-multi-select label=${msg("Additional scopes")}
label=${msg("AdditionalScopes")}
name="propertyMappings" name="propertyMappings"
.options=${scopePairs} >
.values=${scopeValues} <ak-dual-select-dynamic-selected
.errorMessages=${errors?.propertyMappings ?? []} .provider=${proxyPropertyMappingsProvider}
.richhelp=${html` .selector=${makeProxyPropertyMappingsSelector(
<p class="pf-c-form__helper-text"> this.instance?.propertyMappings,
${msg(
"Additional scope mappings, which are passed to the proxy.",
)} )}
</p> available-label="${msg("Available Scopes")}"
selected-label="${msg("Selected Scopes")}"
></ak-dual-select-dynamic-selected>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")} ${msg("Additional scope mappings, which are passed to the proxy.")}
</p> </p>
`} </ak-form-element-horizontal>
></ak-multi-select>
<ak-textarea-input <ak-textarea-input
name="skipPathRegex" name="skipPathRegex"

View File

@ -1,44 +1,28 @@
import "@goauthentik/admin/applications/wizard/ak-wizard-title"; import "@goauthentik/admin/applications/wizard/ak-wizard-title";
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search"; import "@goauthentik/admin/common/ak-flow-search/ak-flow-search";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import {
makeRACPropertyMappingsSelector,
racPropertyMappingsProvider,
} from "@goauthentik/admin/providers/rac/RACPropertyMappings.js";
import "@goauthentik/components/ak-text-input"; import "@goauthentik/components/ak-text-input";
import "@goauthentik/elements/CodeMirror"; import "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { html } from "lit"; import { html } from "lit";
import { customElement, state } from "lit/decorators.js"; import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { import { FlowsInstancesListDesignationEnum, RACProvider } from "@goauthentik/api";
FlowsInstancesListDesignationEnum,
PaginatedRACPropertyMappingList,
PropertymappingsApi,
RACProvider,
} from "@goauthentik/api";
import BaseProviderPanel from "../BaseProviderPanel"; import BaseProviderPanel from "../BaseProviderPanel";
@customElement("ak-application-wizard-authentication-for-rac") @customElement("ak-application-wizard-authentication-for-rac")
export class ApplicationWizardAuthenticationByRAC extends BaseProviderPanel { export class ApplicationWizardAuthenticationByRAC extends BaseProviderPanel {
@state()
propertyMappings?: PaginatedRACPropertyMappingList;
constructor() {
super();
new PropertymappingsApi(DEFAULT_CONFIG)
.propertymappingsRacList({
ordering: "name",
})
.then((propertyMappings) => {
this.propertyMappings = propertyMappings;
});
}
render() { render() {
const provider = this.wizard.provider as RACProvider | undefined; const provider = this.wizard.provider as RACProvider | undefined;
const selected = new Set(Array.from(provider?.propertyMappings ?? []));
const errors = this.wizard.errors.provider; const errors = this.wizard.errors.provider;
return html`<ak-wizard-title return html`<ak-wizard-title
@ -85,17 +69,14 @@ export class ApplicationWizardAuthenticationByRAC extends BaseProviderPanel {
label=${msg("Property mappings")} label=${msg("Property mappings")}
name="propertyMappings" name="propertyMappings"
> >
<select class="pf-c-form-control" multiple> <ak-dual-select-dynamic-selected
${this.propertyMappings?.results.map( .provider=${racPropertyMappingsProvider}
(mapping) => .selector=${makeRACPropertyMappingsSelector(
html`<option provider?.propertyMappings,
value=${ifDefined(mapping.pk)}
?selected=${selected.has(mapping.pk)}
>
${mapping.name}
</option>`,
)} )}
</select> available-label="${msg("Available Property Mappings")}"
selected-label="${msg("Selected Property Mappings")}"
></ak-dual-select-dynamic-selected>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")} ${msg("Hold control/command to select multiple items.")}
</p> </p>

View File

@ -6,6 +6,7 @@ import { ascii_letters, digits, first, randomString } from "@goauthentik/common/
import "@goauthentik/components/ak-radio-input"; import "@goauthentik/components/ak-radio-input";
import "@goauthentik/components/ak-text-input"; import "@goauthentik/components/ak-text-input";
import "@goauthentik/components/ak-textarea-input"; import "@goauthentik/components/ak-textarea-input";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/Radio"; import "@goauthentik/elements/forms/Radio";
@ -23,13 +24,16 @@ import {
IssuerModeEnum, IssuerModeEnum,
OAuth2Provider, OAuth2Provider,
PaginatedOAuthSourceList, PaginatedOAuthSourceList,
PaginatedScopeMappingList,
PropertymappingsApi,
ProvidersApi, ProvidersApi,
SourcesApi, SourcesApi,
SubModeEnum, SubModeEnum,
} from "@goauthentik/api"; } from "@goauthentik/api";
import {
makeOAuth2PropertyMappingsSelector,
oauth2PropertyMappingsProvider,
} from "./Oauth2PropertyMappings.js";
export const clientTypeOptions = [ export const clientTypeOptions = [
{ {
label: msg("Confidential"), label: msg("Confidential"),
@ -123,7 +127,6 @@ export const redirectUriHelp = html`${redirectUriHelpMessages.map(
@customElement("ak-provider-oauth2-form") @customElement("ak-provider-oauth2-form")
export class OAuth2ProviderFormPage extends BaseProviderForm<OAuth2Provider> { export class OAuth2ProviderFormPage extends BaseProviderForm<OAuth2Provider> {
propertyMappings?: PaginatedScopeMappingList;
oauthSources?: PaginatedOAuthSourceList; oauthSources?: PaginatedOAuthSourceList;
@state() @state()
@ -138,11 +141,6 @@ export class OAuth2ProviderFormPage extends BaseProviderForm<OAuth2Provider> {
} }
async load(): Promise<void> { async load(): Promise<void> {
this.propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsScopeList({
ordering: "scope_name",
});
this.oauthSources = await new SourcesApi(DEFAULT_CONFIG).sourcesOauthList({ this.oauthSources = await new SourcesApi(DEFAULT_CONFIG).sourcesOauthList({
ordering: "name", ordering: "name",
hasJwks: true, hasJwks: true,
@ -291,34 +289,20 @@ export class OAuth2ProviderFormPage extends BaseProviderForm<OAuth2Provider> {
> >
</ak-text-input> </ak-text-input>
<ak-form-element-horizontal label=${msg("Scopes")} name="propertyMappings"> <ak-form-element-horizontal label=${msg("Scopes")} name="propertyMappings">
<select class="pf-c-form-control" multiple> <ak-dual-select-dynamic-selected
${this.propertyMappings?.results.map((scope) => { .provider=${oauth2PropertyMappingsProvider}
let selected = false; .selector=${makeOAuth2PropertyMappingsSelector(
if (!provider?.propertyMappings) { provider?.propertyMappings,
selected = scope.managed )}
? defaultScopes.includes(scope.managed) available-label=${msg("Available Scopes")}
: false; selected-label=${msg("Selected Scopes")}
} else { ></ak-dual-select-dynamic-selected>
selected = Array.from(provider?.propertyMappings).some((su) => {
return su == scope.pk;
});
}
return html`<option
value=${ifDefined(scope.pk)}
?selected=${selected}
>
${scope.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
${msg( ${msg(
"Select which scopes can be used by the client. The client still has to specify the scope to access the data.", "Select which scopes can be used by the client. The client still has to specify the scope to access the data.",
)} )}
</p> </p>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-radio-input <ak-radio-input
name="subMode" name="subMode"

View File

@ -0,0 +1,28 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
import { PropertymappingsApi, ScopeMapping } from "@goauthentik/api";
export async function oauth2PropertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsScopeList({
ordering: "scope_name",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map((scope) => [scope.pk, scope.name, scope.name, scope]),
};
}
export function makeOAuth2PropertyMappingsSelector(instanceMappings: string[] | undefined) {
const localMappings = instanceMappings ? new Set(instanceMappings) : undefined;
return localMappings
? ([pk, _]: DualSelectPair) => localMappings.has(pk)
: ([_0, _1, _2, scope]: DualSelectPair<ScopeMapping>) =>
scope?.managed?.startsWith("goauthentik.io/providers/oauth2/scope-") &&
scope?.managed !== "goauthentik.io/providers/oauth2/scope-offline_access";
}

View File

@ -4,6 +4,7 @@ import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm"
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-toggle-group"; import "@goauthentik/components/ak-toggle-group";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/SearchSelect"; import "@goauthentik/elements/forms/SearchSelect";
@ -21,14 +22,17 @@ import PFSpacing from "@patternfly/patternfly/utilities/Spacing/spacing.css";
import { import {
FlowsInstancesListDesignationEnum, FlowsInstancesListDesignationEnum,
PaginatedOAuthSourceList, PaginatedOAuthSourceList,
PaginatedScopeMappingList,
PropertymappingsApi,
ProvidersApi, ProvidersApi,
ProxyMode, ProxyMode,
ProxyProvider, ProxyProvider,
SourcesApi, SourcesApi,
} from "@goauthentik/api"; } from "@goauthentik/api";
import {
makeProxyPropertyMappingsSelector,
proxyPropertyMappingsProvider,
} from "./ProxyProviderPropertyMappings.js";
@customElement("ak-provider-proxy-form") @customElement("ak-provider-proxy-form")
export class ProxyProviderFormPage extends BaseProviderForm<ProxyProvider> { export class ProxyProviderFormPage extends BaseProviderForm<ProxyProvider> {
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
@ -45,18 +49,12 @@ export class ProxyProviderFormPage extends BaseProviderForm<ProxyProvider> {
} }
async load(): Promise<void> { async load(): Promise<void> {
this.propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsScopeList({
ordering: "scope_name",
});
this.oauthSources = await new SourcesApi(DEFAULT_CONFIG).sourcesOauthList({ this.oauthSources = await new SourcesApi(DEFAULT_CONFIG).sourcesOauthList({
ordering: "name", ordering: "name",
hasJwks: true, hasJwks: true,
}); });
} }
propertyMappings?: PaginatedScopeMappingList;
oauthSources?: PaginatedOAuthSourceList; oauthSources?: PaginatedOAuthSourceList;
@state() @state()
@ -323,31 +321,17 @@ export class ProxyProviderFormPage extends BaseProviderForm<ProxyProvider> {
label=${msg("Additional scopes")} label=${msg("Additional scopes")}
name="propertyMappings" name="propertyMappings"
> >
<select class="pf-c-form-control" multiple> <ak-dual-select-dynamic-selected
${this.propertyMappings?.results .provider=${proxyPropertyMappingsProvider}
.filter((scope) => { .selector=${makeProxyPropertyMappingsSelector(
return !scope.managed?.startsWith("goauthentik.io/providers"); this.instance?.propertyMappings,
}) )}
.map((scope) => { available-label="${msg("Available Scopes")}"
const selected = (this.instance?.propertyMappings || []).some( selected-label="${msg("Selected Scopes")}"
(su) => { ></ak-dual-select-dynamic-selected>
return su == scope.pk;
},
);
return html`<option
value=${ifDefined(scope.pk)}
?selected=${selected}
>
${scope.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
${msg("Additional scope mappings, which are passed to the proxy.")} ${msg("Additional scope mappings, which are passed to the proxy.")}
</p> </p>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal <ak-form-element-horizontal

View File

@ -0,0 +1,27 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
import { PropertymappingsApi, ScopeMapping } from "@goauthentik/api";
export async function proxyPropertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsScopeList({
ordering: "scope_name",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map((scope) => [scope.pk, scope.name, scope.name, scope]),
};
}
export function makeProxyPropertyMappingsSelector(mappings?: string[]) {
const localMappings = mappings ? new Set(mappings) : undefined;
return localMappings
? ([pk, _]: DualSelectPair) => localMappings.has(pk)
: ([_0, _1, _2, scope]: DualSelectPair<ScopeMapping>) =>
!(scope?.managed ?? "").startsWith("goauthentik.io/providers");
}

View File

@ -2,6 +2,7 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-radio-input"; import "@goauthentik/components/ak-radio-input";
import "@goauthentik/elements/CodeMirror"; import "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
@ -12,30 +13,18 @@ import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { AuthModeEnum, Endpoint, ProtocolEnum, RacApi } from "@goauthentik/api";
import { import {
AuthModeEnum, makeRACPropertyMappingsSelector,
Endpoint, racPropertyMappingsProvider,
PaginatedRACPropertyMappingList, } from "./RACPropertyMappings.js";
PropertymappingsApi,
ProtocolEnum,
RacApi,
} from "@goauthentik/api";
@customElement("ak-rac-endpoint-form") @customElement("ak-rac-endpoint-form")
export class EndpointForm extends ModelForm<Endpoint, string> { export class EndpointForm extends ModelForm<Endpoint, string> {
@property({ type: Number }) @property({ type: Number })
providerID?: number; providerID?: number;
propertyMappings?: PaginatedRACPropertyMappingList;
async load(): Promise<void> {
this.propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsRacList({
ordering: "name",
});
}
loadInstance(pk: string): Promise<Endpoint> { loadInstance(pk: string): Promise<Endpoint> {
return new RacApi(DEFAULT_CONFIG).racEndpointsRetrieve({ return new RacApi(DEFAULT_CONFIG).racEndpointsRetrieve({
pbmUuid: pk, pbmUuid: pk,
@ -124,22 +113,12 @@ export class EndpointForm extends ModelForm<Endpoint, string> {
</p> </p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Property mappings")} name="propertyMappings"> <ak-form-element-horizontal label=${msg("Property mappings")} name="propertyMappings">
<select class="pf-c-form-control" multiple> <ak-dual-select-dynamic-selected
${this.propertyMappings?.results.map((mapping) => { .provider=${racPropertyMappingsProvider}
let selected = false; .selector=${makeRACPropertyMappingsSelector(this.instance?.propertyMappings)}
if (this.instance?.propertyMappings) { available-label="${msg("Available User Property Mappings")}"
selected = Array.from(this.instance?.propertyMappings).some((su) => { selected-label="${msg("Selected User Property Mappings")}"
return su == mapping.pk; ></ak-dual-select-dynamic-selected>
});
}
return html`<option value=${ifDefined(mapping.pk)} ?selected=${selected}>
${mapping.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-group> <ak-form-group>
<span slot="header"> ${msg("Advanced settings")} </span> <span slot="header"> ${msg("Advanced settings")} </span>

View File

@ -0,0 +1,22 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
import { PropertymappingsApi } from "@goauthentik/api";
export async function racPropertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsRacList({
ordering: "name",
pageSize: 20,
search: search.trim(),
page,
});
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map((mapping) => [mapping.pk, mapping.name]),
};
}
export function makeRACPropertyMappingsSelector(instanceMappings?: string[]) {
const localMappings = new Set(instanceMappings ?? []);
return ([pk, _]: DualSelectPair) => localMappings.has(pk);
}

View File

@ -3,6 +3,7 @@ import "@goauthentik/admin/common/ak-flow-search/ak-branded-flow-search";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/CodeMirror"; import "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import { ModelForm } from "@goauthentik/elements/forms/ModelForm"; import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
@ -13,30 +14,18 @@ import YAML from "yaml";
import { msg } from "@lit/localize"; import { msg } from "@lit/localize";
import { TemplateResult, html } from "lit"; import { TemplateResult, html } from "lit";
import { customElement, state } from "lit/decorators.js"; import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { FlowsInstancesListDesignationEnum, ProvidersApi, RACProvider } from "@goauthentik/api";
import { import {
FlowsInstancesListDesignationEnum, makeRACPropertyMappingsSelector,
PaginatedRACPropertyMappingList, racPropertyMappingsProvider,
PropertymappingsApi, } from "./RACPropertyMappings.js";
ProvidersApi,
RACProvider,
} from "@goauthentik/api";
@customElement("ak-provider-rac-form") @customElement("ak-provider-rac-form")
export class RACProviderFormPage extends ModelForm<RACProvider, number> { export class RACProviderFormPage extends ModelForm<RACProvider, number> {
@state()
propertyMappings?: PaginatedRACPropertyMappingList;
async load(): Promise<void> {
this.propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsRacList({
ordering: "name",
});
}
async loadInstance(pk: number): Promise<RACProvider> { async loadInstance(pk: number): Promise<RACProvider> {
return new ProvidersApi(DEFAULT_CONFIG).providersRacRetrieve({ return new ProvidersApi(DEFAULT_CONFIG).providersRacRetrieve({
id: pk, id: pk,
@ -137,27 +126,14 @@ export class RACProviderFormPage extends ModelForm<RACProvider, number> {
label=${msg("Property mappings")} label=${msg("Property mappings")}
name="propertyMappings" name="propertyMappings"
> >
<select class="pf-c-form-control" multiple> <ak-dual-select-dynamic-selected
${this.propertyMappings?.results.map((mapping) => { .provider=${racPropertyMappingsProvider}
let selected = false; .selector=${makeRACPropertyMappingsSelector(
if (this.instance?.propertyMappings) { this.instance?.propertyMappings,
selected = Array.from(this.instance?.propertyMappings).some( )}
(su) => { available-label="${msg("Available Property Mappings")}"
return su == mapping.pk; selected-label="${msg("Selected Property Mappings")}"
}, ></ak-dual-select-dynamic-selected>
);
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal label=${msg("Settings")} name="settings"> <ak-form-element-horizontal label=${msg("Settings")} name="settings">
<ak-codemirror <ak-codemirror

View File

@ -2,6 +2,8 @@ import "@goauthentik/admin/common/ak-crypto-certificate-search";
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search"; import "@goauthentik/admin/common/ak-flow-search/ak-flow-search";
import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm"; import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/Radio"; import "@goauthentik/elements/forms/Radio";
@ -16,7 +18,6 @@ import { ifDefined } from "lit/directives/if-defined.js";
import { import {
DigestAlgorithmEnum, DigestAlgorithmEnum,
FlowsInstancesListDesignationEnum, FlowsInstancesListDesignationEnum,
PaginatedSAMLPropertyMappingList,
PropertymappingsApi, PropertymappingsApi,
PropertymappingsSamlListRequest, PropertymappingsSamlListRequest,
ProvidersApi, ProvidersApi,
@ -26,6 +27,29 @@ import {
SpBindingEnum, SpBindingEnum,
} from "@goauthentik/api"; } from "@goauthentik/api";
export async function samlPropertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsSamlList(
{
ordering: "saml_name",
pageSize: 20,
search: search.trim(),
page,
},
);
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map((m) => [m.pk, m.name, m.name, m]),
};
}
export function makeSAMLPropertyMappingsSelector(instanceMappings?: string[]) {
const localMappings = instanceMappings ? new Set(instanceMappings) : undefined;
return localMappings
? ([pk, _]: DualSelectPair) => localMappings.has(pk)
: ([_0, _1, _2, mapping]: DualSelectPair<SAMLPropertyMapping>) =>
mapping?.managed?.startsWith("goauthentik.io/providers/saml");
}
@customElement("ak-provider-saml-form") @customElement("ak-provider-saml-form")
export class SAMLProviderFormPage extends BaseProviderForm<SAMLProvider> { export class SAMLProviderFormPage extends BaseProviderForm<SAMLProvider> {
loadInstance(pk: number): Promise<SAMLProvider> { loadInstance(pk: number): Promise<SAMLProvider> {
@ -34,16 +58,6 @@ export class SAMLProviderFormPage extends BaseProviderForm<SAMLProvider> {
}); });
} }
async load(): Promise<void> {
this.propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsSamlList({
ordering: "saml_name",
});
}
propertyMappings?: PaginatedSAMLPropertyMappingList;
async send(data: SAMLProvider): Promise<SAMLProvider> { async send(data: SAMLProvider): Promise<SAMLProvider> {
if (this.instance) { if (this.instance) {
return new ProvidersApi(DEFAULT_CONFIG).providersSamlUpdate({ return new ProvidersApi(DEFAULT_CONFIG).providersSamlUpdate({
@ -193,29 +207,14 @@ export class SAMLProviderFormPage extends BaseProviderForm<SAMLProvider> {
label=${msg("Property mappings")} label=${msg("Property mappings")}
name="propertyMappings" name="propertyMappings"
> >
<select class="pf-c-form-control" multiple> <ak-dual-select-dynamic-selected
${this.propertyMappings?.results.map((mapping) => { .provider=${samlPropertyMappingsProvider}
let selected = false; .selector=${makeSAMLPropertyMappingsSelector(
if (!this.instance?.propertyMappings) { this.instance?.propertyMappings,
selected = )}
mapping.managed?.startsWith( available-label=${msg("Available User Property Mappings")}
"goauthentik.io/providers/saml", selected-label=${msg("Selected User Property Mappings")}
) || false; ></ak-dual-select-dynamic-selected>
} else {
selected = Array.from(this.instance?.propertyMappings).some(
(su) => {
return su == mapping.pk;
},
);
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")} ${msg("Hold control/command to select multiple items.")}
</p> </p>

View File

@ -1,6 +1,8 @@
import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm"; import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/Radio"; import "@goauthentik/elements/forms/Radio";
@ -15,12 +17,35 @@ import {
CoreApi, CoreApi,
CoreGroupsListRequest, CoreGroupsListRequest,
Group, Group,
PaginatedSCIMMappingList,
PropertymappingsApi, PropertymappingsApi,
ProvidersApi, ProvidersApi,
SCIMMapping,
SCIMProvider, SCIMProvider,
} from "@goauthentik/api"; } from "@goauthentik/api";
export async function scimPropertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsScimList(
{
ordering: "managed",
pageSize: 20,
search: search.trim(),
page,
},
);
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map((m) => [m.pk, m.name, m.name, m]),
};
}
export function makeSCIMPropertyMappingsSelector(instanceMappings: string[] | undefined) {
const localMappings = instanceMappings ? new Set(instanceMappings) : undefined;
return localMappings
? ([pk, _]: DualSelectPair) => localMappings.has(pk)
: ([_0, _1, _2, mapping]: DualSelectPair<SCIMMapping>) =>
mapping?.managed === "goauthentik.io/providers/scim/user";
}
@customElement("ak-provider-scim-form") @customElement("ak-provider-scim-form")
export class SCIMProviderFormPage extends BaseProviderForm<SCIMProvider> { export class SCIMProviderFormPage extends BaseProviderForm<SCIMProvider> {
loadInstance(pk: number): Promise<SCIMProvider> { loadInstance(pk: number): Promise<SCIMProvider> {
@ -29,16 +54,6 @@ export class SCIMProviderFormPage extends BaseProviderForm<SCIMProvider> {
}); });
} }
async load(): Promise<void> {
this.propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsScimList({
ordering: "managed",
});
}
propertyMappings?: PaginatedSCIMMappingList;
async send(data: SCIMProvider): Promise<SCIMProvider> { async send(data: SCIMProvider): Promise<SCIMProvider> {
if (this.instance) { if (this.instance) {
return new ProvidersApi(DEFAULT_CONFIG).providersScimUpdate({ return new ProvidersApi(DEFAULT_CONFIG).providersScimUpdate({
@ -152,68 +167,34 @@ export class SCIMProviderFormPage extends BaseProviderForm<SCIMProvider> {
<div slot="body" class="pf-c-form"> <div slot="body" class="pf-c-form">
<ak-form-element-horizontal <ak-form-element-horizontal
label=${msg("User Property Mappings")} label=${msg("User Property Mappings")}
name="propertyMappings" name="propertyMappings">
> <ak-dual-select-dynamic-selected
<select class="pf-c-form-control" multiple> .provider=${scimPropertyMappingsProvider}
${this.propertyMappings?.results.map((mapping) => { .selector=${makeSCIMPropertyMappingsSelector(
let selected = false; this.instance?.propertyMappings,
if (!this.instance?.propertyMappings) { )}
selected = available-label=${msg("Available User Property Mappings")}
mapping.managed === "goauthentik.io/providers/scim/user" || selected-label=${msg("Selected User Property Mappings")}
false; ></ak-dual-select-dynamic-selected>
} else {
selected = Array.from(this.instance?.propertyMappings).some(
(su) => {
return su == mapping.pk;
},
);
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
})}
</select> </select>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
${msg("Property mappings used to user mapping.")} ${msg("Property mappings used to user mapping.")}
</p> </p>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal <ak-form-element-horizontal
label=${msg("Group Property Mappings")} label=${msg("Group Property Mappings")}
name="propertyMappingsGroup" name="propertyMappingsGroup">
> <ak-dual-select-dynamic-selected
<select class="pf-c-form-control" multiple> .provider=${scimPropertyMappingsProvider}
${this.propertyMappings?.results.map((mapping) => { .selector=${makeSCIMPropertyMappingsSelector(
let selected = false;
if (!this.instance?.propertyMappingsGroup) {
selected =
mapping.managed === "goauthentik.io/providers/scim/group";
} else {
selected = Array.from(
this.instance?.propertyMappingsGroup, this.instance?.propertyMappingsGroup,
).some((su) => { )}
return su == mapping.pk; available-label=${msg("Available Group Property Mappings")}
}); selected-label=${msg("Selected Group Property Mappings")}
} ></ak-dual-select-dynamic-selected>
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
${msg("Property mappings used to group creation.")} ${msg("Property mappings used to group creation.")}
</p> </p>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
</div> </div>
</ak-form-group>`; </ak-form-group>`;

View File

@ -3,6 +3,8 @@ import { placeholderHelperText } from "@goauthentik/admin/helperText";
import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm"; import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
import { DualSelectPair } from "@goauthentik/elements/ak-dual-select/types.js";
import "@goauthentik/elements/forms/FormGroup"; import "@goauthentik/elements/forms/FormGroup";
import "@goauthentik/elements/forms/HorizontalFormElement"; import "@goauthentik/elements/forms/HorizontalFormElement";
import "@goauthentik/elements/forms/SearchSelect"; import "@goauthentik/elements/forms/SearchSelect";
@ -16,13 +18,37 @@ import {
CoreApi, CoreApi,
CoreGroupsListRequest, CoreGroupsListRequest,
Group, Group,
LDAPPropertyMapping,
LDAPSource, LDAPSource,
LDAPSourceRequest, LDAPSourceRequest,
PaginatedLDAPPropertyMappingList,
PropertymappingsApi, PropertymappingsApi,
SourcesApi, SourcesApi,
} from "@goauthentik/api"; } from "@goauthentik/api";
async function propertyMappingsProvider(page = 1, search = "") {
const propertyMappings = await new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsLdapList(
{
ordering: "managed,object_field",
pageSize: 20,
search: search.trim(),
page,
},
);
return {
pagination: propertyMappings.pagination,
options: propertyMappings.results.map((m) => [m.pk, m.name, m.name, m]),
};
}
function makePropertyMappingsSelector(instanceMappings?: string[]) {
const localMappings = instanceMappings ? new Set(instanceMappings) : undefined;
return localMappings
? ([pk, _]: DualSelectPair) => localMappings.has(pk)
: ([_0, _1, _2, mapping]: DualSelectPair<LDAPPropertyMapping>) =>
mapping?.managed?.startsWith("goauthentik.io/sources/ldap/default") ||
mapping?.managed?.startsWith("goauthentik.io/sources/ldap/ms");
}
@customElement("ak-source-ldap-form") @customElement("ak-source-ldap-form")
export class LDAPSourceForm extends BaseSourceForm<LDAPSource> { export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
loadInstance(pk: string): Promise<LDAPSource> { loadInstance(pk: string): Promise<LDAPSource> {
@ -31,16 +57,6 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
}); });
} }
async load(): Promise<void> {
this.propertyMappings = await new PropertymappingsApi(
DEFAULT_CONFIG,
).propertymappingsLdapList({
ordering: "managed,object_field",
});
}
propertyMappings?: PaginatedLDAPPropertyMappingList;
async send(data: LDAPSource): Promise<LDAPSource> { async send(data: LDAPSource): Promise<LDAPSource> {
if (this.instance) { if (this.instance) {
return new SourcesApi(DEFAULT_CONFIG).sourcesLdapPartialUpdate({ return new SourcesApi(DEFAULT_CONFIG).sourcesLdapPartialUpdate({
@ -277,71 +293,32 @@ export class LDAPSourceForm extends BaseSourceForm<LDAPSource> {
label=${msg("User Property Mappings")} label=${msg("User Property Mappings")}
name="propertyMappings" name="propertyMappings"
> >
<select class="pf-c-form-control" multiple> <ak-dual-select-dynamic-selected
${this.propertyMappings?.results.map((mapping) => { .provider=${propertyMappingsProvider}
let selected = false; .selector=${makePropertyMappingsSelector(
if (!this.instance?.propertyMappings) { this.instance?.propertyMappings,
selected = )}
mapping.managed?.startsWith( available-label="${msg("Available User Property Mappings")}"
"goauthentik.io/sources/ldap/default", selected-label="${msg("Selected User Property Mappings")}"
) || ></ak-dual-select-dynamic-selected>
mapping.managed?.startsWith(
"goauthentik.io/sources/ldap/ms",
) ||
false;
} else {
selected = Array.from(this.instance?.propertyMappings).some(
(su) => {
return su == mapping.pk;
},
);
}
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
${msg("Property mappings used to user creation.")} ${msg("Property mappings for user creation.")}
</p>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p> </p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
<ak-form-element-horizontal <ak-form-element-horizontal
label=${msg("Group Property Mappings")} label=${msg("Group Property Mappings")}
name="propertyMappingsGroup" name="propertyMappingsGroup"
> >
<select class="pf-c-form-control" multiple> <ak-dual-select-dynamic-selected
${this.propertyMappings?.results.map((mapping) => { .provider=${propertyMappingsProvider}
let selected = false; .selector=${makePropertyMappingsSelector(
if (!this.instance?.propertyMappingsGroup) {
selected =
mapping.managed ===
"goauthentik.io/sources/ldap/default-name";
} else {
selected = Array.from(
this.instance?.propertyMappingsGroup, this.instance?.propertyMappingsGroup,
).some((su) => { )}
return su == mapping.pk; available-label="${msg("Available Group Property Mappings")}"
}); selected-label="${msg("Selected Group Property Mappings")}"
} ></ak-dual-select-dynamic-selected>
return html`<option
value=${ifDefined(mapping.pk)}
?selected=${selected}
>
${mapping.name}
</option>`;
})}
</select>
<p class="pf-c-form__helper-text"> <p class="pf-c-form__helper-text">
${msg("Property mappings used to group creation.")} ${msg("Property mappings for group creation.")}
</p>
<p class="pf-c-form__helper-text">
${msg("Hold control/command to select multiple items.")}
</p> </p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
</div> </div>

View File

@ -0,0 +1,52 @@
import { PropertyValues, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { ref } from "lit/directives/ref.js";
import { AkDualSelectProvider } from "./ak-dual-select-provider.js";
import "./ak-dual-select.js";
import type { DualSelectPair } from "./types.js";
/**
* @element ak-dual-select-dynamic-provider
*
* A top-level component for multi-select elements have dynamically generated "selected"
* lists.
*/
@customElement("ak-dual-select-dynamic-selected")
export class AkDualSelectDynamic extends AkDualSelectProvider {
/**
* An extra source of "default" entries. A number of our collections have an alternative default
* source when initializing a new component instance of that collection's host object. Only run
* on start-up.
*
* @attr
*/
@property({ attribute: false })
selector: ([key, _]: DualSelectPair) => boolean = ([_key, _]) => false;
private firstUpdateHasRun = false;
willUpdate(changed: PropertyValues<this>) {
super.willUpdate(changed);
// On the first update *only*, even before rendering, when the options are handed up, update
// the selected list with the contents derived from the selector.
if (!this.firstUpdateHasRun && this.options.length > 0) {
this.firstUpdateHasRun = true;
this.selected = Array.from(
new Set([...this.selected, ...this.options.filter(this.selector)]),
);
}
}
render() {
return html`<ak-dual-select
${ref(this.dualSelector)}
.options=${this.options}
.pages=${this.pagination}
.selected=${this.selected}
available-label=${this.availableLabel}
selected-label=${this.selectedLabel}
></ak-dual-select>`;
}
}

View File

@ -27,36 +27,59 @@ import type { DataProvider, DualSelectPair } from "./types";
@customElement("ak-dual-select-provider") @customElement("ak-dual-select-provider")
export class AkDualSelectProvider extends CustomListenerElement(AKElement) { export class AkDualSelectProvider extends CustomListenerElement(AKElement) {
/** A function that takes a page and returns the DualSelectPair[] collection with which to update /**
* A function that takes a page and returns the DualSelectPair[] collection with which to update
* the "Available" pane. * the "Available" pane.
*
* @attr
*/ */
@property({ type: Object }) @property({ type: Object })
provider!: DataProvider; provider!: DataProvider;
/**
* The list of selected items. This is the *complete* list, not paginated, as presented by a
* component with a multi-select list of items to track.
*
* @attr
*/
@property({ type: Array }) @property({ type: Array })
selected: DualSelectPair[] = []; selected: DualSelectPair[] = [];
/**
* The label for the left ("available") pane
*
* @attr
*/
@property({ attribute: "available-label" }) @property({ attribute: "available-label" })
availableLabel = msg("Available options"); availableLabel = msg("Available options");
/**
* The label for the right ("selected") pane
*
* @attr
*/
@property({ attribute: "selected-label" }) @property({ attribute: "selected-label" })
selectedLabel = msg("Selected options"); selectedLabel = msg("Selected options");
/** The remote lists are debounced by definition. This is the interval for the debounce. */ /**
* The debounce for the search as the user is typing in a request
*
* @attr
*/
@property({ attribute: "search-delay", type: Number }) @property({ attribute: "search-delay", type: Number })
searchDelay = 250; searchDelay = 250;
@state() @state()
private options: DualSelectPair[] = []; options: DualSelectPair[] = [];
private dualSelector: Ref<AkDualSelect> = createRef(); protected dualSelector: Ref<AkDualSelect> = createRef();
private isLoading = false; protected isLoading = false;
private doneFirstUpdate = false; private doneFirstUpdate = false;
private internalSelected: DualSelectPair[] = []; private internalSelected: DualSelectPair[] = [];
private pagination?: Pagination; protected pagination?: Pagination;
constructor() { constructor() {
super(); super();

View File

@ -4,7 +4,7 @@ import { Pagination } from "@goauthentik/api";
// Key, Label (string or TemplateResult), (optional) string to sort by. If the sort string is // Key, Label (string or TemplateResult), (optional) string to sort by. If the sort string is
// missing, it will use the label, which doesn't always work for TemplateResults). // missing, it will use the label, which doesn't always work for TemplateResults).
export type DualSelectPair = [string, string | TemplateResult, string?]; export type DualSelectPair<T = never> = [string, string | TemplateResult, string?, T?];
export type BasePagination = Pick< export type BasePagination = Pick<
Pagination, Pagination,