diff --git a/services/src/main/java/org/keycloak/protocol/oidc/grants/device/endpoints/DeviceEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/grants/device/endpoints/DeviceEndpoint.java index 0f9074fb60..900f166623 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/grants/device/endpoints/DeviceEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/grants/device/endpoints/DeviceEndpoint.java @@ -80,6 +80,8 @@ public class DeviceEndpoint extends AuthorizationEndpointBase implements RealmRe protected static final Logger logger = Logger.getLogger(DeviceEndpoint.class); + public static final String SHORT_VERIFICATION_URI = "shortVerificationUri"; + private final HttpRequest request; private Cors cors; @@ -168,7 +170,7 @@ public class DeviceEndpoint extends AuthorizationEndpointBase implements RealmRe singleUseStore.put(userCode.serializeKey(), lifespanSeconds, userCode.serializeValue()); try { - String deviceUrl = DeviceGrantType.oauth2DeviceVerificationUrl(session.getContext().getUri()).build(realm.getName()) + String deviceUrl = realm.getAttribute(SHORT_VERIFICATION_URI) != null ? realm.getAttribute(SHORT_VERIFICATION_URI) : DeviceGrantType.oauth2DeviceVerificationUrl(session.getContext().getUri()).build(realm.getName()) .toString(); OAuth2DeviceAuthorizationResponse response = new OAuth2DeviceAuthorizationResponse(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuth2DeviceAuthorizationGrantTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuth2DeviceAuthorizationGrantTest.java index 15bce68cc2..0b1c6e9599 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuth2DeviceAuthorizationGrantTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/OAuth2DeviceAuthorizationGrantTest.java @@ -33,6 +33,7 @@ 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.representations.AccessToken; import org.keycloak.representations.UserInfo; import org.keycloak.representations.idm.ClientRepresentation; @@ -71,10 +72,11 @@ public class OAuth2DeviceAuthorizationGrantTest extends AbstractKeycloakTest { private static String userId; - public static final String REALM_NAME = "test"; - public static final String DEVICE_APP = "test-device"; - public static final String DEVICE_APP_PUBLIC = "test-device-public"; - public static final String DEVICE_APP_PUBLIC_CUSTOM_CONSENT = "test-device-public-custom-consent"; + private static final String REALM_NAME = "test"; + private static final String DEVICE_APP = "test-device"; + private static final String DEVICE_APP_PUBLIC = "test-device-public"; + private static final String DEVICE_APP_PUBLIC_CUSTOM_CONSENT = "test-device-public-custom-consent"; + private static final String SHORT_DEVICE_FLOW_URL = "https://keycloak.org/device"; @Rule public AssertEvents events = new AssertEvents(this); @@ -217,6 +219,32 @@ public class OAuth2DeviceAuthorizationGrantTest extends AbstractKeycloakTest { assertNotNull(token); } + + @Test + public void testCustomVerificationUri() throws Exception { + // Device Authorization Request from device + try { + RealmResource testRealm = adminClient.realm(REALM_NAME); + RealmRepresentation realmRep = testRealm.toRepresentation(); + realmRep.getAttributes().put(DeviceEndpoint.SHORT_VERIFICATION_URI, SHORT_DEVICE_FLOW_URL); + testRealm.update(realmRep); + oauth.realm(REALM_NAME); + oauth.clientId(DEVICE_APP_PUBLIC); + OAuthClient.DeviceAuthorizationResponse response = oauth.doDeviceAuthorizationRequest(DEVICE_APP_PUBLIC, null); + + Assert.assertEquals(200, response.getStatusCode()); + assertNotNull(response.getDeviceCode()); + assertNotNull(response.getUserCode()); + Assert.assertEquals(SHORT_DEVICE_FLOW_URL,response.getVerificationUri()); + Assert.assertEquals(SHORT_DEVICE_FLOW_URL + "?user_code=" + response.getUserCode(),response.getVerificationUriComplete()); + } finally { + RealmResource testRealm = adminClient.realm(REALM_NAME); + RealmRepresentation realmRep = testRealm.toRepresentation(); + realmRep.getAttributes().remove("shortVerificationUri"); + testRealm.update(realmRep); + } + } + @Test public void testPublicClientOptionalScope() throws Exception { // Device Authorization Request from device - check giving optional scope phone diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index 848bae55a2..cd6945b2f4 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -474,6 +474,8 @@ policy-uri=Policy URL policy-uri.tooltip=URL that the Relying Party Client provides to the End-User to read about the how the profile data will be used tos-uri=Terms of service URL tos-uri.tooltip=URL that the Relying Party Client provides to the End-User to read about the Relying Party's terms of service +short-verification-uri= Short verification_uri in Device Authorization flow +short-verification-uri.tooltip= If set, this value will be return as verification_uri in Device Authorization flow. This uri need to redirect to {server-root}/realms/{realm}/device # client import diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html b/themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html index 65232d2de5..c5243a0058 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html @@ -387,6 +387,14 @@ {{:: 'oauth2-device-polling-interval.tooltip' | translate}} +
+ +
+ +
+ {{:: 'short-verification-uri.tooltip' | translate}} +
+