diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.0.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.0.xml index a782f76f79..9e48a6aa08 100644 --- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.0.xml +++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.6.0.xml @@ -10,6 +10,7 @@ + @@ -47,5 +48,11 @@ + + + + + + \ No newline at end of file diff --git a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java index 7ad8c6e443..099950512b 100755 --- a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java @@ -11,6 +11,7 @@ public class ClientRepresentation { protected String id; protected String clientId; protected String name; + protected String description; protected String rootUrl; protected String adminUrl; protected String baseUrl; @@ -51,6 +52,14 @@ public class ClientRepresentation { this.name = name; } + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + public String getClientId() { return clientId; } diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java index c1c53b32f0..1bec10b7cf 100755 --- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java @@ -10,6 +10,7 @@ public class RealmRepresentation { protected String id; protected String realm; protected Integer notBefore; + protected Boolean revokeRefreshToken; protected Integer accessTokenLifespan; protected Integer ssoSessionIdleTimeout; protected Integer ssoSessionMaxLifespan; @@ -166,6 +167,14 @@ public class RealmRepresentation { this.sslRequired = sslRequired; } + public Boolean getRevokeRefreshToken() { + return revokeRefreshToken; + } + + public void setRevokeRefreshToken(Boolean revokeRefreshToken) { + this.revokeRefreshToken = revokeRefreshToken; + } + public Integer getAccessTokenLifespan() { return accessTokenLifespan; } diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json index 669cf41db5..3e4315c136 100644 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/content/standalone/configuration/keycloak-server.json @@ -59,7 +59,7 @@ "connectionsInfinispan": { "default" : { - "cacheContainer" : "java:jboss/infinispan/Keycloak" + "cacheContainer" : "java:comp/env/infinispan/Keycloak" } } } \ No newline at end of file diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/infinispan/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/infinispan/main/module.xml new file mode 100755 index 0000000000..65e3dc2d0c --- /dev/null +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/infinispan/main/module.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml index 11f8141941..c339f44671 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml @@ -54,6 +54,9 @@ + + + diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/web.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/web.xml index 164f6beaff..f59d1d347d 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/web.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/base/org/keycloak/keycloak-server-subsystem/main/server-war/WEB-INF/web.xml @@ -39,4 +39,10 @@ Keycloak REST Interface /* + + + infinispan/Keycloak + org.infinispan.manager.EmbeddedCacheManager + java:jboss/infinispan/Keycloak + diff --git a/distribution/server-overlay/eap6/eap6-server-modules/build.xml b/distribution/server-overlay/eap6/eap6-server-modules/build.xml index 22764719ac..60ccb65be0 100755 --- a/distribution/server-overlay/eap6/eap6-server-modules/build.xml +++ b/distribution/server-overlay/eap6/eap6-server-modules/build.xml @@ -290,6 +290,8 @@ + + diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml index 11f8141941..c339f44671 100755 --- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml +++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/jboss-deployment-structure.xml @@ -54,6 +54,9 @@ + + + diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/web.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/web.xml index 164f6beaff..f59d1d347d 100755 --- a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/web.xml +++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-as7-server-subsystem/main/server-war/WEB-INF/web.xml @@ -39,4 +39,10 @@ Keycloak REST Interface /* + + + infinispan/Keycloak + org.infinispan.manager.EmbeddedCacheManager + java:jboss/infinispan/Keycloak + diff --git a/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-server-subsystem/infinispan/main/module.xml b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-server-subsystem/infinispan/main/module.xml new file mode 100755 index 0000000000..65e3dc2d0c --- /dev/null +++ b/distribution/server-overlay/eap6/eap6-server-modules/src/main/resources/modules/org/keycloak/keycloak-server-subsystem/infinispan/main/module.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/distribution/server-overlay/eap6/eap6-server-overlay/assembly.xml b/distribution/server-overlay/eap6/eap6-server-overlay/assembly.xml index 580b1d7ef7..b3324df8b4 100755 --- a/distribution/server-overlay/eap6/eap6-server-overlay/assembly.xml +++ b/distribution/server-overlay/eap6/eap6-server-overlay/assembly.xml @@ -20,13 +20,13 @@ **/** - + diff --git a/distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-prepare-ha.cli b/distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-install-ha.cli similarity index 79% rename from distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-prepare-ha.cli rename to distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-install-ha.cli index edcd1c6ab6..a95c20fa2c 100644 --- a/distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-prepare-ha.cli +++ b/distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-install-ha.cli @@ -5,4 +5,7 @@ /subsystem=infinispan/cache-container=keycloak/invalidation-cache=realms:add(mode="SYNC") /subsystem=infinispan/cache-container=keycloak/invalidation-cache=users:add(mode="SYNC") /subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:add(mode="SYNC",owners="1") -/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:add(mode="SYNC",owners="1") \ No newline at end of file +/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:add(mode="SYNC",owners="1") +/extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem) +/subsystem=keycloak-server:add(web-context=auth) +:shutdown(restart=true) \ No newline at end of file diff --git a/distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-install.cli b/distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-install.cli index cc594313f9..156197abfa 100644 --- a/distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-install.cli +++ b/distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-install.cli @@ -1,2 +1,10 @@ +/subsystem=datasources/data-source=KeycloakDS/:add(connection-url="jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE",driver-name=h2,jndi-name=java:jboss/datasources/KeycloakDS,password=sa,user-name=sa,use-java-context=true,enabled=true) +/subsystem=logging/logger=org.jboss.resteasy.resteasy_jaxrs.i18n/:add(level=ERROR) +/subsystem=infinispan/cache-container=keycloak:add(jndi-name="infinispan/Keycloak",start="EAGER") +/subsystem=infinispan/cache-container=keycloak/local-cache=realms:add() +/subsystem=infinispan/cache-container=keycloak/local-cache=users:add() +/subsystem=infinispan/cache-container=keycloak/local-cache=sessions:add() +/subsystem=infinispan/cache-container=keycloak/local-cache=loginFailures:add() /extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem) -/subsystem=keycloak-server:add(web-context=auth) \ No newline at end of file +/subsystem=keycloak-server:add(web-context=auth) +:shutdown(restart=true) \ No newline at end of file diff --git a/distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-prepare.cli b/distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-prepare.cli deleted file mode 100644 index 191cb284cf..0000000000 --- a/distribution/server-overlay/eap6/eap6-server-overlay/cli/keycloak-prepare.cli +++ /dev/null @@ -1,7 +0,0 @@ -/subsystem=datasources/data-source=KeycloakDS/:add(connection-url="jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE",driver-name=h2,jndi-name=java:jboss/datasources/KeycloakDS,password=sa,user-name=sa,use-java-context=true,enabled=true) -/subsystem=logging/logger=org.jboss.resteasy.resteasy_jaxrs.i18n/:add(level=ERROR) -/subsystem=infinispan/cache-container=keycloak:add(jndi-name="infinispan/Keycloak",start="EAGER") -/subsystem=infinispan/cache-container=keycloak/local-cache=realms:add() -/subsystem=infinispan/cache-container=keycloak/local-cache=users:add() -/subsystem=infinispan/cache-container=keycloak/local-cache=sessions:add() -/subsystem=infinispan/cache-container=keycloak/local-cache=loginFailures:add() \ No newline at end of file diff --git a/distribution/server-overlay/wf9-server-overlay/assembly.xml b/distribution/server-overlay/wf9-server-overlay/assembly.xml index 0427f802ff..8f0daa28b5 100755 --- a/distribution/server-overlay/wf9-server-overlay/assembly.xml +++ b/distribution/server-overlay/wf9-server-overlay/assembly.xml @@ -47,13 +47,13 @@ - + diff --git a/distribution/server-overlay/wf9-server-overlay/cli/keycloak-prepare-ha.cli b/distribution/server-overlay/wf9-server-overlay/cli/keycloak-install-ha.cli similarity index 77% rename from distribution/server-overlay/wf9-server-overlay/cli/keycloak-prepare-ha.cli rename to distribution/server-overlay/wf9-server-overlay/cli/keycloak-install-ha.cli index 5cfae3876a..f9a8d29ee1 100644 --- a/distribution/server-overlay/wf9-server-overlay/cli/keycloak-prepare-ha.cli +++ b/distribution/server-overlay/wf9-server-overlay/cli/keycloak-install-ha.cli @@ -4,4 +4,7 @@ /subsystem=infinispan/cache-container=keycloak/invalidation-cache=realms:add(mode="SYNC") /subsystem=infinispan/cache-container=keycloak/invalidation-cache=users:add(mode="SYNC") /subsystem=infinispan/cache-container=keycloak/distributed-cache=sessions:add(mode="SYNC",owners="1") -/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:add(mode="SYNC",owners="1") \ No newline at end of file +/subsystem=infinispan/cache-container=keycloak/distributed-cache=loginFailures:add(mode="SYNC",owners="1") +/extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem) +/subsystem=keycloak-server:add(web-context=auth) +:shutdown(restart=true) \ No newline at end of file diff --git a/distribution/server-overlay/wf9-server-overlay/cli/keycloak-install.cli b/distribution/server-overlay/wf9-server-overlay/cli/keycloak-install.cli index cc594313f9..d6c398886b 100644 --- a/distribution/server-overlay/wf9-server-overlay/cli/keycloak-install.cli +++ b/distribution/server-overlay/wf9-server-overlay/cli/keycloak-install.cli @@ -1,2 +1,8 @@ +/subsystem=datasources/data-source=KeycloakDS/:add(connection-url="jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE",driver-name=h2,jndi-name=java:jboss/datasources/KeycloakDS,password=sa,user-name=sa,use-java-context=true) +/subsystem=infinispan/cache-container=keycloak:add(jndi-name="infinispan/Keycloak") +/subsystem=infinispan/cache-container=keycloak/local-cache=realms:add() +/subsystem=infinispan/cache-container=keycloak/local-cache=users:add() +/subsystem=infinispan/cache-container=keycloak/local-cache=sessions:add() +/subsystem=infinispan/cache-container=keycloak/local-cache=loginFailures:add() /extension=org.keycloak.keycloak-server-subsystem/:add(module=org.keycloak.keycloak-server-subsystem) /subsystem=keycloak-server:add(web-context=auth) \ No newline at end of file diff --git a/distribution/server-overlay/wf9-server-overlay/cli/keycloak-prepare.cli b/distribution/server-overlay/wf9-server-overlay/cli/keycloak-prepare.cli deleted file mode 100644 index dcad93a2d9..0000000000 --- a/distribution/server-overlay/wf9-server-overlay/cli/keycloak-prepare.cli +++ /dev/null @@ -1,6 +0,0 @@ -/subsystem=datasources/data-source=KeycloakDS/:add(connection-url="jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE",driver-name=h2,jndi-name=java:jboss/datasources/KeycloakDS,password=sa,user-name=sa,use-java-context=true) -/subsystem=infinispan/cache-container=keycloak:add(jndi-name="infinispan/Keycloak") -/subsystem=infinispan/cache-container=keycloak/local-cache=realms:add() -/subsystem=infinispan/cache-container=keycloak/local-cache=users:add() -/subsystem=infinispan/cache-container=keycloak/local-cache=sessions:add() -/subsystem=infinispan/cache-container=keycloak/local-cache=loginFailures:add() \ No newline at end of file diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml b/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml index 10027b90ac..25a393fd19 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/MigrationFromOlderVersions.xml @@ -79,6 +79,19 @@
Version specific migration +
+ Migrating to 1.6.0.Final + + Refresh tokens are not reusable anymore + + Old versions of Keycloak allowed reusing refresh tokens multiple times. Keycloak no longer permits + this by default. When a refresh token is used to obtain a new access token a new refresh token is also + included. This new refresh token should be used next time the access token is refreshed. If this is + a problem for you it's possible to enable reuse of refresh tokens in the admin console under token + settings. + + +
Migrating to 1.5.0.Final diff --git a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml index 9c78f3a4bf..558f943f12 100755 --- a/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml +++ b/docbook/auth-server-docs/reference/en/en-US/modules/server-installation.xml @@ -43,7 +43,7 @@
- Install on existing WildFly 9.0.0.Final + Install on existing WildFly 9.0.1.Final Keycloak can be installed into an existing WildFly 9.0.0.Final server. To do this download keycloak-overlay-&project.version;.zip or keycloak-overlay-&project.version;.tar.gz. @@ -59,22 +59,23 @@ (username: admin and password: admin). Keycloak will then prompt you to enter in a new password. - +
Install on existing JBoss EAP 6.4.0.GA - Same procedure as WildFly 9.0.0.Final, but download keycloak-overlay-eap6-&project.version;.zip or keycloak-overlay-eap6-&project.version;.tar.gz. + Same procedure as WildFly 9.0.1.Final, but download keycloak-overlay-eap6-&project.version;.zip or keycloak-overlay-eap6-&project.version;.tar.gz.
diff --git a/events/api/src/main/java/org/keycloak/events/EventType.java b/events/api/src/main/java/org/keycloak/events/EventType.java index ac93a4f7a9..0a8b3aecee 100755 --- a/events/api/src/main/java/org/keycloak/events/EventType.java +++ b/events/api/src/main/java/org/keycloak/events/EventType.java @@ -70,6 +70,7 @@ public enum EventType { CUSTOM_REQUIRED_ACTION(true), CUSTOM_REQUIRED_ACTION_ERROR(true), EXECUTE_ACTIONS(true), + EXECUTE_ACTIONS_ERROR(true), CLIENT_INFO(false), CLIENT_INFO_ERROR(false), diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties index 10549e1fe0..87640e0796 100644 --- a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties +++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_de.properties @@ -160,7 +160,7 @@ select-file=de Select file view-details=de View details clear-import=de Clear import client-id.tooltip=de Specifies ID referenced in URI and tokens. For example 'my-client' -client.name.tooltip=de Specifies display name of the client. For example 'My Client'. Supports keys for localized values as well. For example: ${my_client} +client.name.tooltip=de Specifies display name of the client. For example 'My Client'. Supports keys for localized values as well. For example\: ${my_client} client.enabled.tooltip=de Disabled clients cannot initiate a login or have obtain access tokens. consent-required=de Consent Required consent-required.tooltip=de If enabled users have to consent to client access. @@ -460,3 +460,4 @@ realm=de Realm identity-provider-mappers=de Identity Provider Mappers create-identity-provider-mapper=de Create Identity Provider Mapper add-identity-provider-mapper=de Add Identity Provider Mapper +client.description.tooltip=de Specifies description of the client. For example 'My Client for TimeSheets'. Supports keys for localized values as well. For example\: ${my_client_description} diff --git a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index d5193e7f7a..802645e583 100644 --- a/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/forms/common-themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -66,6 +66,8 @@ realm-cache-enabled=Realm Cache Enabled realm-cache-enabled.tooltip=Enable/disable cache for realm, client and role data. user-cache-enabled=User Cache Enabled user-cache-enabled.tooltip=Enable/disable user and user role mapping cache. +revoke-refresh-token=Revoke Refresh Token +revoke-refresh-token.tooltip=If enabled refresh tokens can only be used once. Otherwise refresh tokens are not revoked when used and can be used multiple times. sso-session-idle=SSO Session Idle seconds=Seconds minutes=Minutes @@ -160,7 +162,7 @@ select-file=Select file view-details=View details clear-import=Clear import client-id.tooltip=Specifies ID referenced in URI and tokens. For example 'my-client' -client.name.tooltip=Specifies display name of the client. For example 'My Client'. Supports keys for localized values as well. For example: ${my_client} +client.name.tooltip=Specifies display name of the client. For example 'My Client'. Supports keys for localized values as well. For example\: ${my_client} client.enabled.tooltip=Disabled clients cannot initiate a login or have obtain access tokens. consent-required=Consent Required consent-required.tooltip=If enabled users have to consent to client access. @@ -458,3 +460,4 @@ realm=Realm identity-provider-mappers=Identity Provider Mappers create-identity-provider-mapper=Create Identity Provider Mapper add-identity-provider-mapper=Add Identity Provider Mapper +client.description.tooltip=Specifies description of the client. For example 'My Client for TimeSheets'. Supports keys for localized values as well. For example\: ${my_client_description} diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html index 5d2b2ddd08..443cbe94ca 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html @@ -38,6 +38,13 @@ {{:: 'client.name.tooltip' | translate}} +
+ +
+ +
+ {{:: 'client.description.tooltip' | translate}} +
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html index a44b939b99..bb5502223d 100755 --- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html +++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/realm-tokens.html @@ -3,6 +3,17 @@
+
+ + +
+ +
+ + {{:: 'revoke-refresh-token.tooltip' | translate}} + +
+
diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties index 4cbbf0f44e..3445de50b8 100644 --- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties +++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_de.properties @@ -196,4 +196,5 @@ emailVerifiedMessage=Ihr E-Mail Adresse wurde erfolgreich verifiziert. locale_de=Deutsch locale_en=English locale_it=Italian -locale_pt-BR=Portugu\u00EAs (Brasil) \ No newline at end of file +locale_pt-BR=Portugu\u00EAs (Brasil) +locale_fr=Français diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties index 65ad002ae2..6fa441c0ec 100644 --- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties +++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_en.properties @@ -196,6 +196,7 @@ locale_de=German locale_en=English locale_it=Italian locale_pt-BR=Portugu\u00EAs (Brasil) +locale_fr=Français backToApplication=« Back to Application missingParameterMessage=Missing parameters\: {0} diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties index 4833b9be18..e353153c43 100644 --- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties +++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_it.properties @@ -189,6 +189,7 @@ locale_de=German locale_en=English locale_it=Italian locale_pt-BR=Portugu\u00EAs (Brasil) +locale_fr=Français backToApplication=« Torna all''Applicazione missingParameterMessage=Parametri Mancanti\: {0} diff --git a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties index d2b1b65204..e2a22570ae 100644 --- a/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties +++ b/forms/common-themes/src/main/resources/theme/base/login/messages/messages_pt_BR.properties @@ -194,6 +194,7 @@ locale_de=Deutsch locale_en=English locale_it=Italian locale_pt-BR=Portugu\u00EAs (BR) +locale_fr=Français backToApplication=« Voltar para o aplicativo missingParameterMessage=Par\u00E2metros que faltam\: {0} diff --git a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java index f049085452..9f55e37187 100755 --- a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java +++ b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/FreeMarkerEmailProvider.java @@ -20,6 +20,7 @@ import org.jboss.logging.Logger; import org.keycloak.email.EmailException; import org.keycloak.email.EmailProvider; import org.keycloak.email.freemarker.beans.EventBean; +import org.keycloak.email.freemarker.beans.ProfileBean; import org.keycloak.events.Event; import org.keycloak.events.EventType; import org.keycloak.freemarker.FreeMarkerException; @@ -63,6 +64,7 @@ public class FreeMarkerEmailProvider implements EmailProvider { @Override public void sendEvent(Event event) throws EmailException { Map attributes = new HashMap(); + attributes.put("user", new ProfileBean(user)); attributes.put("event", new EventBean(event)); send(toCamelCase(event.getType()) + "Subject", "event-" + event.getType().toString().toLowerCase() + ".ftl", attributes); @@ -71,6 +73,7 @@ public class FreeMarkerEmailProvider implements EmailProvider { @Override public void sendPasswordReset(String link, long expirationInMinutes) throws EmailException { Map attributes = new HashMap(); + attributes.put("user", new ProfileBean(user)); attributes.put("link", link); attributes.put("linkExpiration", expirationInMinutes); @@ -83,6 +86,7 @@ public class FreeMarkerEmailProvider implements EmailProvider { @Override public void sendExecuteActions(String link, long expirationInMinutes) throws EmailException { Map attributes = new HashMap(); + attributes.put("user", new ProfileBean(user)); attributes.put("link", link); attributes.put("linkExpiration", expirationInMinutes); @@ -96,6 +100,7 @@ public class FreeMarkerEmailProvider implements EmailProvider { @Override public void sendVerifyEmail(String link, long expirationInMinutes) throws EmailException { Map attributes = new HashMap(); + attributes.put("user", new ProfileBean(user)); attributes.put("link", link); attributes.put("linkExpiration", expirationInMinutes); diff --git a/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/beans/ProfileBean.java b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/beans/ProfileBean.java new file mode 100755 index 0000000000..812fb44291 --- /dev/null +++ b/forms/email-freemarker/src/main/java/org/keycloak/email/freemarker/beans/ProfileBean.java @@ -0,0 +1,77 @@ +/* + * JBoss, Home of Professional Open Source. + * Copyright 2012, Red Hat, Inc., and individual contributors + * as indicated by the @author tags. See the copyright.txt file in the + * distribution for a full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ +package org.keycloak.email.freemarker.beans; + +import org.jboss.logging.Logger; +import org.keycloak.models.UserModel; + +import javax.ws.rs.core.MultivaluedMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author Stian Thorgersen + * @author Vlastimil Elias (velias at redhat dot com) + */ +public class ProfileBean { + + private static final Logger logger = Logger.getLogger(ProfileBean.class); + + private UserModel user; + private final Map attributes = new HashMap<>(); + + public ProfileBean(UserModel user) { + this.user = user; + + if (user.getAttributes() != null) { + for (Map.Entry> attr : user.getAttributes().entrySet()) { + List attrValue = attr.getValue(); + if (attrValue != null && attrValue.size() > 0) { + attributes.put(attr.getKey(), attrValue.get(0)); + } + + if (attrValue != null && attrValue.size() > 1) { + logger.warnf("There are more values for attribute '%s' of user '%s' . Will display just first value", attr.getKey(), user.getUsername()); + } + } + } + } + + public String getUsername() { return user.getUsername(); } + + public String getFirstName() { + return user.getFirstName(); + } + + public String getLastName() { + return user.getLastName(); + } + + public String getEmail() { + return user.getEmail(); + } + + public Map getAttributes() { + return attributes; + } +} diff --git a/model/api/src/main/java/org/keycloak/models/ClientModel.java b/model/api/src/main/java/org/keycloak/models/ClientModel.java index ee4317cf5c..8753f45bd0 100755 --- a/model/api/src/main/java/org/keycloak/models/ClientModel.java +++ b/model/api/src/main/java/org/keycloak/models/ClientModel.java @@ -28,6 +28,10 @@ public interface ClientModel extends RoleContainerModel { void setName(String name); + String getDescription(); + + void setDescription(String description); + boolean isEnabled(); void setEnabled(boolean enabled); diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java index 452e2b35d9..7471a4c2b1 100755 --- a/model/api/src/main/java/org/keycloak/models/RealmModel.java +++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java @@ -91,6 +91,9 @@ public interface RealmModel extends RoleContainerModel { void setResetPasswordAllowed(boolean resetPasswordAllowed); + boolean isRevokeRefreshToken(); + void setRevokeRefreshToken(boolean revokeRefreshToken); + int getSsoSessionIdleTimeout(); void setSsoSessionIdleTimeout(int seconds); diff --git a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java index 4c742abc71..aa4f725b09 100755 --- a/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/ClientEntity.java @@ -12,6 +12,7 @@ public class ClientEntity extends AbstractIdentifiableEntity { private String clientId; private String name; + private String description; private String realmId; private boolean enabled; private String clientAuthenticatorType; @@ -61,6 +62,10 @@ public class ClientEntity extends AbstractIdentifiableEntity { this.name = name; } + public String getDescription() { return description; } + + public void setDescription(String description) { this.description = description; } + public boolean isEnabled() { return enabled; } diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java index 3dc43efd32..e5fe6d272f 100755 --- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java @@ -39,6 +39,7 @@ public class RealmEntity extends AbstractIdentifiableEntity { private int failureFactor; //--- end brute force settings + private boolean revokeRefreshToken; private int ssoSessionIdleTimeout; private int ssoSessionMaxLifespan; private int accessTokenLifespan; @@ -229,6 +230,14 @@ public class RealmEntity extends AbstractIdentifiableEntity { this.failureFactor = failureFactor; } + public boolean isRevokeRefreshToken() { + return revokeRefreshToken; + } + + public void setRevokeRefreshToken(boolean revokeRefreshToken) { + this.revokeRefreshToken = revokeRefreshToken; + } + public int getSsoSessionIdleTimeout() { return ssoSessionIdleTimeout; } diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 96b47cb1a4..6b4a6be2cc 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -144,6 +144,7 @@ public class ModelToRepresentation { rep.setVerifyEmail(realm.isVerifyEmail()); rep.setResetPasswordAllowed(realm.isResetPasswordAllowed()); rep.setEditUsernameAllowed(realm.isEditUsernameAllowed()); + rep.setRevokeRefreshToken(realm.isRevokeRefreshToken()); rep.setAccessTokenLifespan(realm.getAccessTokenLifespan()); rep.setSsoSessionIdleTimeout(realm.getSsoSessionIdleTimeout()); rep.setSsoSessionMaxLifespan(realm.getSsoSessionMaxLifespan()); @@ -299,6 +300,7 @@ public class ModelToRepresentation { rep.setId(clientModel.getId()); rep.setClientId(clientModel.getClientId()); rep.setName(clientModel.getName()); + rep.setDescription(clientModel.getDescription()); rep.setEnabled(clientModel.isEnabled()); rep.setAdminUrl(clientModel.getManagementUrl()); rep.setPublicClient(clientModel.isPublicClient()); diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index 62933a04c7..6c61e663f3 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -1,9 +1,5 @@ package org.keycloak.models.utils; -import org.keycloak.models.session.PersistentClientSessionModel; -import org.keycloak.models.session.PersistentUserSessionModel; -import org.keycloak.representations.idm.OfflineClientSessionRepresentation; -import org.keycloak.representations.idm.OfflineUserSessionRepresentation; import org.keycloak.util.Base64; import org.jboss.logging.Logger; import org.keycloak.enums.SslRequired; @@ -100,6 +96,9 @@ public class RepresentationToModel { if (rep.getNotBefore() != null) newRealm.setNotBefore(rep.getNotBefore()); + if (rep.getRevokeRefreshToken() != null) newRealm.setRevokeRefreshToken(rep.getRevokeRefreshToken()); + else newRealm.setRevokeRefreshToken(false); + if (rep.getAccessTokenLifespan() != null) newRealm.setAccessTokenLifespan(rep.getAccessTokenLifespan()); else newRealm.setAccessTokenLifespan(300); @@ -532,6 +531,7 @@ public class RepresentationToModel { if (rep.getAccessCodeLifespanUserAction() != null) realm.setAccessCodeLifespanUserAction(rep.getAccessCodeLifespanUserAction()); if (rep.getAccessCodeLifespanLogin() != null) realm.setAccessCodeLifespanLogin(rep.getAccessCodeLifespanLogin()); if (rep.getNotBefore() != null) realm.setNotBefore(rep.getNotBefore()); + if (rep.getRevokeRefreshToken() != null) realm.setRevokeRefreshToken(rep.getRevokeRefreshToken()); if (rep.getAccessTokenLifespan() != null) realm.setAccessTokenLifespan(rep.getAccessTokenLifespan()); if (rep.getSsoSessionIdleTimeout() != null) realm.setSsoSessionIdleTimeout(rep.getSsoSessionIdleTimeout()); if (rep.getSsoSessionMaxLifespan() != null) realm.setSsoSessionMaxLifespan(rep.getSsoSessionMaxLifespan()); @@ -692,6 +692,7 @@ public class RepresentationToModel { ClientModel client = resourceRep.getId()!=null ? realm.addClient(resourceRep.getId(), resourceRep.getClientId()) : realm.addClient(resourceRep.getClientId()); if (resourceRep.getName() != null) client.setName(resourceRep.getName()); + if(resourceRep.getDescription() != null) client.setDescription(resourceRep.getDescription()); if (resourceRep.isEnabled() != null) client.setEnabled(resourceRep.isEnabled()); client.setManagementUrl(resourceRep.getAdminUrl()); if (resourceRep.isSurrogateAuthRequired() != null) @@ -793,6 +794,7 @@ public class RepresentationToModel { public static void updateClient(ClientRepresentation rep, ClientModel resource) { if (rep.getClientId() != null) resource.setClientId(rep.getClientId()); if (rep.getName() != null) resource.setName(rep.getName()); + if (rep.getDescription() != null) resource.setDescription(rep.getDescription()); if (rep.isEnabled() != null) resource.setEnabled(rep.isEnabled()); if (rep.isBearerOnly() != null) resource.setBearerOnly(rep.isBearerOnly()); if (rep.isConsentRequired() != null) resource.setConsentRequired(rep.isConsentRequired()); diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/ClientAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/ClientAdapter.java index 1ddc36499b..81e60ee295 100755 --- a/model/file/src/main/java/org/keycloak/models/file/adapter/ClientAdapter.java +++ b/model/file/src/main/java/org/keycloak/models/file/adapter/ClientAdapter.java @@ -78,6 +78,12 @@ public class ClientAdapter implements ClientModel { entity.setName(name); } + @Override + public String getDescription() { return entity.getDescription(); } + + @Override + public void setDescription(String description) { entity.setDescription(description); } + @Override public Set getWebOrigins() { Set result = new HashSet(); diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java index 5a62223970..26227b1ee4 100755 --- a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java +++ b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java @@ -325,6 +325,16 @@ public class RealmAdapter implements RealmModel { } + @Override + public boolean isRevokeRefreshToken() { + return realm.isRevokeRefreshToken(); + } + + @Override + public void setRevokeRefreshToken(boolean revokeRefreshToken) { + realm.setRevokeRefreshToken(revokeRefreshToken); + } + @Override public int getSsoSessionIdleTimeout() { return realm.getSsoSessionIdleTimeout(); diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java index e1819787ce..477c5d6ab2 100755 --- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java +++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java @@ -321,6 +321,18 @@ public class ClientAdapter implements ClientModel { updated.setName(name); } + @Override + public String getDescription() { + if (updated != null) return updated.getDescription(); + return cached.getDescription(); + } + + @Override + public void setDescription(String description) { + getDelegateForUpdate(); + updated.setDescription(description); + } + @Override public boolean isSurrogateAuthRequired() { if (updated != null) return updated.isSurrogateAuthRequired(); diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java index 54700e23f0..51d445c890 100755 --- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java +++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java @@ -239,6 +239,18 @@ public class RealmAdapter implements RealmModel { updated.setEditUsernameAllowed(editUsernameAllowed); } + @Override + public boolean isRevokeRefreshToken() { + if (updated != null) return updated.isRevokeRefreshToken(); + return cached.isRevokeRefreshToken(); + } + + @Override + public void setRevokeRefreshToken(boolean revokeRefreshToken) { + getDelegateForUpdate(); + updated.setRevokeRefreshToken(revokeRefreshToken); + } + @Override public int getSsoSessionIdleTimeout() { if (updated != null) return updated.getSsoSessionIdleTimeout(); diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java index 3015acfba6..2d582222a8 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java @@ -25,6 +25,7 @@ public class CachedClient implements Serializable { private String id; private String clientId; private String name; + private String description; private String realm; private Set redirectUris = new HashSet(); private boolean enabled; @@ -58,6 +59,7 @@ public class CachedClient implements Serializable { secret = model.getSecret(); clientId = model.getClientId(); name = model.getName(); + description = model.getDescription(); this.realm = realm.getId(); enabled = model.isEnabled(); protocol = model.getProtocol(); @@ -103,6 +105,10 @@ public class CachedClient implements Serializable { return name; } + public String getDescription() { return description; } + + public void setDescription(String description) { this.description = description; } + public String getRealm() { return realm; } diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java index 27bab7468d..5193588d44 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java @@ -55,6 +55,7 @@ public class CachedRealm implements Serializable { private int failureFactor; //--- end brute force settings + private boolean revokeRefreshToken; private int ssoSessionIdleTimeout; private int ssoSessionMaxLifespan; private int accessTokenLifespan; @@ -136,6 +137,7 @@ public class CachedRealm implements Serializable { failureFactor = model.getFailureFactor(); //--- end brute force settings + revokeRefreshToken = model.isRevokeRefreshToken(); ssoSessionIdleTimeout = model.getSsoSessionIdleTimeout(); ssoSessionMaxLifespan = model.getSsoSessionMaxLifespan(); accessTokenLifespan = model.getAccessTokenLifespan(); @@ -313,6 +315,10 @@ public class CachedRealm implements Serializable { return editUsernameAllowed; } + public boolean isRevokeRefreshToken() { + return revokeRefreshToken; + } + public int getSsoSessionIdleTimeout() { return ssoSessionIdleTimeout; } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java index 1e0c21edaa..3baddd19a7 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ClientAdapter.java @@ -65,6 +65,12 @@ public class ClientAdapter implements ClientModel { entity.setName(name); } + @Override + public String getDescription() { return entity.getDescription(); } + + @Override + public void setDescription(String description) { entity.setDescription(description); } + @Override public boolean isEnabled() { return entity.isEnabled(); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index 6268735339..2c8e2ad9fc 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -336,6 +336,16 @@ public class RealmAdapter implements RealmModel { realm.setNotBefore(notBefore); } + @Override + public boolean isRevokeRefreshToken() { + return realm.isRevokeRefreshToken(); + } + + @Override + public void setRevokeRefreshToken(boolean revokeRefreshToken) { + realm.setRevokeRefreshToken(revokeRefreshToken); + } + @Override public int getAccessTokenLifespan() { return realm.getAccessTokenLifespan(); diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java index b0a30cd67f..f26f65126e 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/ClientEntity.java @@ -34,6 +34,8 @@ public class ClientEntity { private String id; @Column(name = "NAME") private String name; + @Column(name = "DESCRIPTION") + private String description; @Column(name = "CLIENT_ID") private String clientId; @Column(name="ENABLED") @@ -143,6 +145,14 @@ public class ClientEntity { this.name = name; } + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + public boolean isEnabled() { return enabled; } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java index fbfb700b24..27c4824f94 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java @@ -76,6 +76,8 @@ public class RealmEntity { @Column(name="EDIT_USERNAME_ALLOWED") protected boolean editUsernameAllowed; + @Column(name="REVOKE_REFRESH_TOKEN") + private boolean revokeRefreshToken; @Column(name="SSO_IDLE_TIMEOUT") private int ssoSessionIdleTimeout; @Column(name="SSO_MAX_LIFESPAN") @@ -288,6 +290,14 @@ public class RealmEntity { this.editUsernameAllowed = editUsernameAllowed; } + public boolean isRevokeRefreshToken() { + return revokeRefreshToken; + } + + public void setRevokeRefreshToken(boolean revokeRefreshToken) { + this.revokeRefreshToken = revokeRefreshToken; + } + public int getSsoSessionIdleTimeout() { return ssoSessionIdleTimeout; } diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java index 4e1b4faf23..e99f142297 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ClientAdapter.java @@ -70,6 +70,15 @@ public class ClientAdapter extends AbstractMongoAdapter imple updateMongoEntity(); } + @Override + public String getDescription() { return getMongoEntity().getDescription(); } + + @Override + public void setDescription(String description) { + getMongoEntity().setDescription(description); + updateMongoEntity(); + } + @Override public void setClientId(String clientId) { getMongoEntity().setClientId(clientId); diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index a6166cd5e2..05ae8bcd4c 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -311,6 +311,16 @@ public class RealmAdapter extends AbstractMongoAdapter impleme updateRealm(); } + @Override + public boolean isRevokeRefreshToken() { + return realm.isRevokeRefreshToken(); + } + + @Override + public void setRevokeRefreshToken(boolean revokeRefreshToken) { + realm.setRevokeRefreshToken(revokeRefreshToken); + updateRealm(); + } @Override public int getSsoSessionIdleTimeout() { diff --git a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java index e899186c81..0888dab878 100755 --- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java @@ -161,6 +161,15 @@ public class TokenManager { } int currentTime = Time.currentTime(); + + if (realm.isRevokeRefreshToken() && !refreshToken.getType().equals(TokenUtil.TOKEN_TYPE_OFFLINE)) { + if (refreshToken.getIssuedAt() < validation.clientSession.getTimestamp()) { + throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Stale token"); + } + + validation.clientSession.setTimestamp(currentTime); + } + validation.userSession.setLastSessionRefresh(currentTime); AccessTokenResponseBuilder responseBuilder = responseBuilder(realm, authorizedClient, event, session, validation.userSession, validation.clientSession) diff --git a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java index e780dbf9b1..5c80bca40e 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/ClientsResource.java @@ -73,6 +73,7 @@ public class ClientsResource { ClientRepresentation client = new ClientRepresentation(); client.setId(clientModel.getId()); client.setClientId(clientModel.getClientId()); + client.setDescription(clientModel.getDescription()); rep.add(client); } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AbstractClientTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AbstractClientTest.java index cfc9270b3d..e3d6a5713a 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AbstractClientTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AbstractClientTest.java @@ -13,6 +13,8 @@ import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.services.managers.RealmManager; import org.keycloak.testsuite.rule.KeycloakRule; +import org.keycloak.testsuite.rule.WebResource; +import org.openqa.selenium.WebDriver; import java.util.Arrays; import java.util.Collections; @@ -41,6 +43,7 @@ public abstract class AbstractClientTest { public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { RealmModel testRealm = manager.createRealm(REALM_NAME); testRealm.setEnabled(true); + testRealm.setAccessCodeLifespanUserAction(600); KeycloakModelUtils.generateRealmKeys(testRealm); } }); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientTest.java index 209717571e..cf2df9d9aa 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ClientTest.java @@ -48,6 +48,7 @@ public class ClientTest extends AbstractClientTest { private String createClient() { ClientRepresentation rep = new ClientRepresentation(); rep.setClientId("my-app"); + rep.setDescription("my-app description"); rep.setEnabled(true); Response response = realm.clients().create(rep); response.close(); @@ -79,6 +80,19 @@ public class ClientTest extends AbstractClientTest { assertTrue(rep.isEnabled()); } + /** + * See KEYCLOAK-1918 + */ + @Test + public void getClientDescription() { + + String id = createClient(); + + ClientRepresentation rep = realm.clients().get(id).toRepresentation(); + assertEquals(id, rep.getId()); + assertEquals("my-app description", rep.getDescription()); + } + @Test public void getClientSessions() throws Exception { OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("password", "test-user@localhost", "password"); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java index de1b2ca3cc..f262f34bd8 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/UserTest.java @@ -1,18 +1,37 @@ package org.keycloak.testsuite.admin; import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.keycloak.admin.client.resource.IdentityProviderResource; import org.keycloak.admin.client.resource.UserResource; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.idm.ErrorRepresentation; import org.keycloak.representations.idm.FederatedIdentityRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.testsuite.forms.ResetPasswordTest; +import org.keycloak.testsuite.pages.LoginPasswordResetPage; +import org.keycloak.testsuite.pages.LoginPasswordUpdatePage; +import org.keycloak.testsuite.rule.GreenMailRule; +import org.keycloak.testsuite.rule.KeycloakRule; +import org.keycloak.testsuite.rule.WebResource; +import org.keycloak.testsuite.rule.WebRule; +import org.openqa.selenium.WebDriver; +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; +import javax.ws.rs.BadRequestException; import javax.ws.rs.ClientErrorException; import javax.ws.rs.core.Response; +import java.io.IOException; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -27,6 +46,31 @@ import static org.junit.Assert.fail; */ public class UserTest extends AbstractClientTest { + @Rule + public WebRule webRule = new WebRule(this); + + @Rule + public GreenMailRule greenMail = new GreenMailRule(); + + @WebResource + protected LoginPasswordUpdatePage passwordUpdatePage; + + @WebResource + protected WebDriver driver; + + @Before + public void before() { + super.before(); + + keycloakRule.configure(new KeycloakRule.KeycloakSetup() { + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + RealmModel testRealm = manager.getRealm(REALM_NAME); + greenMail.configureRealm(testRealm); + } + }); + } + public String createUser() { return createUser("user1", "user1@localhost"); } @@ -396,6 +440,40 @@ public class UserTest extends AbstractClientTest { } } + @Test + public void sendResetPasswordEmailSuccess() throws IOException, MessagingException { + UserRepresentation userRep = new UserRepresentation(); + userRep.setEnabled(true); + userRep.setUsername("user1"); + userRep.setEmail("user1@test.com"); + Response response = realm.users().create(userRep); + String id = ApiUtil.getCreatedId(response); + response.close(); + UserResource user = realm.users().get(id); + List actions = new LinkedList<>(); + actions.add(UserModel.RequiredAction.UPDATE_PASSWORD.name()); + user.executeActionsEmail("account", actions); + + Assert.assertEquals(1, greenMail.getReceivedMessages().length); + + MimeMessage message = greenMail.getReceivedMessages()[0]; + + String link = ResetPasswordTest.getPasswordResetEmailLink(message); + + driver.navigate().to(link); + + assertTrue(passwordUpdatePage.isCurrent()); + + passwordUpdatePage.changePassword("new-pass", "new-pass"); + + assertEquals("Your account has been updated.", driver.getTitle()); + + driver.navigate().to(link); + + assertEquals("We're sorry...", driver.getTitle()); + } + + @Test public void sendVerifyEmail() { UserRepresentation userRep = new UserRepresentation(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java index 788b7881e1..ab47f49734 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/ResetPasswordTest.java @@ -630,7 +630,7 @@ public class ResetPasswordTest { } } - private String getPasswordResetEmailLink(MimeMessage message) throws IOException, MessagingException { + public static String getPasswordResetEmailLink(MimeMessage message) throws IOException, MessagingException { Multipart multipart = (Multipart) message.getContent(); final String textContentType = multipart.getBodyPart(0).getContentType(); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ClientModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ClientModelTest.java index eff25e390f..bb47ebe523 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ClientModelTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ClientModelTest.java @@ -32,6 +32,7 @@ public class ClientModelTest extends AbstractModelTest { realm = realmManager.createRealm("original"); client = realm.addClient("application"); client.setName("Application"); + client.setDescription("Description"); client.setBaseUrl("http://base"); client.setManagementUrl("http://management"); client.setClientId("app-name"); @@ -87,6 +88,7 @@ public class ClientModelTest extends AbstractModelTest { public static void assertEquals(ClientModel expected, ClientModel actual) { Assert.assertEquals(expected.getClientId(), actual.getClientId()); Assert.assertEquals(expected.getName(), actual.getName()); + Assert.assertEquals(expected.getDescription(), actual.getDescription()); Assert.assertEquals(expected.getBaseUrl(), actual.getBaseUrl()); Assert.assertEquals(expected.getManagementUrl(), actual.getManagementUrl()); Assert.assertEquals(expected.getDefaultRoles(), actual.getDefaultRoles()); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java index 363a1e945b..742f5d6703 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/RefreshTokenTest.java @@ -30,6 +30,7 @@ import org.keycloak.enums.SslRequired; import org.keycloak.events.Details; import org.keycloak.events.Errors; import org.keycloak.events.Event; +import org.keycloak.events.EventType; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; import org.keycloak.models.UserSessionModel; @@ -181,6 +182,93 @@ public class RefreshTokenTest { Time.setOffset(0); } + @Test + public void refreshTokenReuseTokenWithoutRefreshTokensRevoked() throws Exception { + try { + oauth.doLogin("test-user@localhost", "password"); + + Event loginEvent = events.expectLogin().assertEvent(); + + String sessionId = loginEvent.getSessionId(); + String codeId = loginEvent.getDetails().get(Details.CODE_ID); + + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + + AccessTokenResponse response1 = oauth.doAccessTokenRequest(code, "password"); + RefreshToken refreshToken1 = oauth.verifyRefreshToken(response1.getRefreshToken()); + + events.expectCodeToToken(codeId, sessionId).assertEvent(); + + Time.setOffset(2); + + AccessTokenResponse response2 = oauth.doRefreshTokenRequest(response1.getRefreshToken(), "password"); + Assert.assertEquals(200, response2.getStatusCode()); + + events.expectRefresh(refreshToken1.getId(), sessionId).assertEvent(); + + AccessTokenResponse response3 = oauth.doRefreshTokenRequest(response1.getRefreshToken(), "password"); + + Assert.assertEquals(200, response3.getStatusCode()); + + events.expectRefresh(refreshToken1.getId(), sessionId).assertEvent(); + } finally { + Time.setOffset(0); + } + } + + @Test + public void refreshTokenReuseTokenWithRefreshTokensRevoked() throws Exception { + try { + keycloakRule.configure(new KeycloakRule.KeycloakSetup() { + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + appRealm.setRevokeRefreshToken(true); + } + }); + + oauth.doLogin("test-user@localhost", "password"); + + Event loginEvent = events.expectLogin().assertEvent(); + + String sessionId = loginEvent.getSessionId(); + String codeId = loginEvent.getDetails().get(Details.CODE_ID); + + String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE); + + AccessTokenResponse response1 = oauth.doAccessTokenRequest(code, "password"); + RefreshToken refreshToken1 = oauth.verifyRefreshToken(response1.getRefreshToken()); + + events.expectCodeToToken(codeId, sessionId).assertEvent(); + + Time.setOffset(2); + + AccessTokenResponse response2 = oauth.doRefreshTokenRequest(response1.getRefreshToken(), "password"); + RefreshToken refreshToken2 = oauth.verifyRefreshToken(response2.getRefreshToken()); + + Assert.assertEquals(200, response2.getStatusCode()); + + events.expectRefresh(refreshToken1.getId(), sessionId).assertEvent(); + + AccessTokenResponse response3 = oauth.doRefreshTokenRequest(response1.getRefreshToken(), "password"); + + Assert.assertEquals(400, response3.getStatusCode()); + + events.expectRefresh(refreshToken1.getId(), sessionId).removeDetail(Details.TOKEN_ID).removeDetail(Details.UPDATED_REFRESH_TOKEN_ID).error("invalid_token").assertEvent(); + + oauth.doRefreshTokenRequest(response2.getRefreshToken(), "password"); + + events.expectRefresh(refreshToken2.getId(), sessionId).assertEvent(); + } finally { + Time.setOffset(0); + keycloakRule.configure(new KeycloakRule.KeycloakSetup() { + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + appRealm.setRevokeRefreshToken(false); + } + }); + } + } + PrivateKey privateKey; PublicKey publicKey; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java index 23cb7f260b..c07766d227 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java @@ -11,10 +11,7 @@ import org.junit.Test; import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator; import org.keycloak.events.Details; import org.keycloak.events.Errors; -import org.keycloak.models.ClientModel; -import org.keycloak.models.RealmModel; -import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserModel; +import org.keycloak.models.*; import org.keycloak.representations.AccessToken; import org.keycloak.representations.RefreshToken; import org.keycloak.services.managers.ClientManager; @@ -24,6 +21,7 @@ import org.keycloak.testsuite.OAuthClient; import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.WebResource; import org.keycloak.testsuite.rule.WebRule; +import org.keycloak.util.Time; import org.openqa.selenium.WebDriver; import static org.junit.Assert.assertEquals; @@ -233,7 +231,47 @@ public class ResourceOwnerPasswordCredentialsGrantTest { } + @Test + public void grantAccessTokenExpiredPassword() throws Exception { + keycloakRule.update(new KeycloakRule.KeycloakSetup() { + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + appRealm.setPasswordPolicy(new PasswordPolicy("forceExpiredPasswordChange(1)")); + } + }); + try { + Time.setOffset(60 * 60 * 48); + + oauth.clientId("resource-owner"); + + OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "test-user@localhost", "password"); + + assertEquals(400, response.getStatusCode()); + + assertEquals("invalid_grant", response.getError()); + assertEquals("Account is not fully set up", response.getErrorDescription()); + + events.expectLogin() + .client("resource-owner") + .session((String) null) + .clearDetails() + .error(Errors.RESOLVE_REQUIRED_ACTIONS) + .user((String) null) + .assertEvent(); + } finally { + Time.setOffset(0); + + keycloakRule.update(new KeycloakRule.KeycloakSetup() { + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + appRealm.setPasswordPolicy(new PasswordPolicy("")); + UserModel user = manager.getSession().users().getUserByEmail("test-user@localhost", appRealm); + user.removeRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD); + } + }); + } + } @Test diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/GreenMailRule.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/GreenMailRule.java index bd52a59c7c..21a35a649e 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/GreenMailRule.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/rule/GreenMailRule.java @@ -24,10 +24,13 @@ package org.keycloak.testsuite.rule; import com.icegreen.greenmail.util.GreenMail; import com.icegreen.greenmail.util.ServerSetup; import org.junit.rules.ExternalResource; +import org.keycloak.models.RealmModel; import javax.mail.internet.MimeMessage; import java.lang.Thread.UncaughtExceptionHandler; import java.net.SocketException; +import java.util.HashMap; +import java.util.Map; /** * @author Stian Thorgersen @@ -63,6 +66,14 @@ public class GreenMailRule extends ExternalResource { } } + public void configureRealm(RealmModel realm) { + Map config = new HashMap<>(); + config.put("from", "auto@keycloak.org"); + config.put("host", "localhost"); + config.put("port", "3025"); + realm.setSmtpConfig(config); + } + public MimeMessage[] getReceivedMessages() { return greenMail.getReceivedMessages(); }