web: re-organise frontend and cleanup common code (#3572)
* fix repo in api client Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: re-organise files to match their interface Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * core: include version in script tags Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * cleanup maybe broken Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * revert rename Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: get rid of Client.ts Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * move more to common Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * more moving Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * format Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * unfuck files that vscode fucked, thanks Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * move more Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * finish moving (maybe) Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * ok more moving Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix more stuff that vs code destroyed Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * get rid "web" prefix for virtual package Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix locales Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * use custom base element Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix css file Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * don't run autoDetectLanguage when importing locale Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * fix circular dependencies Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> * web: fix build Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org> Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
204
web/src/admin/policies/BoundPoliciesList.ts
Normal file
204
web/src/admin/policies/BoundPoliciesList.ts
Normal file
@ -0,0 +1,204 @@
|
||||
import "@goauthentik/admin/groups/GroupForm";
|
||||
import "@goauthentik/admin/policies/PolicyBindingForm";
|
||||
import "@goauthentik/admin/policies/PolicyWizard";
|
||||
import "@goauthentik/admin/users/UserForm";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { uiConfig } from "@goauthentik/common/ui/config";
|
||||
import { PFColor } from "@goauthentik/elements/Label";
|
||||
import { PFSize } from "@goauthentik/elements/Spinner";
|
||||
import "@goauthentik/elements/Tabs";
|
||||
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||
import "@goauthentik/elements/forms/ModalForm";
|
||||
import "@goauthentik/elements/forms/ProxyForm";
|
||||
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
||||
import { Table, TableColumn } from "@goauthentik/elements/table/Table";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import { PoliciesApi, PolicyBinding } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-bound-policies-list")
|
||||
export class BoundPoliciesList extends Table<PolicyBinding> {
|
||||
@property()
|
||||
target?: string;
|
||||
|
||||
@property({ type: Boolean })
|
||||
policyOnly = false;
|
||||
|
||||
checkbox = true;
|
||||
|
||||
async apiEndpoint(page: number): Promise<PaginatedResponse<PolicyBinding>> {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsList({
|
||||
target: this.target || "",
|
||||
ordering: "order",
|
||||
page: page,
|
||||
pageSize: (await uiConfig()).pagination.perPage,
|
||||
});
|
||||
}
|
||||
|
||||
columns(): TableColumn[] {
|
||||
return [
|
||||
new TableColumn(t`Policy / User / Group`),
|
||||
new TableColumn(t`Enabled`, "enabled"),
|
||||
new TableColumn(t`Order`, "order"),
|
||||
new TableColumn(t`Timeout`, "timeout"),
|
||||
new TableColumn(t`Actions`),
|
||||
];
|
||||
}
|
||||
|
||||
getPolicyUserGroupRowLabel(item: PolicyBinding): string {
|
||||
if (item.policy) {
|
||||
return t`Policy ${item.policyObj?.name}`;
|
||||
} else if (item.group) {
|
||||
return t`Group ${item.groupObj?.name}`;
|
||||
} else if (item.user) {
|
||||
return t`User ${item.userObj?.name}`;
|
||||
} else {
|
||||
return t`-`;
|
||||
}
|
||||
}
|
||||
|
||||
getPolicyUserGroupRow(item: PolicyBinding): TemplateResult {
|
||||
const label = this.getPolicyUserGroupRowLabel(item);
|
||||
if (item.user) {
|
||||
return html` <a href=${`#/identity/users/${item.user}`}> ${label} </a> `;
|
||||
}
|
||||
if (item.group) {
|
||||
return html` <a href=${`#/identity/groups/${item.group}`}> ${label} </a> `;
|
||||
}
|
||||
return html`${label}`;
|
||||
}
|
||||
|
||||
getObjectEditButton(item: PolicyBinding): TemplateResult {
|
||||
if (item.policy) {
|
||||
return html`<ak-forms-modal>
|
||||
<span slot="submit"> ${t`Update`} </span>
|
||||
<span slot="header"> ${t`Update ${item.policyObj?.name}`} </span>
|
||||
<ak-proxy-form
|
||||
slot="form"
|
||||
.args=${{
|
||||
instancePk: item.policyObj?.pk,
|
||||
}}
|
||||
type=${ifDefined(item.policyObj?.component)}
|
||||
>
|
||||
</ak-proxy-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit Policy`}</button>
|
||||
</ak-forms-modal>`;
|
||||
} else if (item.group) {
|
||||
return html`<ak-forms-modal>
|
||||
<span slot="submit"> ${t`Update`} </span>
|
||||
<span slot="header"> ${t`Update Group`} </span>
|
||||
<ak-group-form slot="form" .instancePk=${item.groupObj?.pk}> </ak-group-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit Group`}</button>
|
||||
</ak-forms-modal>`;
|
||||
} else if (item.user) {
|
||||
return html`<ak-forms-modal>
|
||||
<span slot="submit"> ${t`Update`} </span>
|
||||
<span slot="header"> ${t`Update User`} </span>
|
||||
<ak-user-form slot="form" .instancePk=${item.userObj?.pk}> </ak-user-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-secondary">${t`Edit User`}</button>
|
||||
</ak-forms-modal>`;
|
||||
} else {
|
||||
return html``;
|
||||
}
|
||||
}
|
||||
|
||||
renderToolbarSelected(): TemplateResult {
|
||||
const disabled = this.selectedElements.length < 1;
|
||||
return html`<ak-forms-delete-bulk
|
||||
objectLabel=${t`Policy binding(s)`}
|
||||
.objects=${this.selectedElements}
|
||||
.metadata=${(item: PolicyBinding) => {
|
||||
return [
|
||||
{ key: t`Order`, value: item.order.toString() },
|
||||
{ key: t`Policy / User / Group`, value: this.getPolicyUserGroupRowLabel(item) },
|
||||
];
|
||||
}}
|
||||
.usedBy=${(item: PolicyBinding) => {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsUsedByList({
|
||||
policyBindingUuid: item.pk,
|
||||
});
|
||||
}}
|
||||
.delete=${(item: PolicyBinding) => {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsDestroy({
|
||||
policyBindingUuid: item.pk,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
|
||||
${t`Delete`}
|
||||
</button>
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
row(item: PolicyBinding): TemplateResult[] {
|
||||
return [
|
||||
html`${this.getPolicyUserGroupRow(item)}`,
|
||||
html` <ak-label color=${item.enabled ? PFColor.Green : PFColor.Orange}>
|
||||
${item.enabled ? t`Yes` : t`No`}
|
||||
</ak-label>`,
|
||||
html`${item.order}`,
|
||||
html`${item.timeout}`,
|
||||
html` ${this.getObjectEditButton(item)}
|
||||
<ak-forms-modal size=${PFSize.Medium}>
|
||||
<span slot="submit"> ${t`Update`} </span>
|
||||
<span slot="header"> ${t`Update Binding`} </span>
|
||||
<ak-policy-binding-form
|
||||
slot="form"
|
||||
.instancePk=${item.pk}
|
||||
targetPk=${ifDefined(this.target)}
|
||||
?policyOnly=${this.policyOnly}
|
||||
>
|
||||
</ak-policy-binding-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-secondary">
|
||||
${t`Edit Binding`}
|
||||
</button>
|
||||
</ak-forms-modal>`,
|
||||
];
|
||||
}
|
||||
|
||||
renderEmpty(): TemplateResult {
|
||||
return super.renderEmpty(html`<ak-empty-state
|
||||
header=${t`No Policies bound.`}
|
||||
icon="pf-icon-module"
|
||||
>
|
||||
<div slot="body">${t`No policies are currently bound to this object.`}</div>
|
||||
<div slot="primary">
|
||||
<ak-forms-modal size=${PFSize.Medium}>
|
||||
<span slot="submit"> ${t`Create`} </span>
|
||||
<span slot="header"> ${t`Create Binding`} </span>
|
||||
<ak-policy-binding-form
|
||||
slot="form"
|
||||
targetPk=${ifDefined(this.target)}
|
||||
?policyOnly=${this.policyOnly}
|
||||
>
|
||||
</ak-policy-binding-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">
|
||||
${t`Create Binding`}
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
</div>
|
||||
</ak-empty-state>`);
|
||||
}
|
||||
|
||||
renderToolbar(): TemplateResult {
|
||||
return html`<ak-forms-modal size=${PFSize.Medium}>
|
||||
<span slot="submit"> ${t`Create`} </span>
|
||||
<span slot="header"> ${t`Create Binding`} </span>
|
||||
<ak-policy-binding-form
|
||||
slot="form"
|
||||
targetPk=${ifDefined(this.target)}
|
||||
?policyOnly=${this.policyOnly}
|
||||
>
|
||||
</ak-policy-binding-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">
|
||||
${t`Create Binding`}
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
<ak-policy-wizard createText=${t`Create Policy`}></ak-policy-wizard> `;
|
||||
}
|
||||
}
|
||||
325
web/src/admin/policies/PolicyBindingForm.ts
Normal file
325
web/src/admin/policies/PolicyBindingForm.ts
Normal file
@ -0,0 +1,325 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { first, groupBy } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/SearchSelect";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
import { UserOption } from "@goauthentik/elements/user/utils";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { CSSResult, css } from "lit";
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { until } from "lit/directives/until.js";
|
||||
|
||||
import PFContent from "@patternfly/patternfly/components/Content/content.css";
|
||||
import PFToggleGroup from "@patternfly/patternfly/components/ToggleGroup/toggle-group.css";
|
||||
|
||||
import {
|
||||
CoreApi,
|
||||
CoreGroupsListRequest,
|
||||
CoreUsersListRequest,
|
||||
Group,
|
||||
PoliciesApi,
|
||||
Policy,
|
||||
PolicyBinding,
|
||||
User,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
enum target {
|
||||
policy,
|
||||
group,
|
||||
user,
|
||||
}
|
||||
|
||||
@customElement("ak-policy-binding-form")
|
||||
export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
|
||||
loadInstance(pk: string): Promise<PolicyBinding> {
|
||||
return new PoliciesApi(DEFAULT_CONFIG)
|
||||
.policiesBindingsRetrieve({
|
||||
policyBindingUuid: pk,
|
||||
})
|
||||
.then((binding) => {
|
||||
if (binding?.policyObj) {
|
||||
this.policyGroupUser = target.policy;
|
||||
}
|
||||
if (binding?.groupObj) {
|
||||
this.policyGroupUser = target.group;
|
||||
}
|
||||
if (binding?.userObj) {
|
||||
this.policyGroupUser = target.user;
|
||||
}
|
||||
return binding;
|
||||
});
|
||||
}
|
||||
|
||||
@property()
|
||||
targetPk?: string;
|
||||
|
||||
@property({ type: Number })
|
||||
policyGroupUser: target = target.policy;
|
||||
|
||||
@property({ type: Boolean })
|
||||
policyOnly = false;
|
||||
|
||||
getSuccessMessage(): string {
|
||||
if (this.instance) {
|
||||
return t`Successfully updated binding.`;
|
||||
} else {
|
||||
return t`Successfully created binding.`;
|
||||
}
|
||||
}
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return super.styles.concat(
|
||||
PFToggleGroup,
|
||||
PFContent,
|
||||
css`
|
||||
.pf-c-toggle-group {
|
||||
justify-content: center;
|
||||
}
|
||||
`,
|
||||
);
|
||||
}
|
||||
|
||||
send = (data: PolicyBinding): Promise<PolicyBinding> => {
|
||||
if (this.instance) {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsUpdate({
|
||||
policyBindingUuid: this.instance.pk || "",
|
||||
policyBindingRequest: data,
|
||||
});
|
||||
} else {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsCreate({
|
||||
policyBindingRequest: data,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
groupPolicies(policies: Policy[]): TemplateResult {
|
||||
return html`
|
||||
${groupBy<Policy>(policies, (p) => p.verboseName || "").map(([group, policies]) => {
|
||||
return html`<optgroup label=${group}>
|
||||
${policies.map((p) => {
|
||||
const selected = this.instance?.policy === p.pk;
|
||||
return html`<option ?selected=${selected} value=${ifDefined(p.pk)}>
|
||||
${p.name}
|
||||
</option>`;
|
||||
})}
|
||||
</optgroup>`;
|
||||
})}
|
||||
`;
|
||||
}
|
||||
|
||||
getOrder(): Promise<number> {
|
||||
if (this.instance) {
|
||||
return Promise.resolve(this.instance.order);
|
||||
}
|
||||
return new PoliciesApi(DEFAULT_CONFIG)
|
||||
.policiesBindingsList({
|
||||
target: this.targetPk || "",
|
||||
})
|
||||
.then((bindings) => {
|
||||
const orders = bindings.results.map((binding) => binding.order);
|
||||
if (orders.length < 1) {
|
||||
return 0;
|
||||
}
|
||||
return Math.max(...orders) + 1;
|
||||
});
|
||||
}
|
||||
|
||||
renderModeSelector(): TemplateResult {
|
||||
return html` <div class="pf-c-toggle-group__item">
|
||||
<button
|
||||
class="pf-c-toggle-group__button ${this.policyGroupUser === target.policy
|
||||
? "pf-m-selected"
|
||||
: ""}"
|
||||
type="button"
|
||||
@click=${() => {
|
||||
this.policyGroupUser = target.policy;
|
||||
}}
|
||||
>
|
||||
<span class="pf-c-toggle-group__text">${t`Policy`}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
|
||||
<div class="pf-c-toggle-group__item">
|
||||
<button
|
||||
class="pf-c-toggle-group__button ${this.policyGroupUser === target.group
|
||||
? "pf-m-selected"
|
||||
: ""}"
|
||||
type="button"
|
||||
@click=${() => {
|
||||
this.policyGroupUser = target.group;
|
||||
}}
|
||||
>
|
||||
<span class="pf-c-toggle-group__text">${t`Group`}</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pf-c-divider pf-m-vertical" role="separator"></div>
|
||||
<div class="pf-c-toggle-group__item">
|
||||
<button
|
||||
class="pf-c-toggle-group__button ${this.policyGroupUser === target.user
|
||||
? "pf-m-selected"
|
||||
: ""}"
|
||||
type="button"
|
||||
@click=${() => {
|
||||
this.policyGroupUser = target.user;
|
||||
}}
|
||||
>
|
||||
<span class="pf-c-toggle-group__text">${t`User`}</span>
|
||||
</button>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
<div class="pf-c-card pf-m-selectable pf-m-selected">
|
||||
<div class="pf-c-card__body">
|
||||
<div class="pf-c-toggle-group">${this.renderModeSelector()}</div>
|
||||
</div>
|
||||
<div class="pf-c-card__footer">
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Policy`}
|
||||
name="policy"
|
||||
?hidden=${this.policyGroupUser !== target.policy}
|
||||
>
|
||||
<select class="pf-c-form-control">
|
||||
<option value="" ?selected=${this.instance?.policy === undefined}>
|
||||
---------
|
||||
</option>
|
||||
${until(
|
||||
new PoliciesApi(DEFAULT_CONFIG)
|
||||
.policiesAllList({
|
||||
ordering: "name",
|
||||
})
|
||||
.then((policies) => {
|
||||
return this.groupPolicies(policies.results);
|
||||
}),
|
||||
html`<option>${t`Loading...`}</option>`,
|
||||
)}
|
||||
</select>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Group`}
|
||||
name="group"
|
||||
?hidden=${this.policyGroupUser !== target.group}
|
||||
>
|
||||
<!-- @ts-ignore -->
|
||||
<ak-search-select
|
||||
.fetchObjects=${async (query?: string): Promise<Group[]> => {
|
||||
const args: CoreGroupsListRequest = {
|
||||
ordering: "name",
|
||||
};
|
||||
if (query !== undefined) {
|
||||
args.search = query;
|
||||
}
|
||||
const groups = await new CoreApi(DEFAULT_CONFIG).coreGroupsList(
|
||||
args,
|
||||
);
|
||||
return groups.results;
|
||||
}}
|
||||
.renderElement=${(group: Group): string => {
|
||||
return group.name;
|
||||
}}
|
||||
.value=${(group: Group | undefined): string | undefined => {
|
||||
return group ? group.pk : undefined;
|
||||
}}
|
||||
.selected=${(group: Group): boolean => {
|
||||
return group.pk === this.instance?.group;
|
||||
}}
|
||||
?blankable=${true}
|
||||
>
|
||||
</ak-search-select>
|
||||
${this.policyOnly
|
||||
? html`<p class="pf-c-form__helper-text">
|
||||
${t`Group mappings can only be checked if a user is already logged in when trying to access this source.`}
|
||||
</p>`
|
||||
: html``}
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`User`}
|
||||
name="user"
|
||||
?hidden=${this.policyGroupUser !== target.user}
|
||||
>
|
||||
<!-- @ts-ignore -->
|
||||
<ak-search-select
|
||||
.fetchObjects=${async (query?: string): Promise<User[]> => {
|
||||
const args: CoreUsersListRequest = {
|
||||
ordering: "username",
|
||||
};
|
||||
if (query !== undefined) {
|
||||
args.search = query;
|
||||
}
|
||||
const users = await new CoreApi(DEFAULT_CONFIG).coreUsersList(args);
|
||||
return users.results;
|
||||
}}
|
||||
.renderElement=${(user: User): string => {
|
||||
return UserOption(user);
|
||||
}}
|
||||
.value=${(user: User | undefined): number | undefined => {
|
||||
return user ? user.pk : undefined;
|
||||
}}
|
||||
.selected=${(user: User): boolean => {
|
||||
return user.pk === this.instance?.user;
|
||||
}}
|
||||
?blankable=${true}
|
||||
>
|
||||
</ak-search-select>
|
||||
${this.policyOnly
|
||||
? html`<p class="pf-c-form__helper-text">
|
||||
${t`User mappings can only be checked if a user is already logged in when trying to access this source.`}
|
||||
</p>`
|
||||
: html``}
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
required
|
||||
name="target"
|
||||
type="hidden"
|
||||
value=${ifDefined(this.instance?.target || this.targetPk)}
|
||||
/>
|
||||
<ak-form-element-horizontal name="enabled">
|
||||
<div class="pf-c-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="pf-c-check__input"
|
||||
?checked=${first(this.instance?.enabled, true)}
|
||||
/>
|
||||
<label class="pf-c-check__label"> ${t`Enabled`} </label>
|
||||
</div>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal name="negate">
|
||||
<div class="pf-c-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="pf-c-check__input"
|
||||
?checked=${first(this.instance?.negate, false)}
|
||||
/>
|
||||
<label class="pf-c-check__label"> ${t`Negate result`} </label>
|
||||
</div>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Negates the outcome of the binding. Messages are unaffected.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Order`} ?required=${true} name="order">
|
||||
<!-- @ts-ignore -->
|
||||
<input
|
||||
type="number"
|
||||
value="${until(this.getOrder())}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Timeout`} ?required=${true} name="timeout">
|
||||
<input
|
||||
type="number"
|
||||
value="${first(this.instance?.timeout, 30)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
154
web/src/admin/policies/PolicyListPage.ts
Normal file
154
web/src/admin/policies/PolicyListPage.ts
Normal file
@ -0,0 +1,154 @@
|
||||
import "@goauthentik/admin/policies/PolicyTestForm";
|
||||
import "@goauthentik/admin/policies/PolicyWizard";
|
||||
import "@goauthentik/admin/policies/dummy/DummyPolicyForm";
|
||||
import "@goauthentik/admin/policies/event_matcher/EventMatcherPolicyForm";
|
||||
import "@goauthentik/admin/policies/expiry/ExpiryPolicyForm";
|
||||
import "@goauthentik/admin/policies/expression/ExpressionPolicyForm";
|
||||
import "@goauthentik/admin/policies/hibp/HaveIBeenPwnedPolicyForm";
|
||||
import "@goauthentik/admin/policies/password/PasswordPolicyForm";
|
||||
import "@goauthentik/admin/policies/reputation/ReputationPolicyForm";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { uiConfig } from "@goauthentik/common/ui/config";
|
||||
import { groupBy } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/forms/ConfirmationForm";
|
||||
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||
import "@goauthentik/elements/forms/ModalForm";
|
||||
import "@goauthentik/elements/forms/ProxyForm";
|
||||
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
||||
import { TableColumn } from "@goauthentik/elements/table/Table";
|
||||
import { TablePage } from "@goauthentik/elements/table/TablePage";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import { PoliciesApi, Policy } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-policy-list")
|
||||
export class PolicyListPage extends TablePage<Policy> {
|
||||
searchEnabled(): boolean {
|
||||
return true;
|
||||
}
|
||||
pageTitle(): string {
|
||||
return t`Policies`;
|
||||
}
|
||||
pageDescription(): string {
|
||||
return t`Allow users to use Applications based on properties, enforce Password Criteria and selectively apply Stages.`;
|
||||
}
|
||||
pageIcon(): string {
|
||||
return "pf-icon pf-icon-infrastructure";
|
||||
}
|
||||
|
||||
checkbox = true;
|
||||
|
||||
@property()
|
||||
order = "name";
|
||||
|
||||
async apiEndpoint(page: number): Promise<PaginatedResponse<Policy>> {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesAllList({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
pageSize: (await uiConfig()).pagination.perPage,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
||||
columns(): TableColumn[] {
|
||||
return [
|
||||
new TableColumn(t`Name`, "name"),
|
||||
new TableColumn(t`Type`),
|
||||
new TableColumn(t`Actions`),
|
||||
];
|
||||
}
|
||||
|
||||
groupBy(items: Policy[]): [string, Policy[]][] {
|
||||
return groupBy(items, (policy) => policy.verboseNamePlural);
|
||||
}
|
||||
|
||||
row(item: Policy): TemplateResult[] {
|
||||
return [
|
||||
html`<div>
|
||||
<div>${item.name}</div>
|
||||
${(item.boundTo || 0) > 0
|
||||
? html`<i class="pf-icon pf-icon-ok"></i>
|
||||
<small>${t`Assigned to ${item.boundTo} object(s).`}</small>`
|
||||
: html`<i class="pf-icon pf-icon-warning-triangle"></i>
|
||||
<small>${t`Warning: Policy is not assigned.`}</small>`}
|
||||
</div>`,
|
||||
html`${item.verboseName}`,
|
||||
html` <ak-forms-modal>
|
||||
<span slot="submit"> ${t`Update`} </span>
|
||||
<span slot="header"> ${t`Update ${item.verboseName}`} </span>
|
||||
<ak-proxy-form
|
||||
slot="form"
|
||||
.args=${{
|
||||
instancePk: item.pk,
|
||||
}}
|
||||
type=${ifDefined(item.component)}
|
||||
>
|
||||
</ak-proxy-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
||||
<i class="fas fa-pencil-alt" aria-hidden="true"></i>
|
||||
</button>
|
||||
</ak-forms-modal>
|
||||
<ak-forms-modal .closeAfterSuccessfulSubmit=${false}>
|
||||
<span slot="submit"> ${t`Test`} </span>
|
||||
<span slot="header"> ${t`Test Policy`} </span>
|
||||
<ak-policy-test-form slot="form" .policy=${item}> </ak-policy-test-form>
|
||||
<button slot="trigger" class="pf-c-button pf-m-plain">
|
||||
<i class="fas fa-vial" aria-hidden="true"></i>
|
||||
</button>
|
||||
</ak-forms-modal>`,
|
||||
];
|
||||
}
|
||||
|
||||
renderToolbarSelected(): TemplateResult {
|
||||
const disabled = this.selectedElements.length < 1;
|
||||
return html`<ak-forms-delete-bulk
|
||||
objectLabel=${t`Policy / Policies`}
|
||||
.objects=${this.selectedElements}
|
||||
.usedBy=${(item: Policy) => {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesAllUsedByList({
|
||||
policyUuid: item.pk,
|
||||
});
|
||||
}}
|
||||
.delete=${(item: Policy) => {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesAllDestroy({
|
||||
policyUuid: item.pk,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
|
||||
${t`Delete`}
|
||||
</button>
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
renderObjectCreate(): TemplateResult {
|
||||
return html`<ak-policy-wizard> </ak-policy-wizard>`;
|
||||
}
|
||||
|
||||
renderToolbar(): TemplateResult {
|
||||
return html` ${super.renderToolbar()}
|
||||
<ak-forms-confirm
|
||||
successMessage=${t`Successfully cleared policy cache`}
|
||||
errorMessage=${t`Failed to delete policy cache`}
|
||||
action=${t`Clear cache`}
|
||||
.onConfirm=${() => {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesAllCacheClearCreate();
|
||||
}}
|
||||
>
|
||||
<span slot="header"> ${t`Clear Policy cache`} </span>
|
||||
<p slot="body">
|
||||
${t`Are you sure you want to clear the policy cache?
|
||||
This will cause all policies to be re-evaluated on their next usage.`}
|
||||
</p>
|
||||
<button slot="trigger" class="pf-c-button pf-m-secondary" type="button">
|
||||
${t`Clear cache`}
|
||||
</button>
|
||||
<div slot="modal"></div>
|
||||
</ak-forms-confirm>`;
|
||||
}
|
||||
}
|
||||
154
web/src/admin/policies/PolicyTestForm.ts
Normal file
154
web/src/admin/policies/PolicyTestForm.ts
Normal file
@ -0,0 +1,154 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import { PFColor } from "@goauthentik/elements/Label";
|
||||
import { Form } from "@goauthentik/elements/forms/Form";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { UserOption } from "@goauthentik/elements/user/utils";
|
||||
import YAML from "yaml";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
import { until } from "lit/directives/until.js";
|
||||
|
||||
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
|
||||
|
||||
import {
|
||||
CoreApi,
|
||||
PoliciesApi,
|
||||
Policy,
|
||||
PolicyTestRequest,
|
||||
PolicyTestResult,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-policy-test-form")
|
||||
export class PolicyTestForm extends Form<PolicyTestRequest> {
|
||||
@property({ attribute: false })
|
||||
policy?: Policy;
|
||||
|
||||
@property({ attribute: false })
|
||||
result?: PolicyTestResult;
|
||||
|
||||
@property({ attribute: false })
|
||||
request?: PolicyTestRequest;
|
||||
|
||||
getSuccessMessage(): string {
|
||||
return t`Successfully sent test-request.`;
|
||||
}
|
||||
|
||||
send = (data: PolicyTestRequest): Promise<PolicyTestResult> => {
|
||||
this.request = data;
|
||||
return new PoliciesApi(DEFAULT_CONFIG)
|
||||
.policiesAllTestCreate({
|
||||
policyUuid: this.policy?.pk || "",
|
||||
policyTestRequest: data,
|
||||
})
|
||||
.then((result) => (this.result = result));
|
||||
};
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return super.styles.concat(PFDescriptionList);
|
||||
}
|
||||
|
||||
renderResult(): TemplateResult {
|
||||
return html`
|
||||
<ak-form-element-horizontal label=${t`Passing`}>
|
||||
<div class="pf-c-form__group-label">
|
||||
<div class="c-form__horizontal-group">
|
||||
<span class="pf-c-form__label-text">
|
||||
<ak-label color=${this.result?.passing ? PFColor.Green : PFColor.Red}>
|
||||
${this.result?.passing ? t`Yes` : t`No`}
|
||||
</ak-label>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Messages`}>
|
||||
<div class="pf-c-form__group-label">
|
||||
<div class="c-form__horizontal-group">
|
||||
<ul>
|
||||
${(this.result?.messages || []).length > 0
|
||||
? this.result?.messages?.map((m) => {
|
||||
return html`<li>
|
||||
<span class="pf-c-form__label-text">${m}</span>
|
||||
</li>`;
|
||||
})
|
||||
: html`<li>
|
||||
<span class="pf-c-form__label-text">-</span>
|
||||
</li>`}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-element-horizontal label=${t`Log messages`}>
|
||||
<div class="pf-c-form__group-label">
|
||||
<div class="c-form__horizontal-group">
|
||||
<dl class="pf-c-description-list pf-m-horizontal">
|
||||
${(this.result?.logMessages || []).length > 0
|
||||
? this.result?.logMessages?.map((m) => {
|
||||
return html`<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${m.log_level}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${m.event}
|
||||
</div>
|
||||
</dd>
|
||||
</div>`;
|
||||
})
|
||||
: html`<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${t`No log messages.`}</span
|
||||
>
|
||||
</dt>
|
||||
</div>`}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
</ak-form-element-horizontal>
|
||||
`;
|
||||
}
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
<ak-form-element-horizontal label=${t`User`} ?required=${true} name="user">
|
||||
<select class="pf-c-form-control">
|
||||
${until(
|
||||
new CoreApi(DEFAULT_CONFIG)
|
||||
.coreUsersList({
|
||||
ordering: "username",
|
||||
})
|
||||
.then((users) => {
|
||||
return users.results.map((user) => {
|
||||
return html`<option
|
||||
?selected=${this.request?.user.toString() ===
|
||||
user.pk.toString()}
|
||||
value=${user.pk}
|
||||
>
|
||||
${UserOption(user)}
|
||||
</option>`;
|
||||
});
|
||||
}),
|
||||
html`<option>${t`Loading...`}</option>`,
|
||||
)}
|
||||
</select>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Context`} name="context">
|
||||
<ak-codemirror mode="yaml" value=${YAML.stringify(first(this.request?.context, {}))}
|
||||
>>
|
||||
</ak-codemirror>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Set custom attributes using YAML or JSON.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
${this.result ? this.renderResult() : html``}
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
107
web/src/admin/policies/PolicyWizard.ts
Normal file
107
web/src/admin/policies/PolicyWizard.ts
Normal file
@ -0,0 +1,107 @@
|
||||
import "@goauthentik/admin/policies/dummy/DummyPolicyForm";
|
||||
import "@goauthentik/admin/policies/event_matcher/EventMatcherPolicyForm";
|
||||
import "@goauthentik/admin/policies/expiry/ExpiryPolicyForm";
|
||||
import "@goauthentik/admin/policies/expression/ExpressionPolicyForm";
|
||||
import "@goauthentik/admin/policies/hibp/HaveIBeenPwnedPolicyForm";
|
||||
import "@goauthentik/admin/policies/password/PasswordPolicyForm";
|
||||
import "@goauthentik/admin/policies/reputation/ReputationPolicyForm";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/forms/ProxyForm";
|
||||
import "@goauthentik/elements/wizard/FormWizardPage";
|
||||
import "@goauthentik/elements/wizard/Wizard";
|
||||
import { WizardPage } from "@goauthentik/elements/wizard/WizardPage";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
|
||||
import AKGlobal from "@goauthentik/common/styles/authentik.css";
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||
import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { PoliciesApi, TypeCreate } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-policy-wizard-initial")
|
||||
export class InitialPolicyWizardPage extends WizardPage {
|
||||
@property({ attribute: false })
|
||||
policyTypes: TypeCreate[] = [];
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFForm, PFButton, AKGlobal, PFRadio];
|
||||
}
|
||||
sidebarLabel = () => t`Select type`;
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
${this.policyTypes.map((type) => {
|
||||
return html`<div class="pf-c-radio">
|
||||
<input
|
||||
class="pf-c-radio__input"
|
||||
type="radio"
|
||||
name="type"
|
||||
id=${`${type.component}-${type.modelName}`}
|
||||
@change=${() => {
|
||||
this.host.steps = [
|
||||
"initial",
|
||||
`type-${type.component}-${type.modelName}`,
|
||||
];
|
||||
this.host.isValid = true;
|
||||
}}
|
||||
/>
|
||||
<label class="pf-c-radio__label" for=${`${type.component}-${type.modelName}`}
|
||||
>${type.name}</label
|
||||
>
|
||||
<span class="pf-c-radio__description">${type.description}</span>
|
||||
</div>`;
|
||||
})}
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ak-policy-wizard")
|
||||
export class PolicyWizard extends AKElement {
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFButton, AKGlobal, PFRadio];
|
||||
}
|
||||
|
||||
@property()
|
||||
createText = t`Create`;
|
||||
|
||||
@property({ attribute: false })
|
||||
policyTypes: TypeCreate[] = [];
|
||||
|
||||
firstUpdated(): void {
|
||||
new PoliciesApi(DEFAULT_CONFIG).policiesAllTypesList().then((types) => {
|
||||
this.policyTypes = types;
|
||||
});
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`
|
||||
<ak-wizard
|
||||
.steps=${["initial"]}
|
||||
header=${t`New policy`}
|
||||
description=${t`Create a new policy.`}
|
||||
>
|
||||
<ak-policy-wizard-initial slot="initial" .policyTypes=${this.policyTypes}>
|
||||
</ak-policy-wizard-initial>
|
||||
${this.policyTypes.map((type) => {
|
||||
return html`
|
||||
<ak-wizard-page-form
|
||||
slot=${`type-${type.component}-${type.modelName}`}
|
||||
.sidebarLabel=${() => t`Create ${type.name}`}
|
||||
>
|
||||
<ak-proxy-form type=${type.component}></ak-proxy-form>
|
||||
</ak-wizard-page-form>
|
||||
`;
|
||||
})}
|
||||
<button slot="trigger" class="pf-c-button pf-m-primary">${this.createText}</button>
|
||||
</ak-wizard>
|
||||
`;
|
||||
}
|
||||
}
|
||||
114
web/src/admin/policies/dummy/DummyPolicyForm.ts
Normal file
114
web/src/admin/policies/dummy/DummyPolicyForm.ts
Normal file
@ -0,0 +1,114 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import { DummyPolicy, PoliciesApi } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-policy-dummy-form")
|
||||
export class DummyPolicyForm extends ModelForm<DummyPolicy, string> {
|
||||
loadInstance(pk: string): Promise<DummyPolicy> {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesDummyRetrieve({
|
||||
policyUuid: pk,
|
||||
});
|
||||
}
|
||||
|
||||
getSuccessMessage(): string {
|
||||
if (this.instance) {
|
||||
return t`Successfully updated policy.`;
|
||||
} else {
|
||||
return t`Successfully created policy.`;
|
||||
}
|
||||
}
|
||||
|
||||
send = (data: DummyPolicy): Promise<DummyPolicy> => {
|
||||
if (this.instance) {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesDummyUpdate({
|
||||
policyUuid: this.instance.pk || "",
|
||||
dummyPolicyRequest: data,
|
||||
});
|
||||
} else {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesDummyCreate({
|
||||
dummyPolicyRequest: data,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
<div class="form-help-text">
|
||||
${t`A policy used for testing. Always returns the same result as specified below after waiting a random duration.`}
|
||||
</div>
|
||||
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.name || "")}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal name="executionLogging">
|
||||
<div class="pf-c-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="pf-c-check__input"
|
||||
?checked=${first(this.instance?.executionLogging, false)}
|
||||
/>
|
||||
<label class="pf-c-check__label"> ${t`Execution logging`} </label>
|
||||
</div>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group .expanded=${true}>
|
||||
<span slot="header"> ${t`Policy-specific settings`} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal name="result">
|
||||
<div class="pf-c-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="pf-c-check__input"
|
||||
?checked=${first(this.instance?.result, false)}
|
||||
/>
|
||||
<label class="pf-c-check__label"> ${t`Pass policy?`} </label>
|
||||
</div>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Wait (min)`}
|
||||
?required=${true}
|
||||
name="waitMin"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
value="${first(this.instance?.waitMin, 1)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`The policy takes a random time to execute. This controls the minimum time it will take.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Wait (max)`}
|
||||
?required=${true}
|
||||
name="waitMax"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
value="${first(this.instance?.waitMax, 5)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
137
web/src/admin/policies/event_matcher/EventMatcherPolicyForm.ts
Normal file
137
web/src/admin/policies/event_matcher/EventMatcherPolicyForm.ts
Normal file
@ -0,0 +1,137 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
import { until } from "lit/directives/until.js";
|
||||
|
||||
import { AdminApi, EventMatcherPolicy, EventsApi, PoliciesApi } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-policy-event-matcher-form")
|
||||
export class EventMatcherPolicyForm extends ModelForm<EventMatcherPolicy, string> {
|
||||
loadInstance(pk: string): Promise<EventMatcherPolicy> {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesEventMatcherRetrieve({
|
||||
policyUuid: pk,
|
||||
});
|
||||
}
|
||||
|
||||
getSuccessMessage(): string {
|
||||
if (this.instance) {
|
||||
return t`Successfully updated policy.`;
|
||||
} else {
|
||||
return t`Successfully created policy.`;
|
||||
}
|
||||
}
|
||||
|
||||
send = (data: EventMatcherPolicy): Promise<EventMatcherPolicy> => {
|
||||
if (this.instance) {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesEventMatcherUpdate({
|
||||
policyUuid: this.instance.pk || "",
|
||||
eventMatcherPolicyRequest: data,
|
||||
});
|
||||
} else {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesEventMatcherCreate({
|
||||
eventMatcherPolicyRequest: data,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
<div class="form-help-text">
|
||||
${t`Matches an event against a set of criteria. If any of the configured values match, the policy passes.`}
|
||||
</div>
|
||||
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.name || "")}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal name="executionLogging">
|
||||
<div class="pf-c-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="pf-c-check__input"
|
||||
?checked=${first(this.instance?.executionLogging, false)}
|
||||
/>
|
||||
<label class="pf-c-check__label"> ${t`Execution logging`} </label>
|
||||
</div>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group .expanded=${true}>
|
||||
<span slot="header"> ${t`Policy-specific settings`} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal label=${t`Action`} name="action">
|
||||
<select class="pf-c-form-control">
|
||||
<option value="" ?selected=${this.instance?.action === undefined}>
|
||||
---------
|
||||
</option>
|
||||
${until(
|
||||
new EventsApi(DEFAULT_CONFIG)
|
||||
.eventsEventsActionsList()
|
||||
.then((actions) => {
|
||||
return actions.map((action) => {
|
||||
return html`<option
|
||||
value=${action.component}
|
||||
?selected=${this.instance?.action ===
|
||||
action.component}
|
||||
>
|
||||
${action.name}
|
||||
</option>`;
|
||||
});
|
||||
}),
|
||||
html`<option>${t`Loading...`}</option>`,
|
||||
)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Match created events with this action type. When left empty, all action types will be matched.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Client IP`} name="clientIp">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.clientIp || "")}"
|
||||
class="pf-c-form-control"
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Matches Event's Client IP (strict matching, for network matching use an Expression Policy.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`App`} name="app">
|
||||
<select class="pf-c-form-control">
|
||||
<option value="" ?selected=${this.instance?.app === undefined}>
|
||||
---------
|
||||
</option>
|
||||
${until(
|
||||
new AdminApi(DEFAULT_CONFIG).adminAppsList().then((apps) => {
|
||||
return apps.map((app) => {
|
||||
return html`<option
|
||||
value=${app.name}
|
||||
?selected=${this.instance?.app === app.name}
|
||||
>
|
||||
${app.label}
|
||||
</option>`;
|
||||
});
|
||||
}),
|
||||
html`<option>${t`Loading...`}</option>`,
|
||||
)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Match events created by selected application. When left empty, all applications are matched.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
101
web/src/admin/policies/expiry/ExpiryPolicyForm.ts
Normal file
101
web/src/admin/policies/expiry/ExpiryPolicyForm.ts
Normal file
@ -0,0 +1,101 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import { PasswordExpiryPolicy, PoliciesApi } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-policy-password-expiry-form")
|
||||
export class PasswordExpiryPolicyForm extends ModelForm<PasswordExpiryPolicy, string> {
|
||||
loadInstance(pk: string): Promise<PasswordExpiryPolicy> {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesPasswordExpiryRetrieve({
|
||||
policyUuid: pk,
|
||||
});
|
||||
}
|
||||
|
||||
getSuccessMessage(): string {
|
||||
if (this.instance) {
|
||||
return t`Successfully updated policy.`;
|
||||
} else {
|
||||
return t`Successfully created policy.`;
|
||||
}
|
||||
}
|
||||
|
||||
send = (data: PasswordExpiryPolicy): Promise<PasswordExpiryPolicy> => {
|
||||
if (this.instance) {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesPasswordExpiryUpdate({
|
||||
policyUuid: this.instance.pk || "",
|
||||
passwordExpiryPolicyRequest: data,
|
||||
});
|
||||
} else {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesPasswordExpiryCreate({
|
||||
passwordExpiryPolicyRequest: data,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
<div class="form-help-text">
|
||||
${t`Checks if the request's user's password has been changed in the last x days, and denys based on settings.`}
|
||||
</div>
|
||||
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.name || "")}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal name="executionLogging">
|
||||
<div class="pf-c-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="pf-c-check__input"
|
||||
?checked=${first(this.instance?.executionLogging, false)}
|
||||
/>
|
||||
<label class="pf-c-check__label"> ${t`Execution logging`} </label>
|
||||
</div>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group .expanded=${true}>
|
||||
<span slot="header"> ${t`Policy-specific settings`} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Maximum age (in days)`}
|
||||
?required=${true}
|
||||
name="days"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
value="${ifDefined(this.instance?.days || "")}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal name="denyOnly">
|
||||
<div class="pf-c-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="pf-c-check__input"
|
||||
?checked=${first(this.instance?.denyOnly, false)}
|
||||
/>
|
||||
<label class="pf-c-check__label">
|
||||
${t`Only fail the policy, don't invalidate user's password.`}
|
||||
</label>
|
||||
</div>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
98
web/src/admin/policies/expression/ExpressionPolicyForm.ts
Normal file
98
web/src/admin/policies/expression/ExpressionPolicyForm.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import { ExpressionPolicy, PoliciesApi } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-policy-expression-form")
|
||||
export class ExpressionPolicyForm extends ModelForm<ExpressionPolicy, string> {
|
||||
loadInstance(pk: string): Promise<ExpressionPolicy> {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesExpressionRetrieve({
|
||||
policyUuid: pk,
|
||||
});
|
||||
}
|
||||
|
||||
getSuccessMessage(): string {
|
||||
if (this.instance) {
|
||||
return t`Successfully updated policy.`;
|
||||
} else {
|
||||
return t`Successfully created policy.`;
|
||||
}
|
||||
}
|
||||
|
||||
send = (data: ExpressionPolicy): Promise<ExpressionPolicy> => {
|
||||
if (this.instance) {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesExpressionUpdate({
|
||||
policyUuid: this.instance.pk || "",
|
||||
expressionPolicyRequest: data,
|
||||
});
|
||||
} else {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesExpressionCreate({
|
||||
expressionPolicyRequest: data,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
<div class="form-help-text">
|
||||
${t`Executes the python snippet to determine whether to allow or deny a request.`}
|
||||
</div>
|
||||
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.name || "")}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal name="executionLogging">
|
||||
<div class="pf-c-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="pf-c-check__input"
|
||||
?checked=${first(this.instance?.executionLogging, false)}
|
||||
/>
|
||||
<label class="pf-c-check__label"> ${t`Execution logging`} </label>
|
||||
</div>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group .expanded=${true}>
|
||||
<span slot="header"> ${t`Policy-specific settings`} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Expression`}
|
||||
?required=${true}
|
||||
name="expression"
|
||||
>
|
||||
<ak-codemirror
|
||||
mode="python"
|
||||
value="${ifDefined(this.instance?.expression)}"
|
||||
>
|
||||
</ak-codemirror>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Expression using Python.`}
|
||||
<a
|
||||
target="_blank"
|
||||
href="https://goauthentik.io/docs/policies/expression?utm_source=authentik"
|
||||
>
|
||||
${t`See documentation for a list of all variables.`}
|
||||
</a>
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
108
web/src/admin/policies/hibp/HaveIBeenPwnedPolicyForm.ts
Normal file
108
web/src/admin/policies/hibp/HaveIBeenPwnedPolicyForm.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import { HaveIBeenPwendPolicy, PoliciesApi } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-policy-hibp-form")
|
||||
export class HaveIBeenPwnedPolicyForm extends ModelForm<HaveIBeenPwendPolicy, string> {
|
||||
loadInstance(pk: string): Promise<HaveIBeenPwendPolicy> {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesHaveibeenpwnedRetrieve({
|
||||
policyUuid: pk,
|
||||
});
|
||||
}
|
||||
|
||||
getSuccessMessage(): string {
|
||||
if (this.instance) {
|
||||
return t`Successfully updated policy.`;
|
||||
} else {
|
||||
return t`Successfully created policy.`;
|
||||
}
|
||||
}
|
||||
|
||||
send = (data: HaveIBeenPwendPolicy): Promise<HaveIBeenPwendPolicy> => {
|
||||
if (this.instance) {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesHaveibeenpwnedUpdate({
|
||||
policyUuid: this.instance.pk || "",
|
||||
haveIBeenPwendPolicyRequest: data,
|
||||
});
|
||||
} else {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesHaveibeenpwnedCreate({
|
||||
haveIBeenPwendPolicyRequest: data,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
<div class="form-help-text">
|
||||
${t`Checks a value from the policy request against the Have I been Pwned API, and denys the request based upon that.
|
||||
Note that only a part of the hash of the password is sent, the full comparison is done clientside.`}
|
||||
</div>
|
||||
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.name || "")}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal name="executionLogging">
|
||||
<div class="pf-c-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="pf-c-check__input"
|
||||
?checked=${first(this.instance?.executionLogging, false)}
|
||||
/>
|
||||
<label class="pf-c-check__label"> ${t`Execution logging`} </label>
|
||||
</div>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group .expanded=${true}>
|
||||
<span slot="header"> ${t`Policy-specific settings`} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Password field`}
|
||||
?required=${true}
|
||||
name="passwordField"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.passwordField || "password")}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Field key to check, field keys defined in Prompt stages are available.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Allowed count`}
|
||||
?required=${true}
|
||||
name="allowedCount"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
value="${first(this.instance?.allowedCount, 0)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Allow up to N occurrences in the HIBP database.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
188
web/src/admin/policies/password/PasswordPolicyForm.ts
Normal file
188
web/src/admin/policies/password/PasswordPolicyForm.ts
Normal file
@ -0,0 +1,188 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import { PasswordPolicy, PoliciesApi } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-policy-password-form")
|
||||
export class PasswordPolicyForm extends ModelForm<PasswordPolicy, string> {
|
||||
loadInstance(pk: string): Promise<PasswordPolicy> {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesPasswordRetrieve({
|
||||
policyUuid: pk,
|
||||
});
|
||||
}
|
||||
|
||||
getSuccessMessage(): string {
|
||||
if (this.instance) {
|
||||
return t`Successfully updated policy.`;
|
||||
} else {
|
||||
return t`Successfully created policy.`;
|
||||
}
|
||||
}
|
||||
|
||||
send = (data: PasswordPolicy): Promise<PasswordPolicy> => {
|
||||
if (this.instance) {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesPasswordUpdate({
|
||||
policyUuid: this.instance.pk || "",
|
||||
passwordPolicyRequest: data,
|
||||
});
|
||||
} else {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesPasswordCreate({
|
||||
passwordPolicyRequest: data,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
<div class="form-help-text">
|
||||
${t`Checks the value from the policy request against several rules, mostly used to ensure password strength.`}
|
||||
</div>
|
||||
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.name || "")}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal name="executionLogging">
|
||||
<div class="pf-c-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="pf-c-check__input"
|
||||
?checked=${first(this.instance?.executionLogging, false)}
|
||||
/>
|
||||
<label class="pf-c-check__label"> ${t`Execution logging`} </label>
|
||||
</div>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group .expanded=${true}>
|
||||
<span slot="header"> ${t`Policy-specific settings`} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Password field`}
|
||||
?required=${true}
|
||||
name="passwordField"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.passwordField || "password")}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Field key to check, field keys defined in Prompt stages are available.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Minimum length`}
|
||||
?required=${true}
|
||||
name="lengthMin"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
value="${first(this.instance?.lengthMin, 10)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Minimum amount of Uppercase Characters`}
|
||||
?required=${true}
|
||||
name="amountUppercase"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
value="${first(this.instance?.amountUppercase, 2)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Minimum amount of Lowercase Characters`}
|
||||
?required=${true}
|
||||
name="amountLowercase"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
value="${first(this.instance?.amountLowercase, 2)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Minimum amount of Digits`}
|
||||
?required=${true}
|
||||
name="amountDigits"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
value="${first(this.instance?.amountDigits, 2)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Minimum amount of Symbols Characters`}
|
||||
?required=${true}
|
||||
name="amountSymbols"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
value="${first(this.instance?.amountSymbols, 2)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Error message`}
|
||||
?required=${true}
|
||||
name="errorMessage"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.errorMessage)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
<ak-form-group>
|
||||
<span slot="header"> ${t`Advanced settings`} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Symbol charset`}
|
||||
?required=${true}
|
||||
name="symbolCharset"
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(
|
||||
this.instance?.symbolCharset ||
|
||||
"!\\\"#$%&'()*+,-./:;<=>?@[]^_`{|}~ ",
|
||||
)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Characters which are considered as symbols.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
90
web/src/admin/policies/reputation/ReputationListPage.ts
Normal file
90
web/src/admin/policies/reputation/ReputationListPage.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { uiConfig } from "@goauthentik/common/ui/config";
|
||||
import "@goauthentik/elements/buttons/ModalButton";
|
||||
import "@goauthentik/elements/buttons/SpinnerButton";
|
||||
import "@goauthentik/elements/forms/DeleteBulkForm";
|
||||
import "@goauthentik/elements/forms/ModalForm";
|
||||
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
||||
import { TableColumn } from "@goauthentik/elements/table/Table";
|
||||
import { TablePage } from "@goauthentik/elements/table/TablePage";
|
||||
import getUnicodeFlagIcon from "country-flag-icons/unicode";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import { PoliciesApi, Reputation } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-policy-reputation-list")
|
||||
export class ReputationListPage extends TablePage<Reputation> {
|
||||
searchEnabled(): boolean {
|
||||
return true;
|
||||
}
|
||||
pageTitle(): string {
|
||||
return t`Reputation scores`;
|
||||
}
|
||||
pageDescription(): string {
|
||||
return t`Reputation for IP and user identifiers. Scores are decreased for each failed login and increased for each successful login.`;
|
||||
}
|
||||
pageIcon(): string {
|
||||
return "fa fa-ban";
|
||||
}
|
||||
|
||||
@property()
|
||||
order = "identifier";
|
||||
|
||||
checkbox = true;
|
||||
|
||||
async apiEndpoint(page: number): Promise<PaginatedResponse<Reputation>> {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationScoresList({
|
||||
ordering: this.order,
|
||||
page: page,
|
||||
pageSize: (await uiConfig()).pagination.perPage,
|
||||
search: this.search || "",
|
||||
});
|
||||
}
|
||||
|
||||
columns(): TableColumn[] {
|
||||
return [
|
||||
new TableColumn(t`Identifier`, "identifier"),
|
||||
new TableColumn(t`IP`, "ip"),
|
||||
new TableColumn(t`Score`, "score"),
|
||||
new TableColumn(t`Updated`, "updated"),
|
||||
];
|
||||
}
|
||||
|
||||
renderToolbarSelected(): TemplateResult {
|
||||
const disabled = this.selectedElements.length < 1;
|
||||
return html`<ak-forms-delete-bulk
|
||||
objectLabel=${t`Reputation`}
|
||||
.objects=${this.selectedElements}
|
||||
.usedBy=${(item: Reputation) => {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationScoresUsedByList({
|
||||
reputationUuid: item.pk || "",
|
||||
});
|
||||
}}
|
||||
.delete=${(item: Reputation) => {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationScoresDestroy({
|
||||
reputationUuid: item.pk || "",
|
||||
});
|
||||
}}
|
||||
>
|
||||
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
|
||||
${t`Delete`}
|
||||
</button>
|
||||
</ak-forms-delete-bulk>`;
|
||||
}
|
||||
|
||||
row(item: Reputation): TemplateResult[] {
|
||||
return [
|
||||
html`${item.identifier}`,
|
||||
html`${item.ipGeoData?.country
|
||||
? html` ${getUnicodeFlagIcon(item.ipGeoData.country)} `
|
||||
: html``}
|
||||
${item.ip}`,
|
||||
html`${item.score}`,
|
||||
html`${item.updated.toLocaleString()}`,
|
||||
];
|
||||
}
|
||||
}
|
||||
118
web/src/admin/policies/reputation/ReputationPolicyForm.ts
Normal file
118
web/src/admin/policies/reputation/ReputationPolicyForm.ts
Normal file
@ -0,0 +1,118 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
|
||||
import { t } from "@lingui/macro";
|
||||
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { customElement } from "lit/decorators.js";
|
||||
import { ifDefined } from "lit/directives/if-defined.js";
|
||||
|
||||
import { PoliciesApi, ReputationPolicy } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-policy-reputation-form")
|
||||
export class ReputationPolicyForm extends ModelForm<ReputationPolicy, string> {
|
||||
loadInstance(pk: string): Promise<ReputationPolicy> {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationRetrieve({
|
||||
policyUuid: pk,
|
||||
});
|
||||
}
|
||||
|
||||
getSuccessMessage(): string {
|
||||
if (this.instance) {
|
||||
return t`Successfully updated policy.`;
|
||||
} else {
|
||||
return t`Successfully created policy.`;
|
||||
}
|
||||
}
|
||||
|
||||
send = (data: ReputationPolicy): Promise<ReputationPolicy> => {
|
||||
if (this.instance) {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationUpdate({
|
||||
policyUuid: this.instance.pk || "",
|
||||
reputationPolicyRequest: data,
|
||||
});
|
||||
} else {
|
||||
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationCreate({
|
||||
reputationPolicyRequest: data,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
renderForm(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
<div class="form-help-text">
|
||||
${t`Allows/denys requests based on the users and/or the IPs reputation.`}
|
||||
</div>
|
||||
<div class="form-help-text">
|
||||
${t`Invalid login attempts will decrease the score for the client's IP, and the
|
||||
username they are attempting to login as, by one.`}
|
||||
</div>
|
||||
<div class="form-help-text">
|
||||
${t`The policy passes when the reputation score is below the threshold, and
|
||||
doesn't pass when either or both of the selected options are equal or above the
|
||||
threshold.`}
|
||||
</div>
|
||||
<ak-form-element-horizontal label=${t`Name`} ?required=${true} name="name">
|
||||
<input
|
||||
type="text"
|
||||
value="${ifDefined(this.instance?.name || "")}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal name="executionLogging">
|
||||
<div class="pf-c-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="pf-c-check__input"
|
||||
?checked=${first(this.instance?.executionLogging, false)}
|
||||
/>
|
||||
<label class="pf-c-check__label"> ${t`Execution logging`} </label>
|
||||
</div>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`When this option is enabled, all executions of this policy will be logged. By default, only execution errors are logged.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-group .expanded=${true}>
|
||||
<span slot="header"> ${t`Policy-specific settings`} </span>
|
||||
<div slot="body" class="pf-c-form">
|
||||
<ak-form-element-horizontal name="checkIp">
|
||||
<div class="pf-c-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="pf-c-check__input"
|
||||
?checked=${first(this.instance?.checkIp, false)}
|
||||
/>
|
||||
<label class="pf-c-check__label"> ${t`Check IP`} </label>
|
||||
</div>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal name="checkUsername">
|
||||
<div class="pf-c-check">
|
||||
<input
|
||||
type="checkbox"
|
||||
class="pf-c-check__input"
|
||||
?checked=${first(this.instance?.checkUsername, false)}
|
||||
/>
|
||||
<label class="pf-c-check__label"> ${t`Check Username`} </label>
|
||||
</div>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal
|
||||
label=${t`Threshold`}
|
||||
?required=${true}
|
||||
name="threshold"
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
value="${ifDefined(this.instance?.threshold || -5)}"
|
||||
class="pf-c-form-control"
|
||||
required
|
||||
/>
|
||||
</ak-form-element-horizontal>
|
||||
</div>
|
||||
</ak-form-group>
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user