Add new capability configs and tests (#2164)

* add checkbox fields, more tests

unskip test

fix beforeEach

add front channel logout configs

set default to true

set default to true

fix console warning and conditionally render url field

show frontchannel settings under logout settings if OIDC client

PR feedback from Jon

* fix clients test

* resolve console warning

* addCapabilityConfigs
This commit is contained in:
Jenny 2022-03-02 16:59:37 -05:00 committed by GitHub
parent b8cc2f5d7b
commit bbc0bcf8b2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 232 additions and 23 deletions

View file

@ -212,6 +212,32 @@ describe("Clients test", () => {
sidebarPage.goToClients(); sidebarPage.goToClients();
}); });
it("Should cancel creating client", () => {
listingPage.goToCreateItem();
createClientPage.continue().checkClientIdRequiredMessage();
createClientPage
.fillClientData("")
.selectClientType("openid-connect")
.cancel();
cy.url().should("not.include", "/add-client");
});
it("Should navigate to previous using 'back' button", () => {
listingPage.goToCreateItem();
createClientPage.continue().checkClientIdRequiredMessage();
createClientPage
.fillClientData("test_client")
.selectClientType("openid-connect")
.continue()
.back()
.checkGeneralSettingsStepActive();
});
it("Should fail creating client", () => { it("Should fail creating client", () => {
listingPage.goToCreateItem(); listingPage.goToCreateItem();
@ -223,7 +249,7 @@ describe("Clients test", () => {
.continue() .continue()
.checkClientIdRequiredMessage(); .checkClientIdRequiredMessage();
createClientPage.fillClientData("account").continue().continue(); createClientPage.fillClientData("account").continue().save();
// The error should inform about duplicated name/id // The error should inform about duplicated name/id
masthead.checkNotificationMessage( masthead.checkNotificationMessage(
@ -241,7 +267,14 @@ describe("Clients test", () => {
.selectClientType("openid-connect") .selectClientType("openid-connect")
.fillClientData(itemId) .fillClientData(itemId)
.continue() .continue()
.continue(); .switchClientAuthentication()
.clickDirectAccess()
.clickImplicitFlow()
.clickOAuthDeviceAuthorizationGrant()
.clickOidcCibaGrant()
.clickServiceAccountRoles()
.clickStandardFlow()
.save();
masthead.checkNotificationMessage("Client created successfully"); masthead.checkNotificationMessage("Client created successfully");
@ -338,7 +371,7 @@ describe("Clients test", () => {
.selectClientType("openid-connect") .selectClientType("openid-connect")
.fillClientData(client) .fillClientData(client)
.continue() .continue()
.continue(); .save();
advancedTab.goToAdvancedTab(); advancedTab.goToAdvancedTab();
}); });

View file

@ -10,15 +10,21 @@ export default class CreateClientPage {
'[for="kc-always-display-in-console-switch"] .pf-c-switch__toggle'; '[for="kc-always-display-in-console-switch"] .pf-c-switch__toggle';
frontchannelLogoutSwitch = frontchannelLogoutSwitch =
'[for="kc-frontchannelLogout-switch"] .pf-c-switch__toggle'; '[for="kc-frontchannelLogout-switch"] .pf-c-switch__toggle';
clientAuthenticationSwitch = '[for="kc-authentication"] .pf-c-switch__toggle'; clientAuthenticationSwitch =
'[for="kc-authentication-switch"] > .pf-c-switch__toggle';
clientAuthorizationSwitch =
'[for="kc-authorization-switch"] > .pf-c-switch__toggle';
standardFlowChkBx = "#kc-flow-standard"; standardFlowChkBx = "#kc-flow-standard";
directAccessChkBx = "#kc-flow-direct"; directAccessChkBx = "#kc-flow-direct";
implicitFlowChkBx = "#kc-flow-implicit"; implicitFlowChkBx = "#kc-flow-implicit";
oidcCibaGrantChkBx = "#kc-oidc-ciba-grant";
deviceAuthGrantChkBx = "#kc-oauth-device-authorization-grant";
serviceAccountRolesChkBx = "#kc-flow-service-account"; serviceAccountRolesChkBx = "#kc-flow-service-account";
continueBtn = ".pf-c-wizard__footer .pf-m-primary"; saveBtn = "save";
backBtn = ".pf-c-wizard__footer .pf-m-secondary"; continueBtn = "next";
cancelBtn = ".pf-c-wizard__footer .pf-m-link"; backBtn = "back";
cancelBtn = "cancel";
//#region General Settings //#region General Settings
selectClientType(clientType: string) { selectClientType(clientType: string) {
@ -72,6 +78,14 @@ export default class CreateClientPage {
return this; return this;
} }
checkGeneralSettingsStepActive() {
cy.get(".pf-c-wizard__nav-link")
.contains("General Settings")
.should("have.class", "pf-m-current");
return this;
}
//#endregion //#endregion
//#region Capability config //#region Capability config
@ -81,6 +95,12 @@ export default class CreateClientPage {
return this; return this;
} }
switchClientAuthorization() {
cy.get(this.clientAuthorizationSwitch).click();
return this;
}
clickStandardFlow() { clickStandardFlow() {
cy.get(this.standardFlowChkBx).click(); cy.get(this.standardFlowChkBx).click();
@ -104,22 +124,40 @@ export default class CreateClientPage {
return this; return this;
} }
clickOAuthDeviceAuthorizationGrant() {
cy.get(this.deviceAuthGrantChkBx).click();
return this;
}
clickOidcCibaGrant() {
cy.get(this.oidcCibaGrantChkBx).click();
return this;
}
//#endregion //#endregion
save() {
cy.findByTestId(this.saveBtn).click();
return this;
}
continue() { continue() {
cy.get(this.continueBtn).click(); cy.findByTestId(this.continueBtn).click();
return this; return this;
} }
back() { back() {
cy.get(this.backBtn).click(); cy.findByTestId(this.backBtn).click();
return this; return this;
} }
cancel() { cancel() {
cy.get(this.cancelBtn).click(); cy.findByTestId(this.cancelBtn).click();
return this; return this;
} }

View file

@ -47,6 +47,7 @@ export const ClientSettings = ({
"attributes.display.on.consent.screen" "attributes.display.on.consent.screen"
); );
const protocol = watch("protocol"); const protocol = watch("protocol");
const frontchannelLogout = watch("frontchannelLogout");
const sections = useMemo(() => { const sections = useMemo(() => {
let result = ["generalSettings"]; let result = ["generalSettings"];
@ -289,6 +290,75 @@ export const ClientSettings = ({
</FormGroup> </FormGroup>
</FormAccess> </FormAccess>
<FormAccess isHorizontal role="manage-clients"> <FormAccess isHorizontal role="manage-clients">
{protocol === "openid-connect" && (
<>
<FormGroup
label={t("frontchannelLogout")}
labelIcon={
<HelpItem
helpText="clients-help:frontchannelLogout"
fieldLabelId="clients:frontchannelLogout"
/>
}
fieldId="frontchannelLogout"
hasNoPaddingTop
>
<Controller
name="frontchannelLogout"
defaultValue={true}
control={control}
render={({ onChange, value }) => (
<Switch
id="frontchannelLogout"
label={t("common:on")}
labelOff={t("common:off")}
isChecked={value.toString() === "true"}
onChange={(value) => onChange(value.toString())}
/>
)}
/>
</FormGroup>
{frontchannelLogout?.toString() === "true" && (
<FormGroup
label={t("frontchannelLogoutUrl")}
fieldId="frontchannelLogoutUrl"
labelIcon={
<HelpItem
helpText="clients-help:frontchannelLogoutUrl"
fieldLabelId="clients:frontchannelLogoutUrl"
/>
}
helperTextInvalid={
errors.attributes?.frontchannel?.logout?.url?.message
}
validated={
errors.attributes?.frontchannel?.logout?.url?.message
? ValidatedOptions.error
: ValidatedOptions.default
}
>
<TextInput
type="text"
id="frontchannelLogoutUrl"
name="attributes.frontchannel.logout.url"
ref={register({
validate: (uri) =>
((uri.startsWith("https://") ||
uri.startsWith("http://")) &&
!uri.includes("*")) ||
uri === "" ||
t("frontchannelUrlInvalid").toString(),
})}
validated={
errors.attributes?.frontchannel?.logout?.url?.message
? ValidatedOptions.error
: ValidatedOptions.default
}
/>
</FormGroup>
)}
</>
)}
<FormGroup <FormGroup
label={t("backchannelLogoutUrl")} label={t("backchannelLogoutUrl")}
fieldId="backchannelLogoutUrl" fieldId="backchannelLogoutUrl"
@ -314,7 +384,7 @@ export const ClientSettings = ({
ref={register({ ref={register({
validate: (uri) => validate: (uri) =>
((uri.startsWith("https://") || uri.startsWith("http://")) && ((uri.startsWith("https://") || uri.startsWith("http://")) &&
uri.indexOf("*") === -1) || !uri.includes("*")) ||
uri === "" || uri === "" ||
t("backchannelUrlInvalid").toString(), t("backchannelUrlInvalid").toString(),
})} })}

View file

@ -69,6 +69,7 @@ export const CapabilityConfig = ({
if (!value) { if (!value) {
setValue("authorizationServicesEnabled", false); setValue("authorizationServicesEnabled", false);
setValue("serviceAccountsEnabled", false); setValue("serviceAccountsEnabled", false);
setValue("attributes.oidc.ciba.grant.enabled", false);
} }
}} }}
/> />
@ -114,7 +115,7 @@ export const CapabilityConfig = ({
label={t("authenticationFlow")} label={t("authenticationFlow")}
fieldId="kc-flow" fieldId="kc-flow"
> >
<Grid> <Grid id="authenticationFlowGrid">
<GridItem lg={4} sm={6}> <GridItem lg={4} sm={6}>
<Controller <Controller
name="standardFlowEnabled" name="standardFlowEnabled"
@ -127,7 +128,7 @@ export const CapabilityConfig = ({
label={t("standardFlow")} label={t("standardFlow")}
id="kc-flow-standard" id="kc-flow-standard"
name="standardFlowEnabled" name="standardFlowEnabled"
isChecked={value} isChecked={value.toString() === "true"}
onChange={onChange} onChange={onChange}
/> />
<HelpItem <HelpItem
@ -164,7 +165,7 @@ export const CapabilityConfig = ({
<GridItem lg={4} sm={6}> <GridItem lg={4} sm={6}>
<Controller <Controller
name="implicitFlowEnabled" name="implicitFlowEnabled"
defaultValue={false} defaultValue={true}
control={control} control={control}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<InputGroup> <InputGroup>
@ -173,7 +174,7 @@ export const CapabilityConfig = ({
label={t("implicitFlow")} label={t("implicitFlow")}
id="kc-flow-implicit" id="kc-flow-implicit"
name="implicitFlowEnabled" name="implicitFlowEnabled"
isChecked={value} isChecked={value.toString() === "true"}
onChange={onChange} onChange={onChange}
/> />
<HelpItem <HelpItem
@ -197,7 +198,8 @@ export const CapabilityConfig = ({
id="kc-flow-service-account" id="kc-flow-service-account"
name="serviceAccountsEnabled" name="serviceAccountsEnabled"
isChecked={ isChecked={
value || (clientAuthentication && authorization) value.toString() === "true" ||
(clientAuthentication && authorization)
} }
onChange={onChange} onChange={onChange}
isDisabled={ isDisabled={
@ -213,6 +215,53 @@ export const CapabilityConfig = ({
)} )}
/> />
</GridItem> </GridItem>
<GridItem lg={8} sm={6}>
<Controller
name="attributes.oauth2.device.authorization.grant.enabled"
defaultValue={false}
control={control}
render={({ onChange, value }) => (
<InputGroup>
<Checkbox
data-testid="oauth-device-authorization-grant"
label={t("oauthDeviceAuthorizationGrant")}
id="kc-oauth-device-authorization-grant"
name="oauth2.device.authorization.grant.enabled"
isChecked={value.toString() === "true"}
onChange={onChange}
/>
<HelpItem
helpText="clients-help:oauthDeviceAuthorizationGrant"
fieldLabelId="clients:oauthDeviceAuthorizationGrant"
/>
</InputGroup>
)}
/>
</GridItem>
<GridItem lg={8} sm={6}>
<Controller
name="attributes.oidc.ciba.grant.enabled"
defaultValue={false}
control={control}
render={({ onChange, value }) => (
<InputGroup>
<Checkbox
data-testid="oidc-ciba-grant"
label={t("oidcCibaGrant")}
id="kc-oidc-ciba-grant"
name="oidc.ciba.grant.enabled"
isChecked={value.toString() === "true"}
onChange={onChange}
isDisabled={clientAuthentication}
/>
<HelpItem
helpText="clients-help:oidcCibaGrant"
fieldLabelId="clients:oidcCibaGrant"
/>
</InputGroup>
)}
/>
</GridItem>
</Grid> </Grid>
</FormGroup> </FormGroup>
</> </>
@ -233,15 +282,15 @@ export const CapabilityConfig = ({
<Controller <Controller
name="attributes.saml.encrypt" name="attributes.saml.encrypt"
control={control} control={control}
defaultValue="false" defaultValue={false}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Switch <Switch
data-testid="encrypt" data-testid="encrypt"
id="kc-encrypt" id="kc-encrypt"
label={t("common:on")} label={t("common:on")}
labelOff={t("common:off")} labelOff={t("common:off")}
isChecked={value === "true"} isChecked={value}
onChange={(value) => onChange("" + value)} onChange={onChange}
/> />
)} )}
/> />
@ -260,15 +309,15 @@ export const CapabilityConfig = ({
<Controller <Controller
name="attributes.saml.client.signature" name="attributes.saml.client.signature"
control={control} control={control}
defaultValue="false" defaultValue={false}
render={({ onChange, value }) => ( render={({ onChange, value }) => (
<Switch <Switch
data-testid="client-signature" data-testid="client-signature"
id="kc-client-signature" id="kc-client-signature"
label={t("common:on")} label={t("common:on")}
labelOff={t("common:off")} labelOff={t("common:off")}
isChecked={value === "true"} isChecked={value}
onChange={(value) => onChange("" + value)} onChange={onChange}
/> />
)} )}
/> />

View file

@ -48,6 +48,7 @@ export default function NewClientForm() {
const newClient = await adminClient.clients.create({ const newClient = await adminClient.clients.create({
...convertFormValuesToObject(client), ...convertFormValuesToObject(client),
clientId: client.clientId?.trim(), clientId: client.clientId?.trim(),
frontchannelLogout: true,
}); });
addAlert(t("createSuccess"), AlertVariant.success); addAlert(t("createSuccess"), AlertVariant.success);
history.push( history.push(
@ -93,6 +94,9 @@ export default function NewClientForm() {
<> <>
<Button <Button
variant="primary" variant="primary"
data-testid={
activeStep.name === t("capabilityConfig") ? "save" : "next"
}
type="submit" type="submit"
onClick={() => { onClick={() => {
forward(onNext); forward(onNext);
@ -104,6 +108,7 @@ export default function NewClientForm() {
</Button> </Button>
<Button <Button
variant="secondary" variant="secondary"
data-testid="back"
onClick={() => { onClick={() => {
back(); back();
onBack(); onBack();
@ -112,7 +117,7 @@ export default function NewClientForm() {
> >
{t("common:back")} {t("common:back")}
</Button> </Button>
<Button variant="link" onClick={onClose}> <Button data-testid="cancel" variant="link" onClick={onClose}>
{t("common:cancel")} {t("common:cancel")}
</Button> </Button>
</> </>

View file

@ -3,3 +3,7 @@
--pf-c-form--m-horizontal__group-label--md--GridColumnWidth: 10rem; --pf-c-form--m-horizontal__group-label--md--GridColumnWidth: 10rem;
} }
} }
div#authenticationFlowGrid > .pf-l-grid__item {
padding-bottom: var(--pf-global--spacer--lg);
}

View file

@ -18,6 +18,10 @@ export default {
"This enables standard OpenID Connect redirect based authentication with authorization code. In terms of OpenID Connect or OAuth2 specifications, this enables support of 'Authorization Code Flow' for this client.", "This enables standard OpenID Connect redirect based authentication with authorization code. In terms of OpenID Connect or OAuth2 specifications, this enables support of 'Authorization Code Flow' for this client.",
implicitFlow: implicitFlow:
"This enables support for OpenID Connect redirect based authentication without authorization code. In terms of OpenID Connect or OAuth2 specifications, this enables support of 'Implicit Flow' for this client.", "This enables support for OpenID Connect redirect based authentication without authorization code. In terms of OpenID Connect or OAuth2 specifications, this enables support of 'Implicit Flow' for this client.",
oauthDeviceAuthorizationGrant:
"This enables support for OAuth 2.0 Device Authorization Grant, which means that client is an application on device that has limited input capabilities or lack a suitable browser.",
oidcCibaGrant:
"This enables support for OIDC CIBA Grant, which means that the user is authenticated via some external authentication device instead of the user's browser.",
rootURL: "Root URL appended to relative URLs", rootURL: "Root URL appended to relative URLs",
validRedirectURIs: validRedirectURIs:
"Valid URI pattern a browser can redirect to after a successful login or logout. Simple wildcards are allowed such as 'http://example.com/*'. Relative path can be specified too such as /my/relative/path/*. Relative paths are relative to the client root URL, or if none is specified the auth server root URL is used. For SAML, you must set valid URI patterns if you are relying on the consumer service URL embedded with the login request.", "Valid URI pattern a browser can redirect to after a successful login or logout. Simple wildcards are allowed such as 'http://example.com/*'. Relative path can be specified too such as /my/relative/path/*. Relative paths are relative to the client root URL, or if none is specified the auth server root URL is used. For SAML, you must set valid URI patterns if you are relying on the consumer service URL embedded with the login request.",
@ -173,6 +177,8 @@ export default {
"URL to send the HTTP ARTIFACT messages to. You can leave this blank if you are using a different binding. This value should be set when forcing ARTIFACT binding together with IdP initiated login.", "URL to send the HTTP ARTIFACT messages to. You can leave this blank if you are using a different binding. This value should be set when forcing ARTIFACT binding together with IdP initiated login.",
frontchannelLogout: frontchannelLogout:
"When true, logout requires a browser redirect to client. When false, server performs a background invocation for logout.", "When true, logout requires a browser redirect to client. When false, server performs a background invocation for logout.",
frontchannelLogoutUrl:
"URL that will cause the client to log itself out when a logout request is sent to this realm (via end_session_endpoint). If not provided, it defaults to the base url.",
backchannelLogoutUrl: backchannelLogoutUrl:
"URL that will cause the client to log itself out when a logout request is sent to this realm (via end_session_endpoint). If omitted, no logout request will be sent to the client is this case.", "URL that will cause the client to log itself out when a logout request is sent to this realm (via end_session_endpoint). If omitted, no logout request will be sent to the client is this case.",
backchannelLogoutSessionRequired: backchannelLogoutSessionRequired:

View file

@ -348,6 +348,8 @@ export default {
standardFlow: "Standard flow", standardFlow: "Standard flow",
directAccess: "Direct access grants", directAccess: "Direct access grants",
serviceAccount: "Service accounts roles", serviceAccount: "Service accounts roles",
oauthDeviceAuthorizationGrant: "OAuth 2.0 Device Authorization Grant",
oidcCibaGrant: "OIDC CIBA Grant",
enableServiceAccount: "Enable service account roles", enableServiceAccount: "Enable service account roles",
assignRolesTo: "Assign roles to {{client}} account", assignRolesTo: "Assign roles to {{client}} account",
searchByRoleName: "Search by role name", searchByRoleName: "Search by role name",
@ -367,6 +369,8 @@ export default {
backchannelLogoutRevokeOfflineSessions: backchannelLogoutRevokeOfflineSessions:
"Backchannel logout revoke offline sessions", "Backchannel logout revoke offline sessions",
frontchannelLogout: "Front channel logout", frontchannelLogout: "Front channel logout",
frontchannelLogoutUrl: "Front-channel logout URL",
frontchannelUrlInvalid: "Front-channel logout URL is not a valid URL",
accessSettings: "Access settings", accessSettings: "Access settings",
rootUrl: "Root URL", rootUrl: "Root URL",
validRedirectUri: "Valid redirect URIs", validRedirectUri: "Valid redirect URIs",