security: cure53 fix (#6039)

* ATH-01-001: resolve path and check start before loading blueprints

This is even less of an issue since 411ef239f6, since with that commit we only allow files that the listing returns

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

* ATH-01-010: fix missing user filter for webauthn device

This prevents an attack that is only possible when an attacker can intercept HTTP traffic and in the case of HTTPS decrypt it.

* ATH-01-008: fix web forms not submitting correctly when pressing enter

When submitting some forms with the Enter key instead of clicking "Confirm"/etc, the form would not get submitted correctly

This would in the worst case is when setting a user's password, where the new password can end up in the URL, but the password was not actually saved to the user.

* ATH-01-004: remove env from admin system endpoint

this endpoint already required admin access, but for debugging the env variables are used very little

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

* ATH-01-003 / ATH-01-012: disable htmlLabels in mermaid

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

* ATH-01-005: use hmac.compare_digest for secret_key authentication

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

* ATH-01-009: migrate impersonation to use API

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

* ATH-01-010: rework

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

* ATH-01-014: save authenticator validation state in flow context

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

bugfixes

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

* ATH-01-012: escape quotation marks

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

* add website

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

* update release ntoes

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

* update with all notes

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

* fix format

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L
2023-06-22 22:25:04 +02:00
committed by GitHub
parent f099bd764e
commit b0fbd576fc
39 changed files with 505 additions and 329 deletions

View File

@ -30,7 +30,7 @@ import PFDrawer from "@patternfly/patternfly/components/Drawer/drawer.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { AdminApi, SessionUser, UiThemeEnum, Version } from "@goauthentik/api";
import { AdminApi, CoreApi, SessionUser, UiThemeEnum, Version } from "@goauthentik/api";
autoDetectLanguage();
@ -178,10 +178,11 @@ export class AdminInterface extends Interface {
${this.user?.original
? html`<ak-sidebar-item
?highlight=${true}
?isAbsoluteLink=${true}
path=${`/-/impersonation/end/?back=${encodeURIComponent(
`${window.location.pathname}#${window.location.hash}`,
)}`}
@click=${() => {
new CoreApi(DEFAULT_CONFIG).coreUsersImpersonateEndRetrieve().then(() => {
window.location.reload();
});
}}
>
<span slot="label"
>${msg(

View File

@ -114,9 +114,12 @@ export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
`;
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${msg("User")} ?required=${true} name="forUser">
renderInlineForm(): TemplateResult {
return html`<ak-form-element-horizontal
label=${msg("User")}
?required=${true}
name="forUser"
>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<User[]> => {
const args: CoreUsersListRequest = {
@ -143,7 +146,6 @@ export class ApplicationCheckAccessForm extends Form<{ forUser: number }> {
>
</ak-search-select>
</ak-form-element-horizontal>
${this.result ? this.renderResult() : html``}
</form>`;
${this.result ? this.renderResult() : html``}`;
}
}

View File

@ -20,9 +20,8 @@ export class CertificateKeyPairForm extends Form<CertificateGenerationRequest> {
});
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
renderInlineForm(): TemplateResult {
return html`<ak-form-element-horizontal
label=${msg("Common Name")}
name="commonName"
?required=${true}
@ -41,7 +40,6 @@ export class CertificateKeyPairForm extends Form<CertificateGenerationRequest> {
?required=${true}
>
<input class="pf-c-form-control" type="number" value="365" />
</ak-form-element-horizontal>
</form>`;
</ak-form-element-horizontal>`;
}
}

View File

@ -86,9 +86,8 @@ export class FlowImportForm extends Form<Flow> {
`;
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${msg("Flow")} name="flow">
renderInlineForm(): TemplateResult {
return html`<ak-form-element-horizontal label=${msg("Flow")} name="flow">
<input type="file" value="" class="pf-c-form-control" />
<p class="pf-c-form__helper-text">
${msg(
@ -96,7 +95,6 @@ export class FlowImportForm extends Form<Flow> {
)}
</p>
</ak-form-element-horizontal>
${this.result ? this.renderResult() : html``}
</form>`;
${this.result ? this.renderResult() : html``}`;
}
}

View File

@ -45,41 +45,39 @@ export class RelatedGroupAdd extends Form<{ groups: string[] }> {
return data;
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${msg("Groups to add")} name="groups">
<div class="pf-c-input-group">
<ak-user-group-select-table
.confirm=${(items: Group[]) => {
this.groupsToAdd = items;
this.requestUpdate();
return Promise.resolve();
}}
>
<button slot="trigger" class="pf-c-button pf-m-control" type="button">
<i class="fas fa-plus" aria-hidden="true"></i>
</button>
</ak-user-group-select-table>
<div class="pf-c-form-control">
<ak-chip-group>
${this.groupsToAdd.map((group) => {
return html`<ak-chip
.removable=${true}
value=${ifDefined(group.pk)}
@remove=${() => {
const idx = this.groupsToAdd.indexOf(group);
this.groupsToAdd.splice(idx, 1);
this.requestUpdate();
}}
>
${group.name}
</ak-chip>`;
})}
</ak-chip-group>
</div>
renderInlineForm(): TemplateResult {
return html`<ak-form-element-horizontal label=${msg("Groups to add")} name="groups">
<div class="pf-c-input-group">
<ak-user-group-select-table
.confirm=${(items: Group[]) => {
this.groupsToAdd = items;
this.requestUpdate();
return Promise.resolve();
}}
>
<button slot="trigger" class="pf-c-button pf-m-control" type="button">
<i class="fas fa-plus" aria-hidden="true"></i>
</button>
</ak-user-group-select-table>
<div class="pf-c-form-control">
<ak-chip-group>
${this.groupsToAdd.map((group) => {
return html`<ak-chip
.removable=${true}
value=${ifDefined(group.pk)}
@remove=${() => {
const idx = this.groupsToAdd.indexOf(group);
this.groupsToAdd.splice(idx, 1);
this.requestUpdate();
}}
>
${group.name}
</ak-chip>`;
})}
</ak-chip-group>
</div>
</ak-form-element-horizontal>
</form> `;
</div>
</ak-form-element-horizontal>`;
}
}

View File

@ -115,9 +115,8 @@ export class PolicyTestForm extends Form<PolicyTestRequest> {
`;
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${msg("User")} ?required=${true} name="user">
renderInlineForm(): TemplateResult {
return html`<ak-form-element-horizontal label=${msg("User")} ?required=${true} name="user">
<ak-search-select
.fetchObjects=${async (query?: string): Promise<User[]> => {
const args: CoreUsersListRequest = {
@ -154,7 +153,6 @@ export class PolicyTestForm extends Form<PolicyTestRequest> {
${msg("Set custom attributes using YAML or JSON.")}
</p>
</ak-form-element-horizontal>
${this.result ? this.renderResult() : html``}
</form>`;
${this.result ? this.renderResult() : html``}`;
}
}

View File

@ -118,9 +118,8 @@ export class PolicyTestForm extends Form<PolicyTestRequest> {
`;
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${msg("User")} ?required=${true} name="user">
renderInlineForm(): TemplateResult {
return html`<ak-form-element-horizontal label=${msg("User")} ?required=${true} name="user">
<ak-search-select
.fetchObjects=${async (query?: string): Promise<User[]> => {
const args: CoreUsersListRequest = {
@ -155,7 +154,6 @@ export class PolicyTestForm extends Form<PolicyTestRequest> {
</ak-codemirror>
<p class="pf-c-form__helper-text">${this.renderExampleButtons()}</p>
</ak-form-element-horizontal>
${this.result ? this.renderResult() : html``}
</form>`;
${this.result ? this.renderResult() : html``}`;
}
}

View File

@ -36,9 +36,8 @@ export class SAMLProviderImportForm extends Form<SAMLProvider> {
});
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name">
renderInlineForm(): TemplateResult {
return html`<ak-form-element-horizontal label=${msg("Name")} ?required=${true} name="name">
<input type="text" class="pf-c-form-control" required />
</ak-form-element-horizontal>
<ak-form-element-horizontal
@ -76,7 +75,6 @@ export class SAMLProviderImportForm extends Form<SAMLProvider> {
<ak-form-element-horizontal label=${msg("Metadata")} name="metadata">
<input type="file" value="" class="pf-c-form-control" />
</ak-form-element-horizontal>
</form>`;
</ak-form-element-horizontal>`;
}
}

View File

@ -58,9 +58,8 @@ export class RelatedUserAdd extends Form<{ users: number[] }> {
return data;
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
${this.group?.isSuperuser ? html`` : html``}
renderInlineForm(): TemplateResult {
return html`${this.group?.isSuperuser ? html`` : html``}
<ak-form-element-horizontal label=${msg("Users to add")} name="users">
<div class="pf-c-input-group">
<ak-group-member-select-table
@ -92,8 +91,7 @@ export class RelatedUserAdd extends Form<{ users: number[] }> {
</ak-chip-group>
</div>
</div>
</ak-form-element-horizontal>
</form> `;
</ak-form-element-horizontal>`;
}
}
@ -194,12 +192,20 @@ export class RelatedUserList extends Table<User> {
</ak-forms-modal>
${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanImpersonate)
? html`
<a
class="pf-c-button pf-m-tertiary"
href="${`/-/impersonation/${item.pk}/`}"
<ak-action-button
class="pf-m-tertiary"
.apiRequest=${() => {
return new CoreApi(DEFAULT_CONFIG)
.coreUsersImpersonateCreate({
id: item.pk,
})
.then(() => {
window.location.href = "/";
});
}}
>
${msg("Impersonate")}
</a>
</ak-action-button>
`
: html``}`,
];

View File

@ -34,9 +34,12 @@ export class ServiceAccountForm extends Form<UserServiceAccountRequest> {
this.result = undefined;
}
renderRequestForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${msg("Username")} ?required=${true} name="name">
renderInlineForm(): TemplateResult {
return html`<ak-form-element-horizontal
label=${msg("Username")}
?required=${true}
name="name"
>
<input type="text" value="" class="pf-c-form-control" required />
<p class="pf-c-form__helper-text">
${msg("User's primary identifier. 150 characters or fewer.")}
@ -81,8 +84,7 @@ export class ServiceAccountForm extends Form<UserServiceAccountRequest> {
value="${dateTimeLocal(new Date(Date.now() + 1000 * 60 ** 2 * 24 * 360))}"
class="pf-c-form-control"
/>
</ak-form-element-horizontal>
</form>`;
</ak-form-element-horizontal>`;
}
renderResponseForm(): TemplateResult {
@ -120,6 +122,6 @@ export class ServiceAccountForm extends Form<UserServiceAccountRequest> {
if (this.result) {
return this.renderResponseForm();
}
return this.renderRequestForm();
return super.renderForm();
}
}

View File

@ -197,12 +197,20 @@ export class UserListPage extends TablePage<User> {
</ak-forms-modal>
${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.CanImpersonate)
? html`
<a
class="pf-c-button pf-m-tertiary"
href="${`/-/impersonation/${item.pk}/`}"
<ak-action-button
class="pf-m-tertiary"
.apiRequest=${() => {
return new CoreApi(DEFAULT_CONFIG)
.coreUsersImpersonateCreate({
id: item.pk,
})
.then(() => {
window.location.href = "/";
});
}}
>
${msg("Impersonate")}
</a>
</ak-action-button>
`
: html``}`,
];

View File

@ -25,11 +25,13 @@ export class UserPasswordForm extends Form<UserPasswordSetRequest> {
});
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal label=${msg("Password")} ?required=${true} name="password">
<input type="password" value="" class="pf-c-form-control" required />
</ak-form-element-horizontal>
</form>`;
renderInlineForm(): TemplateResult {
return html`<ak-form-element-horizontal
label=${msg("Password")}
?required=${true}
name="password"
>
<input type="password" value="" class="pf-c-form-control" required />
</ak-form-element-horizontal>`;
}
}

View File

@ -31,36 +31,34 @@ export class UserResetEmailForm extends Form<CoreUsersRecoveryEmailRetrieveReque
return new CoreApi(DEFAULT_CONFIG).coreUsersRecoveryEmailRetrieve(data);
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${msg("Email stage")}
?required=${true}
name="emailStage"
renderInlineForm(): TemplateResult {
return html`<ak-form-element-horizontal
label=${msg("Email stage")}
?required=${true}
name="emailStage"
>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Stage[]> => {
const args: StagesAllListRequest = {
ordering: "name",
};
if (query !== undefined) {
args.search = query;
}
const stages = await new StagesApi(DEFAULT_CONFIG).stagesEmailList(args);
return stages.results;
}}
.groupBy=${(items: Stage[]) => {
return groupBy(items, (stage) => stage.verboseNamePlural);
}}
.renderElement=${(stage: Stage): string => {
return stage.name;
}}
.value=${(stage: Stage | undefined): string | undefined => {
return stage?.pk;
}}
>
<ak-search-select
.fetchObjects=${async (query?: string): Promise<Stage[]> => {
const args: StagesAllListRequest = {
ordering: "name",
};
if (query !== undefined) {
args.search = query;
}
const stages = await new StagesApi(DEFAULT_CONFIG).stagesEmailList(args);
return stages.results;
}}
.groupBy=${(items: Stage[]) => {
return groupBy(items, (stage) => stage.verboseNamePlural);
}}
.renderElement=${(stage: Stage): string => {
return stage.name;
}}
.value=${(stage: Stage | undefined): string | undefined => {
return stage?.pk;
}}
>
</ak-search-select>
</ak-form-element-horizontal>
</form>`;
</ak-search-select>
</ak-form-element-horizontal>`;
}
}

View File

@ -204,12 +204,20 @@ export class UserViewPage extends AKElement {
)
? html`
<div class="pf-c-card__footer">
<a
class="pf-c-button pf-m-tertiary"
href="${`/-/impersonation/${this.user?.pk}/`}"
<ak-action-button
class="pf-m-tertiary"
.apiRequest=${() => {
return new CoreApi(DEFAULT_CONFIG)
.coreUsersImpersonateCreate({
id: this.user?.pk || 0,
})
.then(() => {
window.location.href = "/";
});
}}
>
${msg("Impersonate")}
</a>
</ak-action-button>
</div>
`
: html``}

View File

@ -46,6 +46,7 @@ export class Diagram extends AKElement {
flowchart: {
curve: "linear",
},
htmlLabels: false,
};
mermaid.initialize(this.config);
}

View File

@ -283,9 +283,23 @@ export abstract class Form<T> extends AKElement {
}
renderForm(): TemplateResult {
const inline = this.renderInlineForm();
if (inline) {
return html`<form class="pf-c-form pf-m-horizontal" @submit=${this.submit}>
${inline}
</form>`;
}
return html`<slot></slot>`;
}
/**
* Inline form render callback when inheriting this class, should be overwritten
* instead of `this.renderForm`
*/
renderInlineForm(): TemplateResult | undefined {
return undefined;
}
renderNonFieldErrors(): TemplateResult {
if (!this.nonFieldErrors) {
return html``;

View File

@ -11,6 +11,7 @@ import { me } from "@goauthentik/common/users";
import { first } from "@goauthentik/common/utils";
import { WebsocketClient } from "@goauthentik/common/ws";
import { Interface } from "@goauthentik/elements/Base";
import "@goauthentik/elements/buttons/ActionButton";
import "@goauthentik/elements/messages/MessageContainer";
import "@goauthentik/elements/notifications/APIDrawer";
import "@goauthentik/elements/notifications/NotificationDrawer";
@ -35,7 +36,7 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
import PFDisplay from "@patternfly/patternfly/utilities/Display/display.css";
import { EventsApi, SessionUser } from "@goauthentik/api";
import { CoreApi, EventsApi, SessionUser } from "@goauthentik/api";
autoDetectLanguage();
@ -233,18 +234,23 @@ export class UserInterface extends Interface {
: html``}
</div>
${this.me.original
? html`<div class="pf-c-page__header-tools">
<div class="pf-c-page__header-tools-group">
<a
class="pf-c-button pf-m-warning pf-m-small"
href=${`/-/impersonation/end/?back=${encodeURIComponent(
`${window.location.pathname}#${window.location.hash}`,
)}`}
>
${msg("Stop impersonation")}
</a>
</div>
</div>`
? html`&nbsp;
<div class="pf-c-page__header-tools">
<div class="pf-c-page__header-tools-group">
<ak-action-button
class="pf-m-warning pf-m-small"
.apiRequest=${() => {
return new CoreApi(DEFAULT_CONFIG)
.coreUsersImpersonateEndRetrieve()
.then(() => {
window.location.reload();
});
}}
>
${msg("Stop impersonation")}
</ak-action-button>
</div>
</div>`
: html``}
<div class="pf-c-page__header-tools-group">
<div class="pf-c-page__header-tools-item pf-m-hidden pf-m-visible-on-md">