Add dedicated feature flag for oauth device grant flow (#23892)
Closes #23891
This commit is contained in:
parent
e4632c9e78
commit
e567210ed1
26 changed files with 337 additions and 192 deletions
|
@ -92,7 +92,10 @@ public class Profile {
|
|||
|
||||
DPOP("OAuth 2.0 Demonstrating Proof-of-Possession at the Application Layer", Type.PREVIEW),
|
||||
|
||||
LINKEDIN_OAUTH("LinkedIn Social Identity Provider based on OAuth", Type.DEPRECATED);
|
||||
LINKEDIN_OAUTH("LinkedIn Social Identity Provider based on OAuth", Type.DEPRECATED),
|
||||
|
||||
DEVICE_FLOW("OAuth 2.0 Device Authorization Grant", Type.DEFAULT),
|
||||
;
|
||||
|
||||
private final Type type;
|
||||
private final String label;
|
||||
|
|
|
@ -14,6 +14,7 @@ import { FormAccess } from "../../components/form/FormAccess";
|
|||
import { HelpItem } from "ui-shared";
|
||||
import { convertAttributeNameToForm } from "../../util";
|
||||
import { FormFields } from "../ClientDetails";
|
||||
import useIsFeatureEnabled, { Feature } from "../../utils/useIsFeatureEnabled";
|
||||
|
||||
type CapabilityConfigProps = {
|
||||
unWrap?: boolean;
|
||||
|
@ -29,6 +30,7 @@ export const CapabilityConfig = ({
|
|||
const protocol = type || watch("protocol");
|
||||
const clientAuthentication = watch("publicClient");
|
||||
const authorization = watch("authorizationServicesEnabled");
|
||||
const isFeatureEnabled = useIsFeatureEnabled();
|
||||
|
||||
return (
|
||||
<FormAccess
|
||||
|
@ -215,31 +217,33 @@ export const CapabilityConfig = ({
|
|||
)}
|
||||
/>
|
||||
</GridItem>
|
||||
<GridItem lg={8} sm={6}>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm<
|
||||
Required<ClientRepresentation["attributes"]>
|
||||
>("attributes.oauth2.device.authorization.grant.enabled")}
|
||||
defaultValue={false}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<InputGroup>
|
||||
<Checkbox
|
||||
data-testid="oauth-device-authorization-grant"
|
||||
label={t("oauthDeviceAuthorizationGrant")}
|
||||
id="kc-oauth-device-authorization-grant"
|
||||
name="oauth2.device.authorization.grant.enabled"
|
||||
isChecked={field.value.toString() === "true"}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
<HelpItem
|
||||
helpText={t("oauthDeviceAuthorizationGrantHelp")}
|
||||
fieldLabelId="oauthDeviceAuthorizationGrant"
|
||||
/>
|
||||
</InputGroup>
|
||||
)}
|
||||
/>
|
||||
</GridItem>
|
||||
{isFeatureEnabled(Feature.DeviceFlow) && (
|
||||
<GridItem lg={8} sm={6}>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm<
|
||||
Required<ClientRepresentation["attributes"]>
|
||||
>("attributes.oauth2.device.authorization.grant.enabled")}
|
||||
defaultValue={false}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<InputGroup>
|
||||
<Checkbox
|
||||
data-testid="oauth-device-authorization-grant"
|
||||
label={t("oauthDeviceAuthorizationGrant")}
|
||||
id="kc-oauth-device-authorization-grant"
|
||||
name="oauth2.device.authorization.grant.enabled"
|
||||
isChecked={field.value.toString() === "true"}
|
||||
onChange={field.onChange}
|
||||
/>
|
||||
<HelpItem
|
||||
helpText={t("oauthDeviceAuthorizationGrantHelp")}
|
||||
fieldLabelId="oauthDeviceAuthorizationGrant"
|
||||
/>
|
||||
</InputGroup>
|
||||
)}
|
||||
/>
|
||||
</GridItem>
|
||||
)}
|
||||
<GridItem lg={8} sm={6}>
|
||||
<Controller
|
||||
name={convertAttributeNameToForm<FormFields>(
|
||||
|
|
|
@ -27,6 +27,7 @@ import {
|
|||
import { useServerInfo } from "../context/server-info/ServerInfoProvider";
|
||||
import { useWhoAmI } from "../context/whoami/WhoAmI";
|
||||
import { convertToFormValues, sortProviders } from "../util";
|
||||
import useIsFeatureEnabled, { Feature } from "../utils/useIsFeatureEnabled";
|
||||
|
||||
import "./realm-settings-section.css";
|
||||
|
||||
|
@ -43,6 +44,7 @@ export const RealmSettingsTokensTab = ({
|
|||
}: RealmSettingsSessionsTabProps) => {
|
||||
const { t } = useTranslation();
|
||||
const serverInfo = useServerInfo();
|
||||
const isFeatureEnabled = useIsFeatureEnabled();
|
||||
const { whoAmI } = useWhoAmI();
|
||||
|
||||
const [defaultSigAlgDrpdwnIsOpen, setDefaultSigAlgDrpdwnOpen] =
|
||||
|
@ -127,77 +129,81 @@ export const RealmSettingsTokensTab = ({
|
|||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
label={t("oAuthDeviceCodeLifespan")}
|
||||
fieldId="oAuthDeviceCodeLifespan"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={t("oAuthDeviceCodeLifespanHelp")}
|
||||
fieldLabelId="oAuthDeviceCodeLifespan"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Controller
|
||||
name="oauth2DeviceCodeLifespan"
|
||||
defaultValue={0}
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<TimeSelector
|
||||
id="oAuthDeviceCodeLifespan"
|
||||
data-testid="oAuthDeviceCodeLifespan"
|
||||
value={field.value || 0}
|
||||
onChange={field.onChange}
|
||||
units={["minute", "hour", "day"]}
|
||||
{isFeatureEnabled(Feature.DeviceFlow) && (
|
||||
<>
|
||||
<FormGroup
|
||||
label={t("oAuthDeviceCodeLifespan")}
|
||||
fieldId="oAuthDeviceCodeLifespan"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={t("oAuthDeviceCodeLifespanHelp")}
|
||||
fieldLabelId="oAuthDeviceCodeLifespan"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Controller
|
||||
name="oauth2DeviceCodeLifespan"
|
||||
defaultValue={0}
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<TimeSelector
|
||||
id="oAuthDeviceCodeLifespan"
|
||||
data-testid="oAuthDeviceCodeLifespan"
|
||||
value={field.value || 0}
|
||||
onChange={field.onChange}
|
||||
units={["minute", "hour", "day"]}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("oAuthDevicePollingInterval")}
|
||||
fieldId="oAuthDevicePollingInterval"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={t("oAuthDevicePollingIntervalHelp")}
|
||||
fieldLabelId="oAuthDevicePollingInterval"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Controller
|
||||
name="oauth2DevicePollingInterval"
|
||||
defaultValue={0}
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<NumberInput
|
||||
id="oAuthDevicePollingInterval"
|
||||
value={field.value}
|
||||
min={0}
|
||||
onPlus={() => field.onChange(field.value || 0 + 1)}
|
||||
onMinus={() => field.onChange(field.value || 0 - 1)}
|
||||
onChange={(event) => {
|
||||
const newValue = Number(event.currentTarget.value);
|
||||
field.onChange(!isNaN(newValue) ? newValue : 0);
|
||||
}}
|
||||
placeholder={t("oAuthDevicePollingInterval")}
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("oAuthDevicePollingInterval")}
|
||||
fieldId="oAuthDevicePollingInterval"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={t("oAuthDevicePollingIntervalHelp")}
|
||||
fieldLabelId="oAuthDevicePollingInterval"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Controller
|
||||
name="oauth2DevicePollingInterval"
|
||||
defaultValue={0}
|
||||
control={form.control}
|
||||
render={({ field }) => (
|
||||
<NumberInput
|
||||
id="oAuthDevicePollingInterval"
|
||||
value={field.value}
|
||||
min={0}
|
||||
onPlus={() => field.onChange(field.value || 0 + 1)}
|
||||
onMinus={() => field.onChange(field.value || 0 - 1)}
|
||||
onChange={(event) => {
|
||||
const newValue = Number(event.currentTarget.value);
|
||||
field.onChange(!isNaN(newValue) ? newValue : 0);
|
||||
}}
|
||||
placeholder={t("oAuthDevicePollingInterval")}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("shortVerificationUri")}
|
||||
fieldId="shortVerificationUri"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={t("shortVerificationUriTooltipHelp")}
|
||||
fieldLabelId="shortVerificationUri"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
id="shortVerificationUri"
|
||||
placeholder={t("shortVerificationUri")}
|
||||
{...form.register("attributes.shortVerificationUri")}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("shortVerificationUri")}
|
||||
fieldId="shortVerificationUri"
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText={t("shortVerificationUriTooltipHelp")}
|
||||
fieldLabelId="shortVerificationUri"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<KeycloakTextInput
|
||||
id="shortVerificationUri"
|
||||
placeholder={t("shortVerificationUri")}
|
||||
{...form.register("attributes.shortVerificationUri")}
|
||||
/>
|
||||
</FormGroup>
|
||||
</>
|
||||
)}
|
||||
</FormAccess>
|
||||
</FormPanel>
|
||||
<FormPanel
|
||||
|
|
|
@ -7,6 +7,7 @@ export enum Feature {
|
|||
Kerberos = "KERBEROS",
|
||||
DynamicScopes = "DYNAMIC_SCOPES",
|
||||
DPoP = "DPOP",
|
||||
DeviceFlow = "DEVICE_FLOW",
|
||||
}
|
||||
|
||||
export default function useIsFeatureEnabled() {
|
||||
|
|
|
@ -47,17 +47,17 @@ Feature:
|
|||
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
|
||||
HTTP/TLS:
|
||||
|
|
|
@ -47,17 +47,17 @@ Feature:
|
|||
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
|
||||
HTTP/TLS:
|
||||
|
|
|
@ -58,17 +58,17 @@ Feature:
|
|||
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
|
||||
Config:
|
||||
|
|
|
@ -121,17 +121,17 @@ Feature:
|
|||
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
|
||||
Config:
|
||||
|
|
|
@ -58,17 +58,17 @@ Feature:
|
|||
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
|
||||
Config:
|
||||
|
|
|
@ -121,17 +121,17 @@ Feature:
|
|||
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
|
||||
Config:
|
||||
|
|
|
@ -74,17 +74,17 @@ Feature:
|
|||
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
|
||||
Hostname:
|
||||
|
|
|
@ -72,17 +72,17 @@ Feature:
|
|||
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
|
||||
Hostname:
|
||||
|
|
|
@ -137,17 +137,17 @@ Feature:
|
|||
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
|
||||
Hostname:
|
||||
|
|
|
@ -135,17 +135,17 @@ Feature:
|
|||
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
|
||||
Hostname:
|
||||
|
|
|
@ -75,17 +75,17 @@ Feature:
|
|||
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
|
||||
Hostname:
|
||||
|
|
|
@ -73,17 +73,17 @@ Feature:
|
|||
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
|
||||
Hostname:
|
||||
|
|
|
@ -138,17 +138,17 @@ Feature:
|
|||
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
|
||||
Hostname:
|
||||
|
|
|
@ -136,17 +136,17 @@ Feature:
|
|||
--features <feature> Enables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
--features-disabled <feature>
|
||||
Disables a set of one or more features. Possible values are: account-api,
|
||||
account2, account3, admin-api, admin-fine-grained-authz, admin2,
|
||||
authorization, ciba, client-policies, client-secret-rotation,
|
||||
declarative-user-profile, docker, dpop, dynamic-scopes, fips, impersonation,
|
||||
js-adapter, kerberos, linkedin-oauth, map-storage, par, preview,
|
||||
recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
declarative-user-profile, device-flow, docker, dpop, dynamic-scopes, fips,
|
||||
impersonation, js-adapter, kerberos, linkedin-oauth, map-storage, par,
|
||||
preview, recovery-codes, scripts, step-up-authentication, token-exchange,
|
||||
update-email, web-authn.
|
||||
|
||||
Hostname:
|
||||
|
|
|
@ -100,11 +100,15 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
|
|||
public OIDCWellKnownProvider(KeycloakSession session, Map<String, Object> openidConfigOverride, boolean includeClientScopes) {
|
||||
DEFAULT_GRANT_TYPES_SUPPORTED = Stream.of(OAuth2Constants.AUTHORIZATION_CODE,
|
||||
OAuth2Constants.IMPLICIT, OAuth2Constants.REFRESH_TOKEN, OAuth2Constants.PASSWORD, OAuth2Constants.CLIENT_CREDENTIALS,
|
||||
OAuth2Constants.DEVICE_CODE_GRANT_TYPE,
|
||||
OAuth2Constants.CIBA_GRANT_TYPE).collect(Collectors.toList());
|
||||
if (Profile.isFeatureEnabled(Profile.Feature.TOKEN_EXCHANGE)) {
|
||||
DEFAULT_GRANT_TYPES_SUPPORTED.add(OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE);
|
||||
}
|
||||
|
||||
if (Profile.isFeatureEnabled(Profile.Feature.DEVICE_FLOW)) {
|
||||
DEFAULT_GRANT_TYPES_SUPPORTED.add(OAuth2Constants.DEVICE_CODE_GRANT_TYPE);
|
||||
}
|
||||
|
||||
this.session = session;
|
||||
this.openidConfigOverride = openidConfigOverride;
|
||||
this.includeClientScopes = includeClientScopes;
|
||||
|
@ -127,9 +131,11 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
|
|||
config.setIntrospectionEndpoint(backendUriBuilder.clone().path(OIDCLoginProtocolService.class, "token").path(TokenEndpoint.class, "introspect").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
|
||||
config.setUserinfoEndpoint(backendUriBuilder.clone().path(OIDCLoginProtocolService.class, "issueUserInfo").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
|
||||
config.setLogoutEndpoint(frontendUriBuilder.clone().path(OIDCLoginProtocolService.class, "logout").build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
|
||||
config.setDeviceAuthorizationEndpoint(frontendUriBuilder.clone().path(OIDCLoginProtocolService.class, "auth")
|
||||
.path(AuthorizationEndpoint.class, "authorizeDevice").path(DeviceEndpoint.class, "handleDeviceRequest")
|
||||
.build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
|
||||
if (Profile.isFeatureEnabled(Profile.Feature.DEVICE_FLOW)) {
|
||||
config.setDeviceAuthorizationEndpoint(frontendUriBuilder.clone().path(OIDCLoginProtocolService.class, "auth")
|
||||
.path(AuthorizationEndpoint.class, "authorizeDevice").path(DeviceEndpoint.class, "handleDeviceRequest")
|
||||
.build(realm.getName(), OIDCLoginProtocol.LOGIN_PROTOCOL).toString());
|
||||
}
|
||||
URI jwksUri = backendUriBuilder.clone().path(OIDCLoginProtocolService.class, "certs").build(realm.getName(),
|
||||
OIDCLoginProtocol.LOGIN_PROTOCOL);
|
||||
|
||||
|
@ -301,7 +307,9 @@ public class OIDCWellKnownProvider implements WellKnownProvider {
|
|||
mtls_endpoints.setTokenEndpoint(config.getTokenEndpoint());
|
||||
mtls_endpoints.setRevocationEndpoint(config.getRevocationEndpoint());
|
||||
mtls_endpoints.setIntrospectionEndpoint(config.getIntrospectionEndpoint());
|
||||
mtls_endpoints.setDeviceAuthorizationEndpoint(config.getDeviceAuthorizationEndpoint());
|
||||
if (Profile.isFeatureEnabled(Profile.Feature.DEVICE_FLOW)) {
|
||||
mtls_endpoints.setDeviceAuthorizationEndpoint(config.getDeviceAuthorizationEndpoint());
|
||||
}
|
||||
mtls_endpoints.setRegistrationEndpoint(config.getRegistrationEndpoint());
|
||||
mtls_endpoints.setUserInfoEndpoint(config.getUserinfoEndpoint());
|
||||
mtls_endpoints.setBackchannelAuthenticationEndpoint(config.getBackchannelAuthenticationEndpoint());
|
||||
|
|
|
@ -125,6 +125,9 @@ public class AuthorizationEndpoint extends AuthorizationEndpointBase {
|
|||
*/
|
||||
@Path("device")
|
||||
public Object authorizeDevice() {
|
||||
if (!Profile.isFeatureEnabled(Profile.Feature.DEVICE_FLOW)) {
|
||||
return null;
|
||||
}
|
||||
return new DeviceEndpoint(session, event);
|
||||
}
|
||||
|
||||
|
|
|
@ -309,19 +309,26 @@ public class TokenEndpoint {
|
|||
event.event(EventType.PERMISSION_TOKEN);
|
||||
action = Action.PERMISSION;
|
||||
} else if (grantType.equals(OAuth2Constants.DEVICE_CODE_GRANT_TYPE)) {
|
||||
if (!Profile.isFeatureEnabled(Profile.Feature.DEVICE_FLOW)) {
|
||||
throw newUnsupportedGrantTypeException();
|
||||
}
|
||||
event.event(EventType.OAUTH2_DEVICE_CODE_TO_TOKEN);
|
||||
action = Action.OAUTH2_DEVICE_CODE;
|
||||
} else if (grantType.equals(OAuth2Constants.CIBA_GRANT_TYPE)) {
|
||||
event.event(EventType.AUTHREQID_TO_TOKEN);
|
||||
action = Action.CIBA;
|
||||
} else {
|
||||
throw new CorsErrorResponseException(cors, OAuthErrorException.UNSUPPORTED_GRANT_TYPE,
|
||||
"Unsupported " + OIDCLoginProtocol.GRANT_TYPE_PARAM, Response.Status.BAD_REQUEST);
|
||||
throw newUnsupportedGrantTypeException();
|
||||
}
|
||||
|
||||
event.detail(Details.GRANT_TYPE, grantType);
|
||||
}
|
||||
|
||||
private CorsErrorResponseException newUnsupportedGrantTypeException() {
|
||||
return new CorsErrorResponseException(cors, OAuthErrorException.UNSUPPORTED_GRANT_TYPE,
|
||||
"Unsupported " + OIDCLoginProtocol.GRANT_TYPE_PARAM, Status.BAD_REQUEST);
|
||||
}
|
||||
|
||||
private void checkParameterDuplicated() {
|
||||
for (String key : formParams.keySet()) {
|
||||
if (formParams.get(key).size() != 1) {
|
||||
|
|
|
@ -20,18 +20,20 @@
|
|||
package org.keycloak.protocol.oidc.grants.device.endpoints;
|
||||
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.events.EventBuilder;
|
||||
import org.keycloak.models.KeycloakContext;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.provider.EnvironmentDependentProviderFactory;
|
||||
import org.keycloak.services.resource.RealmResourceProvider;
|
||||
import org.keycloak.services.resource.RealmResourceProviderFactory;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
|
||||
*/
|
||||
public class DeviceEndpointFactory implements RealmResourceProviderFactory {
|
||||
public class DeviceEndpointFactory implements RealmResourceProviderFactory, EnvironmentDependentProviderFactory {
|
||||
|
||||
@Override
|
||||
public RealmResourceProvider create(KeycloakSession session) {
|
||||
|
@ -60,4 +62,9 @@ public class DeviceEndpointFactory implements RealmResourceProviderFactory {
|
|||
public String getId() {
|
||||
return "device";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return Profile.isFeatureEnabled(Profile.Feature.DEVICE_FLOW);
|
||||
}
|
||||
}
|
|
@ -113,8 +113,11 @@ import java.io.File;
|
|||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.Files;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Collections;
|
||||
|
@ -124,6 +127,7 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.UUID;
|
||||
|
@ -881,6 +885,19 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
|||
}
|
||||
|
||||
private void setFeatureInProfileFile(File file, Profile.Feature featureProfile, String newState) {
|
||||
doWithProperties(file, props -> {
|
||||
props.setProperty("feature." + featureProfile.toString().toLowerCase(), newState);
|
||||
});
|
||||
}
|
||||
|
||||
private void unsetFeatureInProfileFile(File file, Profile.Feature featureProfile) {
|
||||
doWithProperties(file, props -> {
|
||||
props.remove("feature." + featureProfile.toString().toLowerCase());
|
||||
});
|
||||
}
|
||||
|
||||
private void doWithProperties(File file, Consumer<Properties> callback) {
|
||||
|
||||
Properties properties = new Properties();
|
||||
if (file.isFile() && file.exists()) {
|
||||
try (FileInputStream fis = new FileInputStream(file)) {
|
||||
|
@ -890,7 +907,11 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
|||
}
|
||||
}
|
||||
|
||||
properties.setProperty("feature." + featureProfile.toString().toLowerCase(), newState);
|
||||
callback.accept(properties);
|
||||
|
||||
if (file.isFile() && !file.getParentFile().exists()) {
|
||||
file.getParentFile().mkdirs();
|
||||
}
|
||||
|
||||
try (FileOutputStream fos = new FileOutputStream(file)) {
|
||||
properties.store(fos, null);
|
||||
|
@ -922,6 +943,30 @@ public class TestingResourceProvider implements RealmResourceProvider {
|
|||
return updateFeature(feature, false);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("/reset-feature/{feature}")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
public void resetFeature(@PathParam("feature") String featureKey) {
|
||||
|
||||
Profile.Feature feature;
|
||||
|
||||
try {
|
||||
feature = Profile.Feature.valueOf(featureKey);
|
||||
} catch (IllegalArgumentException e) {
|
||||
System.err.printf("Feature '%s' doesn't exist!!\n", featureKey);
|
||||
throw new BadRequestException();
|
||||
}
|
||||
|
||||
FeatureDeployerUtil.initBeforeChangeFeature(feature);
|
||||
|
||||
String jbossServerConfigDir = System.getProperty("jboss.server.config.dir");
|
||||
// If we are in jboss-based container, we need to write profile.properties file, otherwise the change in system property will disappear after restart
|
||||
if (jbossServerConfigDir != null) {
|
||||
File file = new File(jbossServerConfigDir, "profile.properties");
|
||||
unsetFeatureInProfileFile(file, feature);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<Profile.Feature> updateFeature(String featureKey, boolean shouldEnable) {
|
||||
Profile.Feature feature;
|
||||
|
||||
|
|
|
@ -79,9 +79,9 @@ public class KeycloakTestingClient implements AutoCloseable {
|
|||
}
|
||||
|
||||
public void enableFeature(Profile.Feature feature) {
|
||||
Set<Profile.Feature> disabledFeatures = testing().enableFeature(feature.toString());
|
||||
Assert.assertFalse(disabledFeatures.contains(feature));
|
||||
ProfileAssume.updateDisabledFeatures(disabledFeatures);
|
||||
Set<Profile.Feature> enabledFeatures = testing().enableFeature(feature.toString());
|
||||
Assert.assertFalse(enabledFeatures.contains(feature));
|
||||
ProfileAssume.updateDisabledFeatures(enabledFeatures);
|
||||
}
|
||||
|
||||
public void disableFeature(Profile.Feature feature) {
|
||||
|
@ -90,6 +90,15 @@ public class KeycloakTestingClient implements AutoCloseable {
|
|||
ProfileAssume.updateDisabledFeatures(disabledFeatures);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the feature to it's default setting.
|
||||
*
|
||||
* @param feature
|
||||
*/
|
||||
public void resetFeature(Profile.Feature feature) {
|
||||
testing().resetFeature(feature.toString());
|
||||
}
|
||||
|
||||
public TestApplicationResource testApp() { return target.proxy(TestApplicationResource.class); }
|
||||
|
||||
public TestSamlApplicationResource testSamlApp() { return target.proxy(TestSamlApplicationResource.class); }
|
||||
|
|
|
@ -343,6 +343,16 @@ public interface TestingResource {
|
|||
@Produces(MediaType.APPLICATION_JSON)
|
||||
Set<Profile.Feature> disableFeature(@PathParam("feature") String feature);
|
||||
|
||||
/**
|
||||
* Resets the given feature to it's default state.
|
||||
*
|
||||
* @param feature
|
||||
*/
|
||||
@POST
|
||||
@Path("/reset-feature/{feature}")
|
||||
@Consumes(MediaType.APPLICATION_JSON)
|
||||
void resetFeature(@PathParam("feature") String feature);
|
||||
|
||||
/**
|
||||
* If property-value is null, the system property will be unset (removed) on the server
|
||||
*/
|
||||
|
|
|
@ -22,19 +22,23 @@ import static org.junit.Assert.assertNull;
|
|||
import static org.keycloak.models.OAuth2DeviceConfig.DEFAULT_OAUTH2_DEVICE_CODE_LIFESPAN;
|
||||
import static org.keycloak.models.OAuth2DeviceConfig.DEFAULT_OAUTH2_DEVICE_POLLING_INTERVAL;
|
||||
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.jboss.arquillian.graphene.page.Page;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.OAuthErrorException;
|
||||
import org.keycloak.admin.client.resource.ClientResource;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.events.Errors;
|
||||
import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.OAuth2DeviceConfig;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.protocol.oidc.OIDCConfigAttributes;
|
||||
import org.keycloak.protocol.oidc.grants.device.endpoints.DeviceEndpoint;
|
||||
import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.UserInfo;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
|
@ -49,6 +53,7 @@ import org.keycloak.testsuite.pages.ErrorPage;
|
|||
import org.keycloak.testsuite.pages.OAuth2DeviceVerificationPage;
|
||||
import org.keycloak.testsuite.pages.OAuthGrantPage;
|
||||
import org.keycloak.testsuite.util.ClientBuilder;
|
||||
import org.keycloak.testsuite.util.ContainerAssume;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.RealmBuilder;
|
||||
import org.keycloak.testsuite.util.UserBuilder;
|
||||
|
@ -62,11 +67,11 @@ import org.apache.http.impl.client.CloseableHttpClient;
|
|||
import org.keycloak.util.BasicAuthHelper;
|
||||
import org.openqa.selenium.Cookie;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.LinkedList;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:h2-wada@nri.co.jp">Hiroyuki Wada</a>
|
||||
|
@ -992,6 +997,43 @@ public class OAuth2DeviceAuthorizationGrantTest extends AbstractKeycloakTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ensureDeviceFlowConfigPresentWhenDeviceFlowIsEnabled() {
|
||||
|
||||
OIDCConfigurationRepresentation oidcConfigRep = oauth.doWellKnownRequest(REALM_NAME);
|
||||
Assert.assertNotNull("deviceAuthorizationEndpoint should be not null", oidcConfigRep.getDeviceAuthorizationEndpoint());
|
||||
Assert.assertNotNull("mtlsEndpointAliases.deviceAuthorizationEndpoint should be not null", oidcConfigRep.getMtlsEndpointAliases().getDeviceAuthorizationEndpoint());
|
||||
}
|
||||
|
||||
@Test
|
||||
// @DisableFeature(value = Profile.Feature.DEVICE_FLOW, executeAsLast = false, skipRestart = true)
|
||||
public void ensureDeviceFlowConfigNotPresentWhenDeviceFlowIsDisabled() throws Exception {
|
||||
|
||||
// this test currently does not work with -Pauth-server-quarkus
|
||||
ContainerAssume.assumeAuthServerUndertow();
|
||||
|
||||
testingClient.disableFeature(Profile.Feature.DEVICE_FLOW);
|
||||
|
||||
try {
|
||||
OIDCConfigurationRepresentation oidcConfigRep = oauth.doWellKnownRequest(REALM_NAME);
|
||||
Assert.assertNull("deviceAuthorizationEndpoint should be null", oidcConfigRep.getDeviceAuthorizationEndpoint());
|
||||
Assert.assertNull("mtlsEndpointAliases.deviceAuthorizationEndpoint should be null", oidcConfigRep.getMtlsEndpointAliases().getDeviceAuthorizationEndpoint());
|
||||
|
||||
try (var httpClient = oauth.getHttpClient().get()) {
|
||||
Assert.assertEquals("Should return not found for device auth endpoint"
|
||||
, (long) 404
|
||||
, (long) httpClient.execute(new HttpGet(oauth.getDeviceAuthorizationUrl()), r -> r.getStatusLine().getStatusCode()));
|
||||
}
|
||||
|
||||
oauth.realm(REALM_NAME);
|
||||
oauth.clientId(DEVICE_APP_PUBLIC);
|
||||
OAuthClient.AccessTokenResponse tokenResponse = oauth.doDeviceTokenRequest(DEVICE_APP_PUBLIC, null, "dummy");
|
||||
Assert.assertEquals(OAuthErrorException.UNSUPPORTED_GRANT_TYPE, tokenResponse.getError());
|
||||
} finally {
|
||||
testingClient.resetFeature(Profile.Feature.DEVICE_FLOW);
|
||||
}
|
||||
}
|
||||
|
||||
private void openVerificationPage(String verificationUri) {
|
||||
driver.navigate().to(verificationUri);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue