security: fix CVE 2024 52289 (#12113)
* initial migration Signed-off-by: Jens Langhammer <jens@goauthentik.io> * migrate tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix loading Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix Signed-off-by: Jens Langhammer <jens@goauthentik.io> * start dynamic ui Signed-off-by: Jens Langhammer <jens@goauthentik.io> * initial ui Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add serialize Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add error message handling Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix/add tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> * prepare docs Signed-off-by: Jens Langhammer <jens@goauthentik.io> * migrate to new input Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
		@ -1,11 +1,16 @@
 | 
			
		||||
import "@goauthentik/admin/common/ak-crypto-certificate-search";
 | 
			
		||||
import "@goauthentik/admin/common/ak-flow-search/ak-flow-search";
 | 
			
		||||
import { BaseProviderForm } from "@goauthentik/admin/providers/BaseProviderForm";
 | 
			
		||||
import {
 | 
			
		||||
    IRedirectURIInput,
 | 
			
		||||
    akOAuthRedirectURIInput,
 | 
			
		||||
} from "@goauthentik/admin/providers/oauth2/OAuth2ProviderRedirectURI";
 | 
			
		||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
 | 
			
		||||
import { ascii_letters, digits, first, randomString } from "@goauthentik/common/utils";
 | 
			
		||||
import "@goauthentik/components/ak-radio-input";
 | 
			
		||||
import "@goauthentik/components/ak-text-input";
 | 
			
		||||
import "@goauthentik/components/ak-textarea-input";
 | 
			
		||||
import "@goauthentik/elements/ak-array-input.js";
 | 
			
		||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-dynamic-selected-provider.js";
 | 
			
		||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider.js";
 | 
			
		||||
import "@goauthentik/elements/forms/FormGroup";
 | 
			
		||||
@ -15,7 +20,7 @@ import "@goauthentik/elements/forms/SearchSelect";
 | 
			
		||||
import "@goauthentik/elements/utils/TimeDeltaHelp";
 | 
			
		||||
 | 
			
		||||
import { msg } from "@lit/localize";
 | 
			
		||||
import { TemplateResult, html } from "lit";
 | 
			
		||||
import { TemplateResult, css, html } from "lit";
 | 
			
		||||
import { customElement, state } from "lit/decorators.js";
 | 
			
		||||
import { ifDefined } from "lit/directives/if-defined.js";
 | 
			
		||||
 | 
			
		||||
@ -23,8 +28,10 @@ import {
 | 
			
		||||
    ClientTypeEnum,
 | 
			
		||||
    FlowsInstancesListDesignationEnum,
 | 
			
		||||
    IssuerModeEnum,
 | 
			
		||||
    MatchingModeEnum,
 | 
			
		||||
    OAuth2Provider,
 | 
			
		||||
    ProvidersApi,
 | 
			
		||||
    RedirectURI,
 | 
			
		||||
    SubModeEnum,
 | 
			
		||||
} from "@goauthentik/api";
 | 
			
		||||
 | 
			
		||||
@ -98,13 +105,13 @@ export const issuerModeOptions = [
 | 
			
		||||
 | 
			
		||||
const redirectUriHelpMessages = [
 | 
			
		||||
    msg(
 | 
			
		||||
        "Valid redirect URLs after a successful authorization flow. Also specify any origins here for Implicit flows.",
 | 
			
		||||
        "Valid redirect URIs after a successful authorization flow. Also specify any origins here for Implicit flows.",
 | 
			
		||||
    ),
 | 
			
		||||
    msg(
 | 
			
		||||
        "If no explicit redirect URIs are specified, the first successfully used redirect URI will be saved.",
 | 
			
		||||
    ),
 | 
			
		||||
    msg(
 | 
			
		||||
        'To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have.',
 | 
			
		||||
        'To allow any redirect URI, set the mode to Regex and the value to ".*". Be aware of the possible security implications this can have.',
 | 
			
		||||
    ),
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@ -124,11 +131,23 @@ export class OAuth2ProviderFormPage extends BaseProviderForm<OAuth2Provider> {
 | 
			
		||||
    @state()
 | 
			
		||||
    showClientSecret = true;
 | 
			
		||||
 | 
			
		||||
    @state()
 | 
			
		||||
    redirectUris: RedirectURI[] = [];
 | 
			
		||||
 | 
			
		||||
    static get styles() {
 | 
			
		||||
        return super.styles.concat(css`
 | 
			
		||||
            ak-array-input {
 | 
			
		||||
                width: 100%;
 | 
			
		||||
            }
 | 
			
		||||
        `);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async loadInstance(pk: number): Promise<OAuth2Provider> {
 | 
			
		||||
        const provider = await new ProvidersApi(DEFAULT_CONFIG).providersOauth2Retrieve({
 | 
			
		||||
            id: pk,
 | 
			
		||||
        });
 | 
			
		||||
        this.showClientSecret = provider.clientType === ClientTypeEnum.Confidential;
 | 
			
		||||
        this.redirectUris = provider.redirectUris;
 | 
			
		||||
        return provider;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -203,13 +222,23 @@ export class OAuth2ProviderFormPage extends BaseProviderForm<OAuth2Provider> {
 | 
			
		||||
                        ?hidden=${!this.showClientSecret}
 | 
			
		||||
                    >
 | 
			
		||||
                    </ak-text-input>
 | 
			
		||||
                    <ak-textarea-input
 | 
			
		||||
                    <ak-form-element-horizontal
 | 
			
		||||
                        label=${msg("Redirect URIs/Origins")}
 | 
			
		||||
                        required
 | 
			
		||||
                        name="redirectUris"
 | 
			
		||||
                        label=${msg("Redirect URIs/Origins (RegEx)")}
 | 
			
		||||
                        .value=${provider?.redirectUris}
 | 
			
		||||
                        .bighelp=${redirectUriHelp}
 | 
			
		||||
                    >
 | 
			
		||||
                    </ak-textarea-input>
 | 
			
		||||
                        <ak-array-input
 | 
			
		||||
                            .items=${this.instance?.redirectUris ?? []}
 | 
			
		||||
                            .newItem=${() => ({ matchingMode: MatchingModeEnum.Strict, url: "" })}
 | 
			
		||||
                            .row=${(f?: RedirectURI) =>
 | 
			
		||||
                                akOAuthRedirectURIInput({
 | 
			
		||||
                                    ".redirectURI": f,
 | 
			
		||||
                                    "style": "width: 100%",
 | 
			
		||||
                                } as unknown as IRedirectURIInput)}
 | 
			
		||||
                        >
 | 
			
		||||
                        </ak-array-input>
 | 
			
		||||
                        ${redirectUriHelp}
 | 
			
		||||
                    </ak-form-element-horizontal>
 | 
			
		||||
 | 
			
		||||
                    <ak-form-element-horizontal label=${msg("Signing Key")} name="signingKey">
 | 
			
		||||
                        <!-- NOTE: 'null' cast to 'undefined' on signingKey to satisfy Lit requirements -->
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										104
									
								
								web/src/admin/providers/oauth2/OAuth2ProviderRedirectURI.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								web/src/admin/providers/oauth2/OAuth2ProviderRedirectURI.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,104 @@
 | 
			
		||||
import "@goauthentik/admin/providers/oauth2/OAuth2ProviderRedirectURI";
 | 
			
		||||
import { AkControlElement } from "@goauthentik/elements/AkControlElement.js";
 | 
			
		||||
import { type Spread } from "@goauthentik/elements/types";
 | 
			
		||||
import { spread } from "@open-wc/lit-helpers";
 | 
			
		||||
 | 
			
		||||
import { msg } from "@lit/localize";
 | 
			
		||||
import { css, html } from "lit";
 | 
			
		||||
import { customElement, property, queryAll } from "lit/decorators.js";
 | 
			
		||||
import { ifDefined } from "lit/directives/if-defined.js";
 | 
			
		||||
 | 
			
		||||
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
 | 
			
		||||
import PFInputGroup from "@patternfly/patternfly/components/InputGroup/input-group.css";
 | 
			
		||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
 | 
			
		||||
 | 
			
		||||
import { MatchingModeEnum, RedirectURI } from "@goauthentik/api";
 | 
			
		||||
 | 
			
		||||
export interface IRedirectURIInput {
 | 
			
		||||
    redirectURI: RedirectURI;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@customElement("ak-provider-oauth2-redirect-uri")
 | 
			
		||||
export class OAuth2ProviderRedirectURI extends AkControlElement<RedirectURI> {
 | 
			
		||||
    static get styles() {
 | 
			
		||||
        return [
 | 
			
		||||
            PFBase,
 | 
			
		||||
            PFInputGroup,
 | 
			
		||||
            PFFormControl,
 | 
			
		||||
            css`
 | 
			
		||||
                .pf-c-input-group select {
 | 
			
		||||
                    width: 10em;
 | 
			
		||||
                }
 | 
			
		||||
            `,
 | 
			
		||||
        ];
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @property({ type: Object, attribute: false })
 | 
			
		||||
    redirectURI: RedirectURI = {
 | 
			
		||||
        matchingMode: MatchingModeEnum.Strict,
 | 
			
		||||
        url: "",
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    @queryAll(".ak-form-control")
 | 
			
		||||
    controls?: HTMLInputElement[];
 | 
			
		||||
 | 
			
		||||
    json() {
 | 
			
		||||
        return Object.fromEntries(
 | 
			
		||||
            Array.from(this.controls ?? []).map((control) => [control.name, control.value]),
 | 
			
		||||
        ) as unknown as RedirectURI;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    get isValid() {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    render() {
 | 
			
		||||
        const onChange = () => {
 | 
			
		||||
            this.dispatchEvent(new Event("change", { composed: true, bubbles: true }));
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return html`<div class="pf-c-input-group">
 | 
			
		||||
            <select
 | 
			
		||||
                name="matchingMode"
 | 
			
		||||
                class="pf-c-form-control ak-form-control"
 | 
			
		||||
                @change=${onChange}
 | 
			
		||||
            >
 | 
			
		||||
                <option
 | 
			
		||||
                    value="${MatchingModeEnum.Strict}"
 | 
			
		||||
                    ?selected=${this.redirectURI.matchingMode === MatchingModeEnum.Strict}
 | 
			
		||||
                >
 | 
			
		||||
                    ${msg("Strict")}
 | 
			
		||||
                </option>
 | 
			
		||||
                <option
 | 
			
		||||
                    value="${MatchingModeEnum.Regex}"
 | 
			
		||||
                    ?selected=${this.redirectURI.matchingMode === MatchingModeEnum.Regex}
 | 
			
		||||
                >
 | 
			
		||||
                    ${msg("Regex")}
 | 
			
		||||
                </option>
 | 
			
		||||
            </select>
 | 
			
		||||
            <input
 | 
			
		||||
                type="text"
 | 
			
		||||
                @change=${onChange}
 | 
			
		||||
                value="${ifDefined(this.redirectURI.url ?? undefined)}"
 | 
			
		||||
                class="pf-c-form-control ak-form-control"
 | 
			
		||||
                required
 | 
			
		||||
                id="url"
 | 
			
		||||
                placeholder=${msg("URL")}
 | 
			
		||||
                name="href"
 | 
			
		||||
                tabindex="1"
 | 
			
		||||
            />
 | 
			
		||||
        </div>`;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function akOAuthRedirectURIInput(properties: IRedirectURIInput) {
 | 
			
		||||
    return html`<ak-provider-oauth2-redirect-uri
 | 
			
		||||
        ${spread(properties as unknown as Spread)}
 | 
			
		||||
    ></ak-provider-oauth2-redirect-uri>`;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare global {
 | 
			
		||||
    interface HTMLElementTagNameMap {
 | 
			
		||||
        "ak-provider-oauth2-redirect-uri": OAuth2ProviderRedirectURI;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -2,7 +2,7 @@ import { convertToSlug } from "@goauthentik/common/utils";
 | 
			
		||||
import { AKElement } from "@goauthentik/elements/Base";
 | 
			
		||||
import { FormGroup } from "@goauthentik/elements/forms/FormGroup";
 | 
			
		||||
 | 
			
		||||
import { msg } from "@lit/localize";
 | 
			
		||||
import { msg, str } from "@lit/localize";
 | 
			
		||||
import { CSSResult, css } from "lit";
 | 
			
		||||
import { TemplateResult, html } from "lit";
 | 
			
		||||
import { customElement, property } from "lit/decorators.js";
 | 
			
		||||
@ -33,7 +33,7 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
 | 
			
		||||
 *    where the field isn't available for the user to view unless they explicitly request to be able
 | 
			
		||||
 *    to see the content; otherwise, a dead password field is shown. There are 10 uses of this
 | 
			
		||||
 *    feature.
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
const isAkControl = (el: unknown): boolean =>
 | 
			
		||||
@ -86,7 +86,7 @@ export class HorizontalFormElement extends AKElement {
 | 
			
		||||
    writeOnlyActivated = false;
 | 
			
		||||
 | 
			
		||||
    @property({ attribute: false })
 | 
			
		||||
    errorMessages: string[] = [];
 | 
			
		||||
    errorMessages: string[] | string[][] = [];
 | 
			
		||||
 | 
			
		||||
    @property({ type: Boolean })
 | 
			
		||||
    slugMode = false;
 | 
			
		||||
@ -183,6 +183,16 @@ export class HorizontalFormElement extends AKElement {
 | 
			
		||||
                          </p>`
 | 
			
		||||
                        : html``}
 | 
			
		||||
                    ${this.errorMessages.map((message) => {
 | 
			
		||||
                        if (message instanceof Object) {
 | 
			
		||||
                            return html`${Object.entries(message).map(([field, errMsg]) => {
 | 
			
		||||
                                return html`<p
 | 
			
		||||
                                    class="pf-c-form__helper-text pf-m-error"
 | 
			
		||||
                                    aria-live="polite"
 | 
			
		||||
                                >
 | 
			
		||||
                                    ${msg(str`${field}: ${errMsg}`)}
 | 
			
		||||
                                </p>`;
 | 
			
		||||
                            })}`;
 | 
			
		||||
                        }
 | 
			
		||||
                        return html`<p class="pf-c-form__helper-text pf-m-error" aria-live="polite">
 | 
			
		||||
                            ${message}
 | 
			
		||||
                        </p>`;
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user