core: app entitlements (#12090)

* core: initial app entitlements

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* base off of pbm

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add tests and oauth2

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add to proxy

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* rewrite to use bindings

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make policy bindings form and list more customizable

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* double fix

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* refine permissions

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add missing rbac modal to app entitlements

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* separate scope for app entitlements

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* include entitlements mapping in proxy

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add API validation to prevent policies from being bound to entitlements

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* make preview

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add initial docs

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove duplicate docs

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L.
2024-12-18 14:32:44 +01:00
committed by GitHub
parent 675a4a6788
commit 40a7135c0c
39 changed files with 1246 additions and 94 deletions

View File

@ -1,3 +1,7 @@
import {
PolicyBindingCheckTarget,
PolicyBindingCheckTargetToLabel,
} from "@goauthentik/admin/policies/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { first, groupBy } from "@goauthentik/common/utils";
import "@goauthentik/components/ak-toggle-group";
@ -7,7 +11,7 @@ import "@goauthentik/elements/forms/Radio";
import "@goauthentik/elements/forms/SearchSelect";
import { msg } from "@lit/localize";
import { CSSResult } from "lit";
import { CSSResult, nothing } from "lit";
import { TemplateResult, html } from "lit";
import { customElement, property, state } from "lit/decorators.js";
@ -25,11 +29,7 @@ import {
User,
} from "@goauthentik/api";
enum target {
policy = "policy",
group = "group",
user = "user",
}
export type PolicyBindingNotice = { type: PolicyBindingCheckTarget; notice: string };
@customElement("ak-policy-binding-form")
export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
@ -38,13 +38,13 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
policyBindingUuid: pk,
});
if (binding?.policyObj) {
this.policyGroupUser = target.policy;
this.policyGroupUser = PolicyBindingCheckTarget.policy;
}
if (binding?.groupObj) {
this.policyGroupUser = target.group;
this.policyGroupUser = PolicyBindingCheckTarget.group;
}
if (binding?.userObj) {
this.policyGroupUser = target.user;
this.policyGroupUser = PolicyBindingCheckTarget.user;
}
this.defaultOrder = await this.getOrder();
return binding;
@ -54,10 +54,17 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
targetPk?: string;
@state()
policyGroupUser: target = target.policy;
policyGroupUser: PolicyBindingCheckTarget = PolicyBindingCheckTarget.policy;
@property({ type: Boolean })
policyOnly = false;
@property({ type: Array })
allowedTypes: PolicyBindingCheckTarget[] = [
PolicyBindingCheckTarget.group,
PolicyBindingCheckTarget.user,
PolicyBindingCheckTarget.policy,
];
@property({ type: Array })
typeNotices: PolicyBindingNotice[] = [];
@state()
defaultOrder = 0;
@ -74,20 +81,26 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
return [...super.styles, PFContent];
}
async load(): Promise<void> {
// Overwrite the default for policyGroupUser with the first allowed type,
// as this function is called when the correct parameters are set
this.policyGroupUser = this.allowedTypes[0];
}
send(data: PolicyBinding): Promise<unknown> {
if (this.targetPk) {
data.target = this.targetPk;
}
switch (this.policyGroupUser) {
case target.policy:
case PolicyBindingCheckTarget.policy:
data.user = null;
data.group = null;
break;
case target.group:
case PolicyBindingCheckTarget.group:
data.policy = null;
data.user = null;
break;
case target.user:
case PolicyBindingCheckTarget.user:
data.policy = null;
data.group = null;
break;
@ -122,13 +135,18 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
renderModeSelector(): TemplateResult {
return html` <ak-toggle-group
value=${this.policyGroupUser}
@ak-toggle=${(ev: CustomEvent<{ value: target }>) => {
@ak-toggle=${(ev: CustomEvent<{ value: PolicyBindingCheckTarget }>) => {
this.policyGroupUser = ev.detail.value;
}}
>
<option value=${target.policy}>${msg("Policy")}</option>
<option value=${target.group}>${msg("Group")}</option>
<option value=${target.user}>${msg("User")}</option>
${Object.keys(PolicyBindingCheckTarget).map((ct) => {
if (this.allowedTypes.includes(ct as PolicyBindingCheckTarget)) {
return html`<option value=${ct}>
${PolicyBindingCheckTargetToLabel(ct as PolicyBindingCheckTarget)}
</option>`;
}
return nothing;
})}
</ak-toggle-group>`;
}
@ -139,7 +157,7 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
<ak-form-element-horizontal
label=${msg("Policy")}
name="policy"
?hidden=${this.policyGroupUser !== target.policy}
?hidden=${this.policyGroupUser !== PolicyBindingCheckTarget.policy}
>
<ak-search-select
.groupBy=${(items: Policy[]) => {
@ -169,11 +187,16 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
?blankable=${true}
>
</ak-search-select>
${this.typeNotices
.filter(({ type }) => type === PolicyBindingCheckTarget.policy)
.map((msg) => {
return html`<p class="pf-c-form__helper-text">${msg.notice}</p>`;
})}
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("Group")}
name="group"
?hidden=${this.policyGroupUser !== target.group}
?hidden=${this.policyGroupUser !== PolicyBindingCheckTarget.group}
>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Group[]> => {
@ -201,18 +224,16 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
?blankable=${true}
>
</ak-search-select>
${this.policyOnly
? html`<p class="pf-c-form__helper-text">
${msg(
"Group mappings can only be checked if a user is already logged in when trying to access this source.",
)}
</p>`
: html``}
${this.typeNotices
.filter(({ type }) => type === PolicyBindingCheckTarget.group)
.map((msg) => {
return html`<p class="pf-c-form__helper-text">${msg.notice}</p>`;
})}
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${msg("User")}
name="user"
?hidden=${this.policyGroupUser !== target.user}
?hidden=${this.policyGroupUser !== PolicyBindingCheckTarget.user}
>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<User[]> => {
@ -240,13 +261,11 @@ export class PolicyBindingForm extends ModelForm<PolicyBinding, string> {
?blankable=${true}
>
</ak-search-select>
${this.policyOnly
? html`<p class="pf-c-form__helper-text">
${msg(
"User mappings can only be checked if a user is already logged in when trying to access this source.",
)}
</p>`
: html``}
${this.typeNotices
.filter(({ type }) => type === PolicyBindingCheckTarget.user)
.map((msg) => {
return html`<p class="pf-c-form__helper-text">${msg.notice}</p>`;
})}
</ak-form-element-horizontal>
</div>
</div>