keycloak-aplcache
Changes
forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/ApplicationsBean.java 9(+4 -5)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-detail.html 7(+0 -7)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html 7(+7 -0)
forms/common-themes/src/main/resources/theme/base/admin/resources/partials/role-detail.html 7(+7 -0)
model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/ClientAdapter.java 12(+0 -12)
model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java 12(+12 -0)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedClient.java 6(+0 -6)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRole.java 6(+6 -0)
testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/roles/RoleForm.java 15(+14 -1)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/ClientRolesTest.java 2(+1 -1)
Details
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 061eaa3..24902d5 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
@@ -2,8 +2,8 @@
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.1.xsd">
<changeSet author="mposolda@redhat.com" id="1.6.0">
- <addColumn tableName="CLIENT">
- <column name="OFFLINE_TOKENS_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
+ <addColumn tableName="KEYCLOAK_ROLE">
+ <column name="SCOPE_PARAM_REQUIRED" type="BOOLEAN" defaultValueBoolean="false">
<constraints nullable="false"/>
</column>
</addColumn>
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 42618a1..020445e 100755
--- a/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/ClientRepresentation.java
@@ -24,7 +24,6 @@ public class ClientRepresentation {
protected Boolean bearerOnly;
protected Boolean consentRequired;
protected Boolean serviceAccountsEnabled;
- protected Boolean offlineTokensEnabled;
protected Boolean directGrantsOnly;
protected Boolean publicClient;
protected Boolean frontchannelLogout;
@@ -163,14 +162,6 @@ public class ClientRepresentation {
this.serviceAccountsEnabled = serviceAccountsEnabled;
}
- public Boolean isOfflineTokensEnabled() {
- return offlineTokensEnabled;
- }
-
- public void setOfflineTokensEnabled(Boolean offlineTokensEnabled) {
- this.offlineTokensEnabled = offlineTokensEnabled;
- }
-
public Boolean isDirectGrantsOnly() {
return directGrantsOnly;
}
diff --git a/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java
index c335ab4..4100785 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RoleRepresentation.java
@@ -12,6 +12,7 @@ public class RoleRepresentation {
protected String id;
protected String name;
protected String description;
+ protected Boolean scopeParamRequired;
protected boolean composite;
protected Composites composites;
@@ -46,9 +47,10 @@ public class RoleRepresentation {
public RoleRepresentation() {
}
- public RoleRepresentation(String name, String description) {
+ public RoleRepresentation(String name, String description, boolean scopeParamRequired) {
this.name = name;
this.description = description;
+ this.scopeParamRequired = scopeParamRequired;
}
public String getId() {
@@ -75,6 +77,14 @@ public class RoleRepresentation {
this.description = description;
}
+ public Boolean isScopeParamRequired() {
+ return scopeParamRequired;
+ }
+
+ public void setScopeParamRequired(Boolean scopeParamRequired) {
+ this.scopeParamRequired = scopeParamRequired;
+ }
+
public Composites getComposites() {
return composites;
}
diff --git a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/ApplicationsBean.java b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/ApplicationsBean.java
index 63e04db..371b5d0 100644
--- a/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/ApplicationsBean.java
+++ b/forms/account-freemarker/src/main/java/org/keycloak/account/freemarker/model/ApplicationsBean.java
@@ -5,7 +5,6 @@ import java.util.LinkedList;
import java.util.List;
import java.util.Set;
-import org.keycloak.OAuth2Constants;
import org.keycloak.models.ClientModel;
import org.keycloak.models.UserConsentModel;
import org.keycloak.models.ProtocolMapperModel;
@@ -13,7 +12,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.TokenManager;
-import org.keycloak.services.offline.OfflineUserSessionManager;
+import org.keycloak.services.offline.OfflineTokenUtils;
import org.keycloak.util.MultivaluedHashMap;
/**
@@ -25,7 +24,7 @@ public class ApplicationsBean {
public ApplicationsBean(RealmModel realm, UserModel user) {
- Set<ClientModel> offlineClients = new OfflineUserSessionManager().findClientsWithOfflineToken(realm, user);
+ Set<ClientModel> offlineClients = OfflineTokenUtils.findClientsWithOfflineToken(realm, user);
List<ClientModel> realmClients = realm.getClients();
for (ClientModel client : realmClients) {
@@ -34,7 +33,7 @@ public class ApplicationsBean {
continue;
}
- Set<RoleModel> availableRoles = TokenManager.getAccess(null, client, user);
+ Set<RoleModel> availableRoles = TokenManager.getAccess(null, false, client, user);
// Don't show applications, which user doesn't have access into (any available roles)
if (availableRoles.isEmpty()) {
continue;
@@ -60,7 +59,7 @@ public class ApplicationsBean {
List<String> additionalGrants = new ArrayList<>();
if (offlineClients.contains(client)) {
- additionalGrants.add("${offlineAccess}");
+ additionalGrants.add("${offlineToken}");
}
ApplicationEntry appEntry = new ApplicationEntry(realmRolesAvailable, resourceRolesAvailable, realmRolesGranted, resourceRolesGranted, client,
diff --git a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties
index b0ea19c..23a112d 100755
--- a/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties
+++ b/forms/common-themes/src/main/resources/theme/base/account/messages/messages_en.properties
@@ -52,6 +52,7 @@ role_manage-events=Manage events
role_view-profile=View profile
role_manage-account=Manage account
role_read-token=Read token
+role_offline-access=Offline access
client_account=Account
client_security-admin-console=Security Admin Console
client_realm-management=Realm Management
@@ -89,7 +90,7 @@ additionalGrants=Additional Grants
action=Action
inResource=in
fullAccess=Full Access
-offlineAccess=Offline Access
+offlineToken=Offline Token
revoke=Revoke Grant
configureAuthenticators=Configured Authenticators
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 303e439..06c939b 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
@@ -79,13 +79,6 @@
<input ng-model="client.serviceAccountsEnabled" name="serviceAccountsEnabled" id="serviceAccountsEnabled" onoffswitch />
</div>
</div>
- <div class="form-group" data-ng-show="protocol == 'openid-connect' && !client.bearerOnly">
- <label class="col-md-2 control-label" for="offlineTokensEnabled">Offline Tokens Enabled</label>
- <kc-tooltip>Allows you to retrieve offline tokens for users. Offline token can be stored by client application and is valid even if user is not logged anymore.</kc-tooltip>
- <div class="col-md-6">
- <input ng-model="client.offlineTokensEnabled" name="offlineTokensEnabled" id="offlineTokensEnabled" onoffswitch />
- </div>
- </div>
<div class="form-group clearfix block" data-ng-show="protocol == 'saml'">
<label class="col-md-2 control-label" for="samlServerSignature">Include AuthnStatement</label>
<div class="col-sm-6">
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html
index c0ce766..f93c96e 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/client-role-detail.html
@@ -32,6 +32,13 @@
required> -->
</div>
</div>
+ <div class="form-group">
+ <label class="col-md-2 control-label" for="scopeParamRequired">Scope Param Required </label>
+ <kc-tooltip>This role will be granted just if scope parameter with role name is used during authorization/token request.</kc-tooltip>
+ <div class="col-md-6">
+ <input ng-model="role.scopeParamRequired" name="scopeParamRequired" id="scopeParamRequired" onoffswitch />
+ </div>
+ </div>
<div class="form-group clearfix block" data-ng-hide="create">
<label class="col-md-2 control-label" for="compositeSwitch" class="control-label">Composite Roles</label>
<div class="col-md-6">
diff --git a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/role-detail.html b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/role-detail.html
index 49d09c8..6bf854e 100755
--- a/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/role-detail.html
+++ b/forms/common-themes/src/main/resources/theme/base/admin/resources/partials/role-detail.html
@@ -28,6 +28,13 @@
<textarea class="form-control" rows="5" cols="50" id="description" name="description" data-ng-model="role.description"></textarea>
</div>
</div>
+ <div class="form-group">
+ <label class="col-md-2 control-label" for="scopeParamRequired">Scope Param Required </label>
+ <kc-tooltip>This role will be granted just if scope parameter with role name is used during authorization/token request.</kc-tooltip>
+ <div class="col-md-6">
+ <input ng-model="role.scopeParamRequired" name="scopeParamRequired" id="scopeParamRequired" onoffswitch />
+ </div>
+ </div>
<div class="form-group" data-ng-hide="create">
<label class="col-md-2 control-label" for="compositeSwitch" class="control-label">Composite Roles</label>
<div class="col-md-6">
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 3dfb8ea..94b0627 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
@@ -104,6 +104,7 @@ role_manage-events=Manage events
role_view-profile=View profile
role_manage-account=Manage account
role_read-token=Read token
+role_offline-access=Offline access
client_account=Account
client_security-admin-console=Security Admin Console
client_realm-management=Realm Management
diff --git a/model/api/src/main/java/org/keycloak/migration/MigrationModel.java b/model/api/src/main/java/org/keycloak/migration/MigrationModel.java
index 792822e..beb2f98 100755
--- a/model/api/src/main/java/org/keycloak/migration/MigrationModel.java
+++ b/model/api/src/main/java/org/keycloak/migration/MigrationModel.java
@@ -11,7 +11,7 @@ public interface MigrationModel {
/**
* Must have the form of major.minor.micro as the version is parsed and numbers are compared
*/
- public static final String LATEST_VERSION = "1.5.0";
+ public static final String LATEST_VERSION = "1.6.0";
String getStoredVersion();
void setStoredVersion(String version);
diff --git a/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java b/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java
index 61b6244..e5c9484 100755
--- a/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java
+++ b/model/api/src/main/java/org/keycloak/migration/MigrationModelManager.java
@@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
import org.keycloak.migration.migrators.MigrateTo1_3_0;
import org.keycloak.migration.migrators.MigrateTo1_4_0;
import org.keycloak.migration.migrators.MigrateTo1_5_0;
+import org.keycloak.migration.migrators.MigrateTo1_6_0;
import org.keycloak.migration.migrators.MigrationTo1_2_0_CR1;
import org.keycloak.models.KeycloakSession;
@@ -47,6 +48,12 @@ public class MigrationModelManager {
}
new MigrateTo1_5_0().migrate(session);
}
+ if (stored == null || stored.lessThan(MigrateTo1_6_0.VERSION)) {
+ if (stored != null) {
+ logger.debug("Migrating older model to 1.6.0 updates");
+ }
+ new MigrateTo1_6_0().migrate(session);
+ }
model.setStoredVersion(MigrationModel.LATEST_VERSION);
}
diff --git a/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java
new file mode 100644
index 0000000..8010586
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/migration/migrators/MigrateTo1_6_0.java
@@ -0,0 +1,25 @@
+package org.keycloak.migration.migrators;
+
+import java.util.List;
+
+import org.keycloak.migration.ModelVersion;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class MigrateTo1_6_0 {
+
+ public static final ModelVersion VERSION = new ModelVersion("1.6.0");
+
+ public void migrate(KeycloakSession session) {
+ List<RealmModel> realms = session.realms().getRealms();
+ for (RealmModel realm : realms) {
+ KeycloakModelUtils.setupOfflineTokens(realm);
+ }
+
+ }
+
+}
diff --git a/model/api/src/main/java/org/keycloak/migration/migrators/MigrationTo1_2_0_CR1.java b/model/api/src/main/java/org/keycloak/migration/migrators/MigrationTo1_2_0_CR1.java
index c9e10ad..1aa4929 100755
--- a/model/api/src/main/java/org/keycloak/migration/migrators/MigrationTo1_2_0_CR1.java
+++ b/model/api/src/main/java/org/keycloak/migration/migrators/MigrationTo1_2_0_CR1.java
@@ -5,6 +5,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.List;
@@ -26,7 +27,9 @@ public class MigrationTo1_2_0_CR1 {
client.setFullScopeAllowed(false);
for (String role : Constants.BROKER_SERVICE_ROLES) {
- client.addRole(role).setDescription("${role_"+ role.toLowerCase().replaceAll("_", "-") +"}");
+ RoleModel roleModel = client.addRole(role);
+ roleModel.setDescription("${role_" + role.toLowerCase().replaceAll("_", "-") + "}");
+ roleModel.setScopeParamRequired(false);
}
}
}
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 6461a33..daccf8e 100755
--- a/model/api/src/main/java/org/keycloak/models/ClientModel.java
+++ b/model/api/src/main/java/org/keycloak/models/ClientModel.java
@@ -109,9 +109,6 @@ public interface ClientModel extends RoleContainerModel {
boolean isServiceAccountsEnabled();
void setServiceAccountsEnabled(boolean serviceAccountsEnabled);
- boolean isOfflineTokensEnabled();
- void setOfflineTokensEnabled(boolean offlineTokensEnabled);
-
Set<RoleModel> getScopeMappings();
void addScopeMapping(RoleModel role);
void deleteScopeMapping(RoleModel role);
diff --git a/model/api/src/main/java/org/keycloak/models/Constants.java b/model/api/src/main/java/org/keycloak/models/Constants.java
index b1adbfa..5fe3189 100755
--- a/model/api/src/main/java/org/keycloak/models/Constants.java
+++ b/model/api/src/main/java/org/keycloak/models/Constants.java
@@ -1,5 +1,7 @@
package org.keycloak.models;
+import org.keycloak.OAuth2Constants;
+
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
@@ -16,4 +18,5 @@ public interface Constants {
String INSTALLED_APP_URL = "http://localhost";
String READ_TOKEN_ROLE = "read-token";
String[] BROKER_SERVICE_ROLES = {READ_TOKEN_ROLE};
+ String OFFLINE_ACCESS_ROLE = OAuth2Constants.OFFLINE_ACCESS;
}
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 83fa12d..52e9721 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
@@ -28,7 +28,6 @@ public class ClientEntity extends AbstractIdentifiableEntity {
private boolean bearerOnly;
private boolean consentRequired;
private boolean serviceAccountsEnabled;
- private boolean offlineTokensEnabled;
private boolean directGrantsOnly;
private int nodeReRegistrationTimeout;
@@ -229,14 +228,6 @@ public class ClientEntity extends AbstractIdentifiableEntity {
this.serviceAccountsEnabled = serviceAccountsEnabled;
}
- public boolean isOfflineTokensEnabled() {
- return offlineTokensEnabled;
- }
-
- public void setOfflineTokensEnabled(boolean offlineTokensEnabled) {
- this.offlineTokensEnabled = offlineTokensEnabled;
- }
-
public boolean isDirectGrantsOnly() {
return directGrantsOnly;
}
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RoleEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RoleEntity.java
index a610d39..4b4551e 100644
--- a/model/api/src/main/java/org/keycloak/models/entities/RoleEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RoleEntity.java
@@ -9,6 +9,7 @@ public class RoleEntity extends AbstractIdentifiableEntity {
private String name;
private String description;
+ private boolean scopeParamRequired;
private List<String> compositeRoleIds;
@@ -31,6 +32,14 @@ public class RoleEntity extends AbstractIdentifiableEntity {
this.description = description;
}
+ public boolean isScopeParamRequired() {
+ return scopeParamRequired;
+ }
+
+ public void setScopeParamRequired(boolean scopeParamRequired) {
+ this.scopeParamRequired = scopeParamRequired;
+ }
+
public List<String> getCompositeRoleIds() {
return compositeRoleIds;
}
diff --git a/model/api/src/main/java/org/keycloak/models/ImpersonationConstants.java b/model/api/src/main/java/org/keycloak/models/ImpersonationConstants.java
index 274e7a6..54f212a 100755
--- a/model/api/src/main/java/org/keycloak/models/ImpersonationConstants.java
+++ b/model/api/src/main/java/org/keycloak/models/ImpersonationConstants.java
@@ -26,6 +26,7 @@ public class ImpersonationConstants {
if (realmAdminApp.getRole(IMPERSONATION_ROLE) != null) return;
RoleModel impersonationRole = realmAdminApp.addRole(IMPERSONATION_ROLE);
impersonationRole.setDescription("${role_" + IMPERSONATION_ROLE + "}");
+ impersonationRole.setScopeParamRequired(false);
adminRole.addCompositeRole(impersonationRole);
}
@@ -36,6 +37,7 @@ public class ImpersonationConstants {
if (realmAdminApp.getRole(IMPERSONATION_ROLE) != null) return;
RoleModel impersonationRole = realmAdminApp.addRole(IMPERSONATION_ROLE);
impersonationRole.setDescription("${role_" + IMPERSONATION_ROLE + "}");
+ impersonationRole.setScopeParamRequired(false);
RoleModel adminRole = realmAdminApp.getRole(AdminRoles.REALM_ADMIN);
adminRole.addCompositeRole(impersonationRole);
}
diff --git a/model/api/src/main/java/org/keycloak/models/RoleModel.java b/model/api/src/main/java/org/keycloak/models/RoleModel.java
index c296795..9904a16 100755
--- a/model/api/src/main/java/org/keycloak/models/RoleModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RoleModel.java
@@ -17,6 +17,10 @@ public interface RoleModel {
void setName(String name);
+ boolean isScopeParamRequired();
+
+ void setScopeParamRequired(boolean scopeParamRequired);
+
boolean isComposite();
void addCompositeRole(RoleModel role);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
index b64ebb0..846269e 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java
@@ -4,6 +4,7 @@ import org.bouncycastle.openssl.PEMWriter;
import org.keycloak.constants.KerberosConstants;
import org.keycloak.constants.ServiceAccountConstants;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakSessionTask;
@@ -360,4 +361,13 @@ public final class KeycloakModelUtils {
public static String toLowerCaseSafe(String str) {
return str==null ? null : str.toLowerCase();
}
+
+ public static void setupOfflineTokens(RealmModel realm) {
+ if (realm.getRole(Constants.OFFLINE_ACCESS_ROLE) == null) {
+ RoleModel role = realm.addRole(Constants.OFFLINE_ACCESS_ROLE);
+ role.setDescription("${role_offline-access}");
+ role.setScopeParamRequired(true);
+ realm.addDefaultRole(Constants.OFFLINE_ACCESS_ROLE);
+ }
+ }
}
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 a382880..b71fce9 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
@@ -89,6 +89,7 @@ public class ModelToRepresentation {
rep.setId(role.getId());
rep.setName(role.getName());
rep.setDescription(role.getDescription());
+ rep.setScopeParamRequired(role.isScopeParamRequired());
rep.setComposite(role.isComposite());
return rep;
}
@@ -303,7 +304,6 @@ public class ModelToRepresentation {
rep.setBearerOnly(clientModel.isBearerOnly());
rep.setConsentRequired(clientModel.isConsentRequired());
rep.setServiceAccountsEnabled(clientModel.isServiceAccountsEnabled());
- rep.setOfflineTokensEnabled(clientModel.isOfflineTokensEnabled());
rep.setDirectGrantsOnly(clientModel.isDirectGrantsOnly());
rep.setSurrogateAuthRequired(clientModel.isSurrogateAuthRequired());
rep.setBaseUrl(clientModel.getBaseUrl());
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 3907f96..77ae66e 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
@@ -181,6 +181,8 @@ public class RepresentationToModel {
// Application role may already exists (for example if it is defaultRole)
RoleModel role = roleRep.getId()!=null ? client.addRole(roleRep.getId(), roleRep.getName()) : client.addRole(roleRep.getName());
role.setDescription(roleRep.getDescription());
+ boolean scopeParamRequired = roleRep.isScopeParamRequired()==null ? false : roleRep.isScopeParamRequired();
+ role.setScopeParamRequired(scopeParamRequired);
}
}
}
@@ -633,6 +635,8 @@ public class RepresentationToModel {
public static void createRole(RealmModel newRealm, RoleRepresentation roleRep) {
RoleModel role = roleRep.getId()!=null ? newRealm.addRole(roleRep.getId(), roleRep.getName()) : newRealm.addRole(roleRep.getName());
if (roleRep.getDescription() != null) role.setDescription(roleRep.getDescription());
+ boolean scopeParamRequired = roleRep.isScopeParamRequired() == null ? false : roleRep.isScopeParamRequired();
+ role.setScopeParamRequired(scopeParamRequired);
}
private static void addComposites(RoleModel role, RoleRepresentation roleRep, RealmModel realm) {
@@ -692,7 +696,6 @@ public class RepresentationToModel {
if (resourceRep.isBearerOnly() != null) client.setBearerOnly(resourceRep.isBearerOnly());
if (resourceRep.isConsentRequired() != null) client.setConsentRequired(resourceRep.isConsentRequired());
if (resourceRep.isServiceAccountsEnabled() != null) client.setServiceAccountsEnabled(resourceRep.isServiceAccountsEnabled());
- if (resourceRep.isOfflineTokensEnabled() != null) client.setOfflineTokensEnabled(resourceRep.isOfflineTokensEnabled());
if (resourceRep.isDirectGrantsOnly() != null) client.setDirectGrantsOnly(resourceRep.isDirectGrantsOnly());
if (resourceRep.isPublicClient() != null) client.setPublicClient(resourceRep.isPublicClient());
if (resourceRep.isFrontchannelLogout() != null) client.setFrontchannelLogout(resourceRep.isFrontchannelLogout());
@@ -789,7 +792,6 @@ public class RepresentationToModel {
if (rep.isBearerOnly() != null) resource.setBearerOnly(rep.isBearerOnly());
if (rep.isConsentRequired() != null) resource.setConsentRequired(rep.isConsentRequired());
if (rep.isServiceAccountsEnabled() != null) resource.setServiceAccountsEnabled(rep.isServiceAccountsEnabled());
- if (rep.isOfflineTokensEnabled() != null) resource.setOfflineTokensEnabled(rep.isOfflineTokensEnabled());
if (rep.isDirectGrantsOnly() != null) resource.setDirectGrantsOnly(rep.isDirectGrantsOnly());
if (rep.isPublicClient() != null) resource.setPublicClient(rep.isPublicClient());
if (rep.isFullScopeAllowed() != null) resource.setFullScopeAllowed(rep.isFullScopeAllowed());
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 366dc31..8003b70 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
@@ -462,16 +462,6 @@ public class ClientAdapter implements ClientModel {
}
@Override
- public boolean isOfflineTokensEnabled() {
- return entity.isOfflineTokensEnabled();
- }
-
- @Override
- public void setOfflineTokensEnabled(boolean offlineTokensEnabled) {
- entity.setOfflineTokensEnabled(offlineTokensEnabled);
- }
-
- @Override
public boolean isDirectGrantsOnly() {
return entity.isDirectGrantsOnly();
}
diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/RoleAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/RoleAdapter.java
index 9448def..7706459 100755
--- a/model/file/src/main/java/org/keycloak/models/file/adapter/RoleAdapter.java
+++ b/model/file/src/main/java/org/keycloak/models/file/adapter/RoleAdapter.java
@@ -89,6 +89,16 @@ public class RoleAdapter implements RoleModel {
}
@Override
+ public boolean isScopeParamRequired() {
+ return role.isScopeParamRequired();
+ }
+
+ @Override
+ public void setScopeParamRequired(boolean scopeParamRequired) {
+ role.setScopeParamRequired(scopeParamRequired);
+ }
+
+ @Override
public boolean isComposite() {
return role.getCompositeRoleIds() != null && role.getCompositeRoleIds().size() > 0;
}
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 582e5f1..9f79b15 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
@@ -431,18 +431,6 @@ public class ClientAdapter implements ClientModel {
}
@Override
- public boolean isOfflineTokensEnabled() {
- if (updated != null) return updated.isOfflineTokensEnabled();
- return cached.isOfflineTokensEnabled();
- }
-
- @Override
- public void setOfflineTokensEnabled(boolean offlineTokensEnabled) {
- getDelegateForUpdate();
- updated.setOfflineTokensEnabled(offlineTokensEnabled);
- }
-
- @Override
public RoleModel getRole(String name) {
if (updated != null) return updated.getRole(name);
String id = cached.getRoles().get(name);
diff --git a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java
index 536c2ef..861c252 100755
--- a/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java
+++ b/model/invalidation-cache/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RoleAdapter.java
@@ -60,6 +60,18 @@ public class RoleAdapter implements RoleModel {
}
@Override
+ public boolean isScopeParamRequired() {
+ if (updated != null) return updated.isScopeParamRequired();
+ return cached.isScopeParamRequired();
+ }
+
+ @Override
+ public void setScopeParamRequired(boolean scopeParamRequired) {
+ getDelegateForUpdate();
+ updated.setScopeParamRequired(scopeParamRequired);
+ }
+
+ @Override
public String getId() {
if (updated != null) return updated.getId();
return cached.getId();
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 b90c077..11447d0 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
@@ -47,7 +47,6 @@ public class CachedClient implements Serializable {
private boolean bearerOnly;
private boolean consentRequired;
private boolean serviceAccountsEnabled;
- private boolean offlineTokensEnabled;
private Map<String, String> roles = new HashMap<String, String>();
private int nodeReRegistrationTimeout;
private Map<String, Integer> registeredNodes;
@@ -82,7 +81,6 @@ public class CachedClient implements Serializable {
bearerOnly = model.isBearerOnly();
consentRequired = model.isConsentRequired();
serviceAccountsEnabled = model.isServiceAccountsEnabled();
- offlineTokensEnabled = model.isOfflineTokensEnabled();
for (RoleModel role : model.getRoles()) {
roles.put(role.getName(), role.getId());
cache.addCachedRole(new CachedClientRole(id, role, realm));
@@ -191,10 +189,6 @@ public class CachedClient implements Serializable {
return serviceAccountsEnabled;
}
- public boolean isOfflineTokensEnabled() {
- return offlineTokensEnabled;
- }
-
public Map<String, String> getRoles() {
return roles;
}
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRole.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRole.java
index 6afef48..1bbaa5c 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRole.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRole.java
@@ -17,6 +17,7 @@ public class CachedRole implements Serializable {
final protected String name;
final protected String realm;
final protected String description;
+ final protected Boolean scopeParamRequired;
final protected boolean composite;
final protected Set<String> composites = new HashSet<String>();
@@ -25,6 +26,7 @@ public class CachedRole implements Serializable {
description = model.getDescription();
id = model.getId();
name = model.getName();
+ scopeParamRequired = model.isScopeParamRequired();
this.realm = realm.getId();
if (composite) {
for (RoleModel child : model.getComposites()) {
@@ -50,6 +52,10 @@ public class CachedRole implements Serializable {
return description;
}
+ public Boolean isScopeParamRequired() {
+ return scopeParamRequired;
+ }
+
public boolean isComposite() {
return composite;
}
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 9fc7377..b0acc41 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
@@ -482,16 +482,6 @@ public class ClientAdapter implements ClientModel {
}
@Override
- public boolean isOfflineTokensEnabled() {
- return entity.isOfflineTokensEnabled();
- }
-
- @Override
- public void setOfflineTokensEnabled(boolean offlineTokensEnabled) {
- entity.setOfflineTokensEnabled(offlineTokensEnabled);
- }
-
- @Override
public boolean isDirectGrantsOnly() {
return entity.isDirectGrantsOnly();
}
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 d853cb3..1f6ac25 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
@@ -100,9 +100,6 @@ public class ClientEntity {
@Column(name="SERVICE_ACCOUNTS_ENABLED")
private boolean serviceAccountsEnabled;
- @Column(name="OFFLINE_TOKENS_ENABLED")
- private boolean offlineTokensEnabled;
-
@Column(name="NODE_REREG_TIMEOUT")
private int nodeReRegistrationTimeout;
@@ -319,14 +316,6 @@ public class ClientEntity {
this.serviceAccountsEnabled = serviceAccountsEnabled;
}
- public boolean isOfflineTokensEnabled() {
- return offlineTokensEnabled;
- }
-
- public void setOfflineTokensEnabled(boolean offlineTokensEnabled) {
- this.offlineTokensEnabled = offlineTokensEnabled;
- }
-
public boolean isDirectGrantsOnly() {
return directGrantsOnly;
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
index 4c9edb7..437867e 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RoleEntity.java
@@ -37,6 +37,8 @@ public class RoleEntity {
private String name;
@Column(name = "DESCRIPTION")
private String description;
+ @Column(name = "SCOPE_PARAM_REQUIRED")
+ private boolean scopeParamRequired;
// hax! couldn't get constraint to work properly
@Column(name = "REALM_ID")
@@ -93,6 +95,14 @@ public class RoleEntity {
this.description = description;
}
+ public boolean isScopeParamRequired() {
+ return scopeParamRequired;
+ }
+
+ public void setScopeParamRequired(boolean scopeParamRequired) {
+ this.scopeParamRequired = scopeParamRequired;
+ }
+
public Collection<RoleEntity> getCompositeRoles() {
return compositeRoles;
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
index 4ab8d33..0e49d59 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RoleAdapter.java
@@ -50,6 +50,16 @@ public class RoleAdapter implements RoleModel {
}
@Override
+ public boolean isScopeParamRequired() {
+ return role.isScopeParamRequired();
+ }
+
+ @Override
+ public void setScopeParamRequired(boolean scopeParamRequired) {
+ role.setScopeParamRequired(scopeParamRequired);
+ }
+
+ @Override
public String getId() {
return role.getId();
}
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 2cb863e..26effca 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
@@ -484,17 +484,6 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> imple
}
@Override
- public boolean isOfflineTokensEnabled() {
- return getMongoEntity().isOfflineTokensEnabled();
- }
-
- @Override
- public void setOfflineTokensEnabled(boolean offlineTokensEnabled) {
- getMongoEntity().setOfflineTokensEnabled(offlineTokensEnabled);
- updateMongoEntity();
- }
-
- @Override
public boolean isDirectGrantsOnly() {
return getMongoEntity().isDirectGrantsOnly();
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java
index 91ef361..f3d918b 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RoleAdapter.java
@@ -69,6 +69,17 @@ public class RoleAdapter extends AbstractMongoAdapter<MongoRoleEntity> implement
}
@Override
+ public boolean isScopeParamRequired() {
+ return role.isScopeParamRequired();
+ }
+
+ @Override
+ public void setScopeParamRequired(boolean scopeParamRequired) {
+ role.setScopeParamRequired(scopeParamRequired);
+ updateRole();
+ }
+
+ @Override
public boolean isComposite() {
return role.getCompositeRoleIds() != null && role.getCompositeRoleIds().size() > 0;
}
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 46d995f..c43140a 100755
--- a/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
+++ b/services/src/main/java/org/keycloak/protocol/oidc/TokenManager.java
@@ -2,6 +2,7 @@ package org.keycloak.protocol.oidc;
import org.jboss.logging.Logger;
import org.keycloak.ClientConnection;
+import org.keycloak.OAuth2Constants;
import org.keycloak.OAuthErrorException;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
@@ -30,7 +31,7 @@ import org.keycloak.representations.RefreshToken;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
-import org.keycloak.services.offline.OfflineUserSessionManager;
+import org.keycloak.services.offline.OfflineTokenUtils;
import org.keycloak.util.RefreshTokenUtil;
import org.keycloak.util.Time;
@@ -38,6 +39,9 @@ import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@@ -92,13 +96,9 @@ public class TokenManager {
UserSessionModel userSession = null;
ClientSessionModel clientSession = null;
if (RefreshTokenUtil.TOKEN_TYPE_OFFLINE.equals(oldToken.getType())) {
- // Check if offline tokens still allowed for the client
- clientSession = new OfflineUserSessionManager().findOfflineClientSession(realm, user, oldToken.getClientSession(), oldToken.getSessionState());
- if (clientSession != null) {
- if (!clientSession.getClient().isOfflineTokensEnabled()) {
- throw new OAuthErrorException(OAuthErrorException.INVALID_GRANT, "Offline tokens not allowed for client", "Offline tokens not allowed for client");
- }
+ clientSession = OfflineTokenUtils.findOfflineClientSession(realm, user, oldToken.getClientSession(), oldToken.getSessionState());
+ if (clientSession != null) {
userSession = clientSession.getUserSession();
}
} else {
@@ -136,7 +136,8 @@ public class TokenManager {
// recreate token.
- Set<RoleModel> requestedRoles = TokenManager.getAccess(null, clientSession.getClient(), user);
+ String scopeParam = clientSession.getNote(OAuth2Constants.SCOPE);
+ Set<RoleModel> requestedRoles = TokenManager.getAccess(scopeParam, true, clientSession.getClient(), user);
AccessToken newToken = createClientAccessToken(session, requestedRoles, realm, client, user, userSession, clientSession);
verifyAccess(oldToken, newToken);
@@ -233,7 +234,8 @@ public class TokenManager {
clientSession.setUserSession(session);
Set<String> requestedRoles = new HashSet<String>();
// todo scope param protocol independent
- for (RoleModel r : TokenManager.getAccess(null, clientSession.getClient(), user)) {
+ String scopeParam = clientSession.getNote(OAuth2Constants.SCOPE);
+ for (RoleModel r : TokenManager.getAccess(scopeParam, true, clientSession.getClient(), user)) {
requestedRoles.add(r.getId());
}
clientSession.setRoles(requestedRoles);
@@ -269,26 +271,62 @@ public class TokenManager {
}
}
- public static Set<RoleModel> getAccess(String scopeParam, ClientModel client, UserModel user) {
- // todo scopeParam is ignored until we figure out a scheme that fits with openid connect
+ public static Set<RoleModel> getAccess(String scopeParam, boolean applyScopeParam, ClientModel client, UserModel user) {
Set<RoleModel> requestedRoles = new HashSet<RoleModel>();
Set<RoleModel> roleMappings = user.getRoleMappings();
- if (client.isFullScopeAllowed()) return roleMappings;
- Set<RoleModel> scopeMappings = client.getScopeMappings();
- scopeMappings.addAll(client.getRoles());
+ if (client.isFullScopeAllowed()) {
+ requestedRoles = roleMappings;
+ } else {
+
+ Set<RoleModel> scopeMappings = client.getScopeMappings();
+ scopeMappings.addAll(client.getRoles());
+
+ for (RoleModel role : roleMappings) {
+ for (RoleModel desiredRole : scopeMappings) {
+ Set<RoleModel> visited = new HashSet<RoleModel>();
+ applyScope(role, desiredRole, visited, requestedRoles);
+ }
+ }
+ }
+
+ if (applyScopeParam) {
+ Collection<String> scopeParamRoles;
+ if (scopeParam != null) {
+ String[] scopes = scopeParam.split(" ");
+ scopeParamRoles = Arrays.asList(scopes);
+ } else {
+ scopeParamRoles = Collections.emptyList();
+ }
- for (RoleModel role : roleMappings) {
- for (RoleModel desiredRole : scopeMappings) {
- Set<RoleModel> visited = new HashSet<RoleModel>();
- applyScope(role, desiredRole, visited, requestedRoles);
+ Set<RoleModel> roles = new HashSet<>();
+ for (RoleModel role : requestedRoles) {
+ String roleName = getRoleNameForScopeParam(role);
+ if (!role.isScopeParamRequired() || scopeParamRoles.contains(roleName)) {
+ roles.add(role);
+ } else {
+ if (logger.isTraceEnabled()) {
+ logger.tracef("Role '%s' excluded by scope param. Client is '%s', User is '%s', Scope param is '%s' ", role.getName(), client.getClientId(), user.getUsername(), scopeParam);
+ }
+ }
}
+ requestedRoles = roles;
}
return requestedRoles;
}
+ // For now, just use "roleName" for realm roles and "clientId/roleName" for client roles
+ private static String getRoleNameForScopeParam(RoleModel role) {
+ if (role.getContainer() instanceof RealmModel) {
+ return role.getName();
+ } else {
+ ClientModel client = (ClientModel) role.getContainer();
+ return client.getClientId() + "/" + role.getName();
+ }
+ }
+
public void verifyAccess(AccessToken token, AccessToken newToken) throws OAuthErrorException {
if (token.getRealmAccess() != null) {
if (newToken.getRealmAccess() == null) throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "User no long has permission for realm roles");
@@ -437,7 +475,7 @@ public class TokenManager {
public AccessTokenResponseBuilder generateAccessToken() {
UserModel user = userSession.getUser();
String scopeParam = clientSession.getNote(OIDCLoginProtocol.SCOPE_PARAM);
- Set<RoleModel> requestedRoles = getAccess(scopeParam, client, user);
+ Set<RoleModel> requestedRoles = getAccess(scopeParam, true, client, user);
accessToken = createClientAccessToken(session, requestedRoles, realm, client, user, userSession, clientSession);
return this;
}
@@ -450,14 +488,14 @@ public class TokenManager {
String scopeParam = clientSession.getNote(OIDCLoginProtocol.SCOPE_PARAM);
boolean offlineTokenRequested = RefreshTokenUtil.isOfflineTokenRequested(scopeParam);
if (offlineTokenRequested) {
- if (!clientSession.getClient().isOfflineTokensEnabled()) {
- event.error(Errors.INVALID_CLIENT);
- throw new ErrorResponseException("invalid_client", "Offline tokens not allowed for the client", Response.Status.BAD_REQUEST);
+ if (!OfflineTokenUtils.isOfflineTokenAllowed(realm, clientSession)) {
+ event.error(Errors.NOT_ALLOWED);
+ throw new ErrorResponseException("not_allowed", "Offline tokens not allowed for the user or client", Response.Status.BAD_REQUEST);
}
refreshToken = new RefreshToken(accessToken);
refreshToken.type(RefreshTokenUtil.TOKEN_TYPE_OFFLINE);
- new OfflineUserSessionManager().persistOfflineSession(clientSession, userSession);
+ OfflineTokenUtils.persistOfflineSession(clientSession, userSession);
} else {
refreshToken = new RefreshToken(accessToken);
refreshToken.expiration(Time.currentTime() + realm.getSsoSessionIdleTimeout());
diff --git a/services/src/main/java/org/keycloak/services/managers/RealmManager.java b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
index fb0578e..958cd3e 100755
--- a/services/src/main/java/org/keycloak/services/managers/RealmManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/RealmManager.java
@@ -27,6 +27,7 @@ import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.OAuthClientRepresentation;
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
+import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.timer.TimerProvider;
import java.util.Collections;
@@ -95,6 +96,7 @@ public class RealmManager implements RealmImporter {
setupImpersonationService(realm);
setupAuthenticationFlows(realm);
setupRequiredActions(realm);
+ setupOfflineTokens(realm);
return realm;
}
@@ -107,6 +109,10 @@ public class RealmManager implements RealmImporter {
if (realm.getRequiredActionProviders().size() == 0) DefaultRequiredActions.addActions(realm);
}
+ protected void setupOfflineTokens(RealmModel realm) {
+ KeycloakModelUtils.setupOfflineTokens(realm);
+ }
+
protected void setupAdminConsole(RealmModel realm) {
ClientModel adminConsole = realm.getClientByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID);
if (adminConsole == null) adminConsole = new ClientManager(this).createClient(realm, Constants.ADMIN_CONSOLE_CLIENT_ID);
@@ -216,12 +222,14 @@ public class RealmManager implements RealmImporter {
RoleModel createRealmRole = realm.addRole(AdminRoles.CREATE_REALM);
adminRole.addCompositeRole(createRealmRole);
- createRealmRole.setDescription("${role_"+AdminRoles.CREATE_REALM+"}");
+ createRealmRole.setDescription("${role_" + AdminRoles.CREATE_REALM + "}");
+ createRealmRole.setScopeParamRequired(false);
} else {
adminRealm = model.getRealmByName(Config.getAdminRealm());
adminRole = adminRealm.getRole(AdminRoles.ADMIN);
}
adminRole.setDescription("${role_"+AdminRoles.ADMIN+"}");
+ adminRole.setScopeParamRequired(false);
ClientModel realmAdminApp = KeycloakModelUtils.createClient(adminRealm, KeycloakModelUtils.getMasterRealmAdminApplicationClientId(realm.getName()));
// No localized name for now
@@ -232,6 +240,7 @@ public class RealmManager implements RealmImporter {
for (String r : AdminRoles.ALL_REALM_ROLES) {
RoleModel role = realmAdminApp.addRole(r);
role.setDescription("${role_"+r+"}");
+ role.setScopeParamRequired(false);
adminRole.addCompositeRole(role);
}
}
@@ -249,12 +258,14 @@ public class RealmManager implements RealmImporter {
}
RoleModel adminRole = realmAdminClient.addRole(AdminRoles.REALM_ADMIN);
adminRole.setDescription("${role_" + AdminRoles.REALM_ADMIN + "}");
+ adminRole.setScopeParamRequired(false);
realmAdminClient.setBearerOnly(true);
realmAdminClient.setFullScopeAllowed(false);
for (String r : AdminRoles.ALL_REALM_ROLES) {
RoleModel role = realmAdminClient.addRole(r);
role.setDescription("${role_"+r+"}");
+ role.setScopeParamRequired(false);
adminRole.addCompositeRole(role);
}
}
@@ -274,7 +285,9 @@ public class RealmManager implements RealmImporter {
for (String role : AccountRoles.ALL) {
client.addDefaultRole(role);
- client.getRole(role).setDescription("${role_"+role+"}");
+ RoleModel roleModel = client.getRole(role);
+ roleModel.setDescription("${role_" + role + "}");
+ roleModel.setScopeParamRequired(false);
}
}
}
@@ -292,7 +305,9 @@ public class RealmManager implements RealmImporter {
client.setFullScopeAllowed(false);
for (String role : Constants.BROKER_SERVICE_ROLES) {
- client.addRole(role).setDescription("${role_"+ role.toLowerCase().replaceAll("_", "-") +"}");
+ RoleModel roleModel = client.addRole(role);
+ roleModel.setDescription("${role_"+ role.toLowerCase().replaceAll("_", "-") +"}");
+ roleModel.setScopeParamRequired(false);
}
}
}
@@ -329,6 +344,7 @@ public class RealmManager implements RealmImporter {
if (!hasBrokerClient(rep)) setupBrokerService(realm);
if (!hasAdminConsoleClient(rep)) setupAdminConsole(realm);
+ if (!hasRealmRole(rep, Constants.OFFLINE_ACCESS_ROLE)) setupOfflineTokens(realm);
RepresentationToModel.importRealm(session, rep, realm);
@@ -409,6 +425,20 @@ public class RealmManager implements RealmImporter {
return false;
}
+ private boolean hasRealmRole(RealmRepresentation rep, String roleName) {
+ if (rep.getRoles() == null || rep.getRoles().getRealm() == null) {
+ return false;
+ }
+
+ for (RoleRepresentation role : rep.getRoles().getRealm()) {
+ if (roleName.equals(role.getName())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
/**
* Query users based on a search string:
* <p/>
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 14d3639..3b6edea 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -46,7 +46,6 @@ import org.keycloak.models.UserSessionModel;
import org.keycloak.models.utils.CredentialValidation;
import org.keycloak.models.utils.FormMessage;
import org.keycloak.models.utils.ModelToRepresentation;
-import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.utils.RedirectUtils;
import org.keycloak.representations.idm.CredentialRepresentation;
@@ -58,7 +57,7 @@ import org.keycloak.services.managers.Auth;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.messages.Messages;
-import org.keycloak.services.offline.OfflineUserSessionManager;
+import org.keycloak.services.offline.OfflineTokenUtils;
import org.keycloak.services.util.ResolveRelative;
import org.keycloak.services.validation.Validation;
import org.keycloak.util.UriUtils;
@@ -487,7 +486,7 @@ public class AccountService extends AbstractSecuredLocalService {
// Revoke grant in UserModel
UserModel user = auth.getUser();
user.revokeConsentForClient(client.getId());
- new OfflineUserSessionManager().revokeOfflineToken(user, client);
+ OfflineTokenUtils.revokeOfflineToken(user, client);
// Logout clientSessions for this user and client
AuthenticationManager.backchannelUserFromClient(session, realm, user, client, uriInfo, headers);
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
index 168ff47..879ff6f 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleContainerResource.java
@@ -82,6 +82,8 @@ public class RoleContainerResource extends RoleResource {
try {
RoleModel role = roleContainer.addRole(rep.getName());
role.setDescription(rep.getDescription());
+ boolean scopeParamRequired = rep.isScopeParamRequired()==null ? false : rep.isScopeParamRequired();
+ role.setScopeParamRequired(scopeParamRequired);
adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, role.getId()).representation(rep).success();
diff --git a/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java b/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java
index 827b817..3635da8 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/RoleResource.java
@@ -38,6 +38,7 @@ public abstract class RoleResource {
protected void updateRole(RoleRepresentation rep, RoleModel role) {
role.setName(rep.getName());
role.setDescription(rep.getDescription());
+ if (rep.isScopeParamRequired() != null) role.setScopeParamRequired(rep.isScopeParamRequired());
}
protected void addComposites(AdminEventBuilder adminEvent, UriInfo uriInfo, List<RoleRepresentation> roles, RoleModel role) {
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
index 1f401fa..9458998 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/account/AccountTest.java
@@ -668,7 +668,8 @@ public class AccountTest {
Assert.assertTrue(accountEntry.getProtocolMappersGranted().contains("Full Access"));
AccountApplicationsPage.AppEntry testAppEntry = apps.get("test-app");
- Assert.assertEquals(4, testAppEntry.getRolesAvailable().size());
+ Assert.assertEquals(5, testAppEntry.getRolesAvailable().size());
+ Assert.assertTrue(testAppEntry.getRolesAvailable().contains("Offline access"));
Assert.assertTrue(testAppEntry.getRolesGranted().contains("Full Access"));
Assert.assertTrue(testAppEntry.getProtocolMappersGranted().contains("Full Access"));
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java
index bae16d5..aaed86e 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/AdminAPITest.java
@@ -85,7 +85,7 @@ public class AdminAPITest {
ClientSessionModel clientSession = session.sessions().createClientSession(adminRealm, adminConsole);
clientSession.setNote(OIDCLoginProtocol.ISSUER, "http://localhost:8081/auth/realms/master");
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false, null, null);
- AccessToken token = tm.createClientAccessToken(session, tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession, clientSession);
+ AccessToken token = tm.createClientAccessToken(session, tm.getAccess(null, true, adminConsole, admin), adminRealm, adminConsole, admin, userSession, clientSession);
return tm.encodeToken(adminRealm, token);
} finally {
keycloakRule.stopSession(session, true);
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 c30ee36..2097175 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
@@ -108,7 +108,7 @@ public class ClientTest extends AbstractClientTest {
response.close();
String id = ApiUtil.getCreatedId(response);
- RoleRepresentation role = new RoleRepresentation("test", "test");
+ RoleRepresentation role = new RoleRepresentation("test", "test", false);
realm.clients().get(id).roles().create(role);
rep = realm.clients().get(id).toRepresentation();
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java
index 0de7dd6..0f1510b 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/ImpersonationTest.java
@@ -123,7 +123,7 @@ public class ImpersonationTest {
ClientSessionModel clientSession = session.sessions().createClientSession(adminRealm, adminConsole);
clientSession.setNote(OIDCLoginProtocol.ISSUER, "http://localhost:8081/auth/realms/" + realm);
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false, null, null);
- AccessToken token = tm.createClientAccessToken(session, tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession, clientSession);
+ AccessToken token = tm.createClientAccessToken(session, tm.getAccess(null, true, adminConsole, admin), adminRealm, adminConsole, admin, userSession, clientSession);
return tm.encodeToken(adminRealm, token);
} finally {
keycloakRule.stopSession(session, true);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
index 6cf6427..8208aa2 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/admin/RealmTest.java
@@ -112,7 +112,7 @@ public class RealmTest extends AbstractClientTest {
@Test
// KEYCLOAK-1110
public void deleteDefaultRole() {
- RoleRepresentation role = new RoleRepresentation("test", "test");
+ RoleRepresentation role = new RoleRepresentation("test", "test", false);
realm.roles().create(role);
assertNotNull(realm.roles().get("test").toRepresentation());
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AbstractModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AbstractModelTest.java
index a3e0976..d6d8724 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AbstractModelTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AbstractModelTest.java
@@ -117,6 +117,7 @@ public class AbstractModelTest {
Assert.assertEquals(expected.getId(), actual.getId());
Assert.assertEquals(expected.getName(), actual.getName());
Assert.assertEquals(expected.getDescription(), actual.getDescription());
+ Assert.assertEquals(expected.isScopeParamRequired(), actual.isScopeParamRequired());
Assert.assertEquals(expected.getContainer(), actual.getContainer());
Assert.assertEquals(expected.getComposites().size(), actual.getComposites().size());
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java
index 48ed318..4e771df 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java
@@ -68,8 +68,8 @@ public class AdapterTest extends AbstractModelTest {
Assert.assertEquals(realmModel.getName(), "JUGGLER");
Assert.assertArrayEquals(realmModel.getPrivateKey().getEncoded(), keyPair.getPrivate().getEncoded());
Assert.assertArrayEquals(realmModel.getPublicKey().getEncoded(), keyPair.getPublic().getEncoded());
- Assert.assertEquals(1, realmModel.getDefaultRoles().size());
- Assert.assertEquals("foo", realmModel.getDefaultRoles().get(0));
+ Assert.assertEquals(2, realmModel.getDefaultRoles().size());
+ Assert.assertTrue(realmModel.getDefaultRoles().contains("foo"));
}
@Test
@@ -94,8 +94,8 @@ public class AdapterTest extends AbstractModelTest {
Assert.assertEquals(realmModel.getName(), "JUGGLER");
Assert.assertArrayEquals(realmModel.getPrivateKey().getEncoded(), keyPair.getPrivate().getEncoded());
Assert.assertArrayEquals(realmModel.getPublicKey().getEncoded(), keyPair.getPublic().getEncoded());
- Assert.assertEquals(1, realmModel.getDefaultRoles().size());
- Assert.assertEquals("foo", realmModel.getDefaultRoles().get(0));
+ Assert.assertEquals(2, realmModel.getDefaultRoles().size());
+ Assert.assertTrue(realmModel.getDefaultRoles().contains("foo"));
realmModel.getId();
@@ -444,7 +444,7 @@ public class AdapterTest extends AbstractModelTest {
realmModel.addRole("admin");
realmModel.addRole("user");
Set<RoleModel> roles = realmModel.getRoles();
- Assert.assertEquals(3, roles.size());
+ Assert.assertEquals(4, roles.size());
UserModel user = realmManager.getSession().users().addUser(realmModel, "bburke");
RoleModel realmUserRole = realmModel.getRole("user");
user.grantRole(realmUserRole);
@@ -470,7 +470,7 @@ public class AdapterTest extends AbstractModelTest {
user.grantRole(application.getRole("user"));
roles = user.getRealmRoleMappings();
- Assert.assertEquals(roles.size(), 2);
+ Assert.assertEquals(roles.size(), 3);
assertRolesContains(realmUserRole, roles);
Assert.assertTrue(user.hasRole(realmUserRole));
// Role "foo" is default realm role
@@ -485,13 +485,13 @@ public class AdapterTest extends AbstractModelTest {
// Test that application role 'user' don't clash with realm role 'user'
Assert.assertNotEquals(realmModel.getRole("user").getId(), application.getRole("user").getId());
- Assert.assertEquals(6, user.getRoleMappings().size());
+ Assert.assertEquals(7, user.getRoleMappings().size());
// Revoke some roles
user.deleteRoleMapping(realmModel.getRole("foo"));
user.deleteRoleMapping(appBarRole);
roles = user.getRoleMappings();
- Assert.assertEquals(4, roles.size());
+ Assert.assertEquals(5, roles.size());
assertRolesContains(realmUserRole, roles);
assertRolesContains(application.getRole("user"), roles);
Assert.assertFalse(user.hasRole(appBarRole));
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
index d56056d..6f42fd5 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java
@@ -79,7 +79,7 @@ public class ImportTest extends AbstractModelTest {
Assert.assertEquals(1, creds.size());
RequiredCredentialModel cred = creds.get(0);
Assert.assertEquals("password", cred.getFormLabel());
- Assert.assertEquals(2, realm.getDefaultRoles().size());
+ Assert.assertEquals(3, realm.getDefaultRoles().size());
Assert.assertNotNull(realm.getRole("foo"));
Assert.assertNotNull(realm.getRole("bar"));
@@ -132,6 +132,10 @@ public class ImportTest extends AbstractModelTest {
Assert.assertTrue(allRoles.contains(application.getRole("app-admin")));
Assert.assertTrue(allRoles.contains(otherApp.getRole("otherapp-admin")));
+ Assert.assertTrue(application.getRole("app-admin").isScopeParamRequired());
+ Assert.assertFalse(otherApp.getRole("otherapp-admin").isScopeParamRequired());
+ Assert.assertFalse(otherApp.getRole("otherapp-user").isScopeParamRequired());
+
UserModel wburke = session.users().getUserByUsername("wburke", realm);
// user with creation timestamp in import
Assert.assertEquals(new Long(123654), wburke.getCreatedTimestamp());
@@ -326,8 +330,6 @@ public class ImportTest extends AbstractModelTest {
// Test service accounts
Assert.assertFalse(application.isServiceAccountsEnabled());
Assert.assertTrue(otherApp.isServiceAccountsEnabled());
- Assert.assertFalse(application.isOfflineTokensEnabled());
- Assert.assertTrue(otherApp.isOfflineTokensEnabled());
Assert.assertNull(session.users().getUserByServiceAccountClient(application));
UserModel linked = session.users().getUserByServiceAccountClient(otherApp);
Assert.assertNotNull(linked);
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
index 2363305..059cdd7 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OAuthGrantTest.java
@@ -34,6 +34,7 @@ import org.keycloak.models.ClientModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserModel;
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
@@ -290,4 +291,71 @@ public class OAuthGrantTest {
});
}
+ @Test
+ public void oauthGrantScopeParamRequired() throws Exception {
+ keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ ClientModel thirdParty = appRealm.getClientByClientId("third-party");
+ RoleModel barAppRole = thirdParty.addRole("bar-role");
+ barAppRole.setScopeParamRequired(true);
+
+ RoleModel fooRole = appRealm.addRole("foo-role");
+ fooRole.setScopeParamRequired(true);
+ thirdParty.addScopeMapping(fooRole);
+
+ UserModel testUser = manager.getSession().users().getUserByUsername("test-user@localhost", appRealm);
+ testUser.grantRole(fooRole);
+ testUser.grantRole(barAppRole);
+ }
+
+ });
+
+ // Assert roles not on grant screen when not requested
+ oauth.clientId("third-party");
+ oauth.doLoginGrant("test-user@localhost", "password");
+ grantPage.assertCurrent();
+ Assert.assertFalse(driver.getPageSource().contains("foo-role"));
+ Assert.assertFalse(driver.getPageSource().contains("bar-role"));
+ grantPage.cancel();
+
+ events.expectLogin()
+ .client("third-party")
+ .error("rejected_by_user")
+ .removeDetail(Details.CONSENT)
+ .assertEvent();
+
+ oauth.scope("foo-role third-party/bar-role");
+ oauth.doLoginGrant("test-user@localhost", "password");
+ grantPage.assertCurrent();
+ Assert.assertTrue(driver.getPageSource().contains("foo-role"));
+ Assert.assertTrue(driver.getPageSource().contains("bar-role"));
+ grantPage.accept();
+
+ events.expectLogin()
+ .client("third-party")
+ .detail(Details.CONSENT, Details.CONSENT_VALUE_CONSENT_GRANTED)
+ .assertEvent();
+
+ // Revoke
+ accountAppsPage.open();
+ accountAppsPage.revokeGrant("third-party");
+ events.expect(EventType.REVOKE_GRANT)
+ .client("account").detail(Details.REVOKED_CLIENT, "third-party").assertEvent();
+
+ // cleanup
+ keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ appRealm.removeRole(appRealm.getRole("foo-role"));
+ ClientModel thirdparty = appRealm.getClientByClientId("third-party");
+ thirdparty.removeRole(thirdparty.getRole("bar-role"));
+ }
+
+ });
+
+ }
+
}
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
index 33e6ff4..8eab300 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/oauth/OfflineTokenTest.java
@@ -26,6 +26,7 @@ import org.keycloak.events.Event;
import org.keycloak.events.EventType;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.Constants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
@@ -37,6 +38,7 @@ import org.keycloak.testsuite.AssertEvents;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.pages.AccountApplicationsPage;
import org.keycloak.testsuite.pages.LoginPage;
+import org.keycloak.testsuite.pages.OAuthGrantPage;
import org.keycloak.testsuite.rule.KeycloakRule;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
@@ -74,7 +76,6 @@ public class OfflineTokenTest {
RoleModel customerUserRole = appRealm.getClientByClientId("test-app").getRole("customer-user");
serviceAccountUser.grantRole(customerUserRole);
- app.setOfflineTokensEnabled(true);
userId = manager.getSession().users().getUserByUsername("test-user@localhost", appRealm).getId();
URL url = getClass().getResource("/oidc/offline-client-keycloak.json");
@@ -102,6 +103,9 @@ public class OfflineTokenTest {
protected LoginPage loginPage;
@WebResource
+ protected OAuthGrantPage oauthGrantPage;
+
+ @WebResource
protected AccountApplicationsPage accountAppPage;
@Rule
@@ -115,23 +119,80 @@ public class OfflineTokenTest {
@Test
public void offlineTokenDisabledForClient() throws Exception {
+ keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ appRealm.getClientByClientId("offline-client").setFullScopeAllowed(false);
+ }
+
+ });
+
oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
+ oauth.clientId("offline-client");
+ oauth.redirectUri(offlineClientAppUri);
oauth.doLogin("test-user@localhost", "password");
- Event loginEvent = events.expectLogin().assertEvent();
+ Event loginEvent = events.expectLogin()
+ .client("offline-client")
+ .detail(Details.REDIRECT_URI, offlineClientAppUri)
+ .assertEvent();
+
+ String sessionId = loginEvent.getSessionId();
+ String codeId = loginEvent.getDetails().get(Details.CODE_ID);
+
+ String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
+
+ OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "secret1");
+
+ assertEquals(400, tokenResponse.getStatusCode());
+ assertEquals("not_allowed", tokenResponse.getError());
+
+ events.expectCodeToToken(codeId, sessionId)
+ .client("offline-client")
+ .error("not_allowed")
+ .clearDetails()
+ .assertEvent();
+
+ keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ appRealm.getClientByClientId("offline-client").setFullScopeAllowed(true);
+ }
+
+ });
+ }
+
+ @Test
+ public void offlineTokenUserNotAllowed() throws Exception {
+ String userId = keycloakRule.getUser("test", "keycloak-user@localhost").getId();
+
+ oauth.scope(OAuth2Constants.OFFLINE_ACCESS);
+ oauth.clientId("offline-client");
+ oauth.redirectUri(offlineClientAppUri);
+ oauth.doLogin("keycloak-user@localhost", "password");
+
+ Event loginEvent = events.expectLogin()
+ .client("offline-client")
+ .user(userId)
+ .detail(Details.REDIRECT_URI, offlineClientAppUri)
+ .assertEvent();
String sessionId = loginEvent.getSessionId();
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
- OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "password");
+ OAuthClient.AccessTokenResponse tokenResponse = oauth.doAccessTokenRequest(code, "secret1");
assertEquals(400, tokenResponse.getStatusCode());
- assertEquals("invalid_client", tokenResponse.getError());
+ assertEquals("not_allowed", tokenResponse.getError());
events.expectCodeToToken(codeId, sessionId)
- .error("invalid_client")
+ .client("offline-client")
+ .user(userId)
+ .error("not_allowed")
.clearDetails()
.assertEvent();
}
@@ -206,8 +267,9 @@ public class OfflineTokenTest {
Assert.assertEquals(userId, refreshedToken.getSubject());
- Assert.assertEquals(1, refreshedToken.getRealmAccess().getRoles().size());
+ Assert.assertEquals(2, refreshedToken.getRealmAccess().getRoles().size());
Assert.assertTrue(refreshedToken.getRealmAccess().isUserInRole("user"));
+ Assert.assertTrue(refreshedToken.getRealmAccess().isUserInRole(Constants.OFFLINE_ACCESS_ROLE));
Assert.assertEquals(1, refreshedToken.getResourceAccess("test-app").getRoles().size());
Assert.assertTrue(refreshedToken.getResourceAccess("test-app").isUserInRole("customer-user"));
@@ -374,7 +436,7 @@ public class OfflineTokenTest {
accountAppPage.open();
List<String> additionalGrants = accountAppPage.getApplications().get("offline-client").getAdditionalGrants();
Assert.assertEquals(additionalGrants.size(), 1);
- Assert.assertEquals(additionalGrants.get(0), "Offline Access");
+ Assert.assertEquals(additionalGrants.get(0), "Offline Token");
accountAppPage.revokeGrant("offline-client");
Assert.assertEquals(accountAppPage.getApplications().get("offline-client").getAdditionalGrants().size(), 0);
@@ -389,6 +451,55 @@ public class OfflineTokenTest {
Time.setOffset(0);
}
+ @Test
+ public void testServletWithConsent() {
+ keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ appRealm.getClientByClientId("offline-client").setConsentRequired(true);
+ }
+
+ });
+
+ // Assert grant page doesn't have 'Offline Access' role when offline token is not requested
+ driver.navigate().to(offlineClientAppUri);
+ loginPage.login("test-user@localhost", "password");
+ oauthGrantPage.assertCurrent();
+ Assert.assertFalse(driver.getPageSource().contains("Offline access"));
+ oauthGrantPage.cancel();
+
+ // Assert grant page has 'Offline Access' role now
+ String servletUri = UriBuilder.fromUri(offlineClientAppUri)
+ .queryParam(OAuth2Constants.SCOPE, OAuth2Constants.OFFLINE_ACCESS)
+ .build().toString();
+ driver.navigate().to(servletUri);
+ loginPage.login("test-user@localhost", "password");
+ oauthGrantPage.assertCurrent();
+ Assert.assertTrue(driver.getPageSource().contains("Offline access"));
+ oauthGrantPage.accept();
+
+ Assert.assertTrue(driver.getCurrentUrl().startsWith(offlineClientAppUri));
+ Assert.assertEquals(OfflineTokenServlet.tokenInfo.refreshToken.getType(), RefreshTokenUtil.TOKEN_TYPE_OFFLINE);
+
+ accountAppPage.open();
+ AccountApplicationsPage.AppEntry offlineClient = accountAppPage.getApplications().get("offline-client");
+ Assert.assertTrue(offlineClient.getRolesGranted().contains("Offline access"));
+ Assert.assertTrue(offlineClient.getAdditionalGrants().contains("Offline Token"));
+
+ events.clear();
+
+ // Revert change
+ keycloakRule.update(new KeycloakRule.KeycloakSetup() {
+
+ @Override
+ public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
+ appRealm.getClientByClientId("offline-client").setConsentRequired(false);
+ }
+
+ });
+ }
+
public static class OfflineTokenServlet extends HttpServlet {
private static TokenInfo tokenInfo;
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java
index d556a83..2d2ab44 100755
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/saml/SamlBindingTest.java
@@ -457,7 +457,7 @@ public class SamlBindingTest {
ClientSessionModel clientSession = session.sessions().createClientSession(adminRealm, adminConsole);
clientSession.setNote(OIDCLoginProtocol.ISSUER, "http://localhost:8081/auth/realms/master");
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false, null, null);
- AccessToken token = tm.createClientAccessToken(session, tm.getAccess(null, adminConsole, admin), adminRealm, adminConsole, admin, userSession, clientSession);
+ AccessToken token = tm.createClientAccessToken(session, tm.getAccess(null, true, adminConsole, admin), adminRealm, adminConsole, admin, userSession, clientSession);
return tm.encodeToken(adminRealm, token);
} finally {
keycloakRule.stopSession(session, true);
diff --git a/testsuite/integration/src/test/resources/model/testrealm.json b/testsuite/integration/src/test/resources/model/testrealm.json
index 55a75b5..f7c8cdd 100755
--- a/testsuite/integration/src/test/resources/model/testrealm.json
+++ b/testsuite/integration/src/test/resources/model/testrealm.json
@@ -164,7 +164,6 @@
"name": "Other Application",
"enabled": true,
"serviceAccountsEnabled": true,
- "offlineTokensEnabled": true,
"clientAuthenticatorType": "client-jwt",
"protocolMappers" : [
{
@@ -199,7 +198,8 @@
"application" : {
"Application" : [
{
- "name": "app-admin"
+ "name": "app-admin",
+ "scopeParamRequired": true
},
{
"name": "app-user"
@@ -207,7 +207,8 @@
],
"OtherApp" : [
{
- "name": "otherapp-admin"
+ "name": "otherapp-admin",
+ "scopeParamRequired": false
},
{
"name": "otherapp-user"
diff --git a/testsuite/integration/src/test/resources/testrealm.json b/testsuite/integration/src/test/resources/testrealm.json
index 5344ad0..e16e3e2 100755
--- a/testsuite/integration/src/test/resources/testrealm.json
+++ b/testsuite/integration/src/test/resources/testrealm.json
@@ -26,7 +26,7 @@
{ "type" : "password",
"value" : "password" }
],
- "realmRoles": ["user"],
+ "realmRoles": ["user", "offline_access"],
"clientRoles": {
"test-app": [ "customer-user" ],
"account": [ "view-profile", "manage-account" ]
diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/roles/RoleForm.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/roles/RoleForm.java
index ac1db15..b4f974a 100644
--- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/roles/RoleForm.java
+++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/console/page/roles/RoleForm.java
@@ -18,6 +18,9 @@ public class RoleForm extends Form {
@FindBy(id = "description")
private WebElement descriptionInput;
+ @FindBy(xpath = ".//div[@class='onoffswitch' and ./input[@id='scopeParamRequired']]")
+ private OnOffSwitch scopeParamRequired;
+
@FindBy(xpath = ".//div[contains(@class,'onoffswitch') and ./input[@id='compositeSwitch']]")
private OnOffSwitch compositeSwitch;
@@ -28,7 +31,7 @@ public class RoleForm extends Form {
private WebElement removeIcon;
public RoleRepresentation getRole() {
- RoleRepresentation role = new RoleRepresentation(getName(), getDescription());
+ RoleRepresentation role = new RoleRepresentation(getName(), getDescription(), isScopeParamRequired());
role.setComposite(isComposite());
if (role.isComposite()) {
role.setComposites(compositeRoles.getComposites());
@@ -44,6 +47,7 @@ public class RoleForm extends Form {
RoleRepresentation role = new RoleRepresentation();
role.setName(getName());
role.setDescription(getDescription());
+ role.setScopeParamRequired(isScopeParamRequired());
role.setComposite(isComposite());
log.info(role.getName() + ": " + role.getDescription() + ", comp: " + role.isComposite());
return role;
@@ -52,6 +56,7 @@ public class RoleForm extends Form {
public void setBasicAttributes(RoleRepresentation role) {
setName(role.getName());
setDescription(role.getDescription());
+ setScopeParamRequired(role.isScopeParamRequired());
if (role.isComposite()) {
setCompositeRoles(role);
}
@@ -82,6 +87,14 @@ public class RoleForm extends Form {
return getInputValue(descriptionInput);
}
+ public void setScopeParamRequired(boolean scopeParamRequired) {
+ this.scopeParamRequired.setOn(scopeParamRequired);
+ }
+
+ public boolean isScopeParamRequired() {
+ return scopeParamRequired.isOn();
+ }
+
public void setComposite(boolean composite) {
compositeSwitch.setOn(composite);
}
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/ClientRolesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/ClientRolesTest.java
index e005d60..a8a4c35 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/ClientRolesTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/clients/ClientRolesTest.java
@@ -44,7 +44,7 @@ public class ClientRolesTest extends AbstractClientTest {
@Test
public void testAddClientRole() {
ClientRepresentation newClient = createClientRepresentation("test-client1", "http://example.com/*");
- RoleRepresentation newRole = new RoleRepresentation("client-role", "");
+ RoleRepresentation newRole = new RoleRepresentation("client-role", "", false);
createClient(newClient);
assertFlashMessageSuccess();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/roles/DefaultRolesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/roles/DefaultRolesTest.java
index 2f3bd8e..e156c39 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/roles/DefaultRolesTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/roles/DefaultRolesTest.java
@@ -30,7 +30,7 @@ public class DefaultRolesTest extends AbstractRolesTest {
@Before
public void beforeDefaultRolesTest() {
// create a role via admin client
- defaultRoleRep = new RoleRepresentation("default-role", "");
+ defaultRoleRep = new RoleRepresentation("default-role", "", false);
rolesPage.rolesResource().create(defaultRoleRep);
defaultRolesPage.navigateTo();
diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/roles/RealmRolesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/roles/RealmRolesTest.java
index f92ccac..0df9c15 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/roles/RealmRolesTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/console/roles/RealmRolesTest.java
@@ -32,7 +32,7 @@ public class RealmRolesTest extends AbstractRolesTest {
@Before
public void beforeTestAddNewRole() {
- testRole = new RoleRepresentation("test_role", "role description");
+ testRole = new RoleRepresentation("test_role", "role description", false);
realmRolesPage.navigateTo();
}
@@ -104,7 +104,7 @@ public class RealmRolesTest extends AbstractRolesTest {
@Ignore
public void testAddRoleWithLongName() {
String name = "hjewr89y1894yh98(*&*&$jhjkashd)*(&y8934h*&@#hjkahsdj";
- addRole(new RoleRepresentation(name, ""));
+ addRole(new RoleRepresentation(name, "", false));
assertNotNull(realmRolesPage.table().findRole(name));
}
@@ -124,7 +124,7 @@ public class RealmRolesTest extends AbstractRolesTest {
Timer.time();
for (int i = 0; i < count; i++) {
String roleName = String.format("%s%02d", namePrefix, i);
- RoleRepresentation rr = new RoleRepresentation(roleName, "");
+ RoleRepresentation rr = new RoleRepresentation(roleName, "", false);
testRealmResource().roles().create(rr);
}
Timer.time("create " + count + " roles");