KEYCLOAK-904 Consents support. Added scopeParamRequired flag to RoleModel
This commit is contained in:
parent
8cc4c8c5e2
commit
046edbbd54
53 changed files with 495 additions and 171 deletions
|
@ -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">
|
<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">
|
<changeSet author="mposolda@redhat.com" id="1.6.0">
|
||||||
|
|
||||||
<addColumn tableName="CLIENT">
|
<addColumn tableName="KEYCLOAK_ROLE">
|
||||||
<column name="OFFLINE_TOKENS_ENABLED" type="BOOLEAN" defaultValueBoolean="false">
|
<column name="SCOPE_PARAM_REQUIRED" type="BOOLEAN" defaultValueBoolean="false">
|
||||||
<constraints nullable="false"/>
|
<constraints nullable="false"/>
|
||||||
</column>
|
</column>
|
||||||
</addColumn>
|
</addColumn>
|
||||||
|
|
|
@ -24,7 +24,6 @@ public class ClientRepresentation {
|
||||||
protected Boolean bearerOnly;
|
protected Boolean bearerOnly;
|
||||||
protected Boolean consentRequired;
|
protected Boolean consentRequired;
|
||||||
protected Boolean serviceAccountsEnabled;
|
protected Boolean serviceAccountsEnabled;
|
||||||
protected Boolean offlineTokensEnabled;
|
|
||||||
protected Boolean directGrantsOnly;
|
protected Boolean directGrantsOnly;
|
||||||
protected Boolean publicClient;
|
protected Boolean publicClient;
|
||||||
protected Boolean frontchannelLogout;
|
protected Boolean frontchannelLogout;
|
||||||
|
@ -163,14 +162,6 @@ public class ClientRepresentation {
|
||||||
this.serviceAccountsEnabled = serviceAccountsEnabled;
|
this.serviceAccountsEnabled = serviceAccountsEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean isOfflineTokensEnabled() {
|
|
||||||
return offlineTokensEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOfflineTokensEnabled(Boolean offlineTokensEnabled) {
|
|
||||||
this.offlineTokensEnabled = offlineTokensEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Boolean isDirectGrantsOnly() {
|
public Boolean isDirectGrantsOnly() {
|
||||||
return directGrantsOnly;
|
return directGrantsOnly;
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ public class RoleRepresentation {
|
||||||
protected String id;
|
protected String id;
|
||||||
protected String name;
|
protected String name;
|
||||||
protected String description;
|
protected String description;
|
||||||
|
protected Boolean scopeParamRequired;
|
||||||
protected boolean composite;
|
protected boolean composite;
|
||||||
protected Composites composites;
|
protected Composites composites;
|
||||||
|
|
||||||
|
@ -46,9 +47,10 @@ public class RoleRepresentation {
|
||||||
public RoleRepresentation() {
|
public RoleRepresentation() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public RoleRepresentation(String name, String description) {
|
public RoleRepresentation(String name, String description, boolean scopeParamRequired) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.description = description;
|
this.description = description;
|
||||||
|
this.scopeParamRequired = scopeParamRequired;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
|
@ -75,6 +77,14 @@ public class RoleRepresentation {
|
||||||
this.description = description;
|
this.description = description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean isScopeParamRequired() {
|
||||||
|
return scopeParamRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScopeParamRequired(Boolean scopeParamRequired) {
|
||||||
|
this.scopeParamRequired = scopeParamRequired;
|
||||||
|
}
|
||||||
|
|
||||||
public Composites getComposites() {
|
public Composites getComposites() {
|
||||||
return composites;
|
return composites;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.keycloak.OAuth2Constants;
|
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.UserConsentModel;
|
import org.keycloak.models.UserConsentModel;
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
|
@ -13,7 +12,7 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.protocol.oidc.TokenManager;
|
import org.keycloak.protocol.oidc.TokenManager;
|
||||||
import org.keycloak.services.offline.OfflineUserSessionManager;
|
import org.keycloak.services.offline.OfflineTokenUtils;
|
||||||
import org.keycloak.util.MultivaluedHashMap;
|
import org.keycloak.util.MultivaluedHashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,7 +24,7 @@ public class ApplicationsBean {
|
||||||
|
|
||||||
public ApplicationsBean(RealmModel realm, UserModel user) {
|
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();
|
List<ClientModel> realmClients = realm.getClients();
|
||||||
for (ClientModel client : realmClients) {
|
for (ClientModel client : realmClients) {
|
||||||
|
@ -34,7 +33,7 @@ public class ApplicationsBean {
|
||||||
continue;
|
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)
|
// Don't show applications, which user doesn't have access into (any available roles)
|
||||||
if (availableRoles.isEmpty()) {
|
if (availableRoles.isEmpty()) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -60,7 +59,7 @@ public class ApplicationsBean {
|
||||||
|
|
||||||
List<String> additionalGrants = new ArrayList<>();
|
List<String> additionalGrants = new ArrayList<>();
|
||||||
if (offlineClients.contains(client)) {
|
if (offlineClients.contains(client)) {
|
||||||
additionalGrants.add("${offlineAccess}");
|
additionalGrants.add("${offlineToken}");
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationEntry appEntry = new ApplicationEntry(realmRolesAvailable, resourceRolesAvailable, realmRolesGranted, resourceRolesGranted, client,
|
ApplicationEntry appEntry = new ApplicationEntry(realmRolesAvailable, resourceRolesAvailable, realmRolesGranted, resourceRolesGranted, client,
|
||||||
|
|
|
@ -52,6 +52,7 @@ role_manage-events=Manage events
|
||||||
role_view-profile=View profile
|
role_view-profile=View profile
|
||||||
role_manage-account=Manage account
|
role_manage-account=Manage account
|
||||||
role_read-token=Read token
|
role_read-token=Read token
|
||||||
|
role_offline-access=Offline access
|
||||||
client_account=Account
|
client_account=Account
|
||||||
client_security-admin-console=Security Admin Console
|
client_security-admin-console=Security Admin Console
|
||||||
client_realm-management=Realm Management
|
client_realm-management=Realm Management
|
||||||
|
@ -89,7 +90,7 @@ additionalGrants=Additional Grants
|
||||||
action=Action
|
action=Action
|
||||||
inResource=in
|
inResource=in
|
||||||
fullAccess=Full Access
|
fullAccess=Full Access
|
||||||
offlineAccess=Offline Access
|
offlineToken=Offline Token
|
||||||
revoke=Revoke Grant
|
revoke=Revoke Grant
|
||||||
|
|
||||||
configureAuthenticators=Configured Authenticators
|
configureAuthenticators=Configured Authenticators
|
||||||
|
|
|
@ -79,13 +79,6 @@
|
||||||
<input ng-model="client.serviceAccountsEnabled" name="serviceAccountsEnabled" id="serviceAccountsEnabled" onoffswitch />
|
<input ng-model="client.serviceAccountsEnabled" name="serviceAccountsEnabled" id="serviceAccountsEnabled" onoffswitch />
|
||||||
</div>
|
</div>
|
||||||
</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'">
|
<div class="form-group clearfix block" data-ng-show="protocol == 'saml'">
|
||||||
<label class="col-md-2 control-label" for="samlServerSignature">Include AuthnStatement</label>
|
<label class="col-md-2 control-label" for="samlServerSignature">Include AuthnStatement</label>
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
|
|
|
@ -32,6 +32,13 @@
|
||||||
required> -->
|
required> -->
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<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>
|
<label class="col-md-2 control-label" for="compositeSwitch" class="control-label">Composite Roles</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
|
|
@ -28,6 +28,13 @@
|
||||||
<textarea class="form-control" rows="5" cols="50" id="description" name="description" data-ng-model="role.description"></textarea>
|
<textarea class="form-control" rows="5" cols="50" id="description" name="description" data-ng-model="role.description"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="form-group" data-ng-hide="create">
|
||||||
<label class="col-md-2 control-label" for="compositeSwitch" class="control-label">Composite Roles</label>
|
<label class="col-md-2 control-label" for="compositeSwitch" class="control-label">Composite Roles</label>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
|
|
|
@ -104,6 +104,7 @@ role_manage-events=Manage events
|
||||||
role_view-profile=View profile
|
role_view-profile=View profile
|
||||||
role_manage-account=Manage account
|
role_manage-account=Manage account
|
||||||
role_read-token=Read token
|
role_read-token=Read token
|
||||||
|
role_offline-access=Offline access
|
||||||
client_account=Account
|
client_account=Account
|
||||||
client_security-admin-console=Security Admin Console
|
client_security-admin-console=Security Admin Console
|
||||||
client_realm-management=Realm Management
|
client_realm-management=Realm Management
|
||||||
|
|
|
@ -11,7 +11,7 @@ public interface MigrationModel {
|
||||||
/**
|
/**
|
||||||
* Must have the form of major.minor.micro as the version is parsed and numbers are compared
|
* 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();
|
String getStoredVersion();
|
||||||
void setStoredVersion(String version);
|
void setStoredVersion(String version);
|
||||||
|
|
|
@ -4,6 +4,7 @@ import org.jboss.logging.Logger;
|
||||||
import org.keycloak.migration.migrators.MigrateTo1_3_0;
|
import org.keycloak.migration.migrators.MigrateTo1_3_0;
|
||||||
import org.keycloak.migration.migrators.MigrateTo1_4_0;
|
import org.keycloak.migration.migrators.MigrateTo1_4_0;
|
||||||
import org.keycloak.migration.migrators.MigrateTo1_5_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.migration.migrators.MigrationTo1_2_0_CR1;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
|
||||||
|
@ -47,6 +48,12 @@ public class MigrationModelManager {
|
||||||
}
|
}
|
||||||
new MigrateTo1_5_0().migrate(session);
|
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);
|
model.setStoredVersion(MigrationModel.LATEST_VERSION);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -26,7 +27,9 @@ public class MigrationTo1_2_0_CR1 {
|
||||||
client.setFullScopeAllowed(false);
|
client.setFullScopeAllowed(false);
|
||||||
|
|
||||||
for (String role : Constants.BROKER_SERVICE_ROLES) {
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,9 +109,6 @@ public interface ClientModel extends RoleContainerModel {
|
||||||
boolean isServiceAccountsEnabled();
|
boolean isServiceAccountsEnabled();
|
||||||
void setServiceAccountsEnabled(boolean serviceAccountsEnabled);
|
void setServiceAccountsEnabled(boolean serviceAccountsEnabled);
|
||||||
|
|
||||||
boolean isOfflineTokensEnabled();
|
|
||||||
void setOfflineTokensEnabled(boolean offlineTokensEnabled);
|
|
||||||
|
|
||||||
Set<RoleModel> getScopeMappings();
|
Set<RoleModel> getScopeMappings();
|
||||||
void addScopeMapping(RoleModel role);
|
void addScopeMapping(RoleModel role);
|
||||||
void deleteScopeMapping(RoleModel role);
|
void deleteScopeMapping(RoleModel role);
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package org.keycloak.models;
|
package org.keycloak.models;
|
||||||
|
|
||||||
|
import org.keycloak.OAuth2Constants;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
|
||||||
* @version $Revision: 1 $
|
* @version $Revision: 1 $
|
||||||
|
@ -16,4 +18,5 @@ public interface Constants {
|
||||||
String INSTALLED_APP_URL = "http://localhost";
|
String INSTALLED_APP_URL = "http://localhost";
|
||||||
String READ_TOKEN_ROLE = "read-token";
|
String READ_TOKEN_ROLE = "read-token";
|
||||||
String[] BROKER_SERVICE_ROLES = {READ_TOKEN_ROLE};
|
String[] BROKER_SERVICE_ROLES = {READ_TOKEN_ROLE};
|
||||||
|
String OFFLINE_ACCESS_ROLE = OAuth2Constants.OFFLINE_ACCESS;
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ public class ImpersonationConstants {
|
||||||
if (realmAdminApp.getRole(IMPERSONATION_ROLE) != null) return;
|
if (realmAdminApp.getRole(IMPERSONATION_ROLE) != null) return;
|
||||||
RoleModel impersonationRole = realmAdminApp.addRole(IMPERSONATION_ROLE);
|
RoleModel impersonationRole = realmAdminApp.addRole(IMPERSONATION_ROLE);
|
||||||
impersonationRole.setDescription("${role_" + IMPERSONATION_ROLE + "}");
|
impersonationRole.setDescription("${role_" + IMPERSONATION_ROLE + "}");
|
||||||
|
impersonationRole.setScopeParamRequired(false);
|
||||||
adminRole.addCompositeRole(impersonationRole);
|
adminRole.addCompositeRole(impersonationRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,6 +37,7 @@ public class ImpersonationConstants {
|
||||||
if (realmAdminApp.getRole(IMPERSONATION_ROLE) != null) return;
|
if (realmAdminApp.getRole(IMPERSONATION_ROLE) != null) return;
|
||||||
RoleModel impersonationRole = realmAdminApp.addRole(IMPERSONATION_ROLE);
|
RoleModel impersonationRole = realmAdminApp.addRole(IMPERSONATION_ROLE);
|
||||||
impersonationRole.setDescription("${role_" + IMPERSONATION_ROLE + "}");
|
impersonationRole.setDescription("${role_" + IMPERSONATION_ROLE + "}");
|
||||||
|
impersonationRole.setScopeParamRequired(false);
|
||||||
RoleModel adminRole = realmAdminApp.getRole(AdminRoles.REALM_ADMIN);
|
RoleModel adminRole = realmAdminApp.getRole(AdminRoles.REALM_ADMIN);
|
||||||
adminRole.addCompositeRole(impersonationRole);
|
adminRole.addCompositeRole(impersonationRole);
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,10 @@ public interface RoleModel {
|
||||||
|
|
||||||
void setName(String name);
|
void setName(String name);
|
||||||
|
|
||||||
|
boolean isScopeParamRequired();
|
||||||
|
|
||||||
|
void setScopeParamRequired(boolean scopeParamRequired);
|
||||||
|
|
||||||
boolean isComposite();
|
boolean isComposite();
|
||||||
|
|
||||||
void addCompositeRole(RoleModel role);
|
void addCompositeRole(RoleModel role);
|
||||||
|
|
|
@ -28,7 +28,6 @@ public class ClientEntity extends AbstractIdentifiableEntity {
|
||||||
private boolean bearerOnly;
|
private boolean bearerOnly;
|
||||||
private boolean consentRequired;
|
private boolean consentRequired;
|
||||||
private boolean serviceAccountsEnabled;
|
private boolean serviceAccountsEnabled;
|
||||||
private boolean offlineTokensEnabled;
|
|
||||||
private boolean directGrantsOnly;
|
private boolean directGrantsOnly;
|
||||||
private int nodeReRegistrationTimeout;
|
private int nodeReRegistrationTimeout;
|
||||||
|
|
||||||
|
@ -229,14 +228,6 @@ public class ClientEntity extends AbstractIdentifiableEntity {
|
||||||
this.serviceAccountsEnabled = serviceAccountsEnabled;
|
this.serviceAccountsEnabled = serviceAccountsEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOfflineTokensEnabled() {
|
|
||||||
return offlineTokensEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOfflineTokensEnabled(boolean offlineTokensEnabled) {
|
|
||||||
this.offlineTokensEnabled = offlineTokensEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDirectGrantsOnly() {
|
public boolean isDirectGrantsOnly() {
|
||||||
return directGrantsOnly;
|
return directGrantsOnly;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ public class RoleEntity extends AbstractIdentifiableEntity {
|
||||||
|
|
||||||
private String name;
|
private String name;
|
||||||
private String description;
|
private String description;
|
||||||
|
private boolean scopeParamRequired;
|
||||||
|
|
||||||
private List<String> compositeRoleIds;
|
private List<String> compositeRoleIds;
|
||||||
|
|
||||||
|
@ -31,6 +32,14 @@ public class RoleEntity extends AbstractIdentifiableEntity {
|
||||||
this.description = description;
|
this.description = description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isScopeParamRequired() {
|
||||||
|
return scopeParamRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScopeParamRequired(boolean scopeParamRequired) {
|
||||||
|
this.scopeParamRequired = scopeParamRequired;
|
||||||
|
}
|
||||||
|
|
||||||
public List<String> getCompositeRoleIds() {
|
public List<String> getCompositeRoleIds() {
|
||||||
return compositeRoleIds;
|
return compositeRoleIds;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import org.bouncycastle.openssl.PEMWriter;
|
||||||
import org.keycloak.constants.KerberosConstants;
|
import org.keycloak.constants.KerberosConstants;
|
||||||
import org.keycloak.constants.ServiceAccountConstants;
|
import org.keycloak.constants.ServiceAccountConstants;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.KeycloakSessionTask;
|
import org.keycloak.models.KeycloakSessionTask;
|
||||||
|
@ -360,4 +361,13 @@ public final class KeycloakModelUtils {
|
||||||
public static String toLowerCaseSafe(String str) {
|
public static String toLowerCaseSafe(String str) {
|
||||||
return str==null ? null : str.toLowerCase();
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,7 @@ public class ModelToRepresentation {
|
||||||
rep.setId(role.getId());
|
rep.setId(role.getId());
|
||||||
rep.setName(role.getName());
|
rep.setName(role.getName());
|
||||||
rep.setDescription(role.getDescription());
|
rep.setDescription(role.getDescription());
|
||||||
|
rep.setScopeParamRequired(role.isScopeParamRequired());
|
||||||
rep.setComposite(role.isComposite());
|
rep.setComposite(role.isComposite());
|
||||||
return rep;
|
return rep;
|
||||||
}
|
}
|
||||||
|
@ -303,7 +304,6 @@ public class ModelToRepresentation {
|
||||||
rep.setBearerOnly(clientModel.isBearerOnly());
|
rep.setBearerOnly(clientModel.isBearerOnly());
|
||||||
rep.setConsentRequired(clientModel.isConsentRequired());
|
rep.setConsentRequired(clientModel.isConsentRequired());
|
||||||
rep.setServiceAccountsEnabled(clientModel.isServiceAccountsEnabled());
|
rep.setServiceAccountsEnabled(clientModel.isServiceAccountsEnabled());
|
||||||
rep.setOfflineTokensEnabled(clientModel.isOfflineTokensEnabled());
|
|
||||||
rep.setDirectGrantsOnly(clientModel.isDirectGrantsOnly());
|
rep.setDirectGrantsOnly(clientModel.isDirectGrantsOnly());
|
||||||
rep.setSurrogateAuthRequired(clientModel.isSurrogateAuthRequired());
|
rep.setSurrogateAuthRequired(clientModel.isSurrogateAuthRequired());
|
||||||
rep.setBaseUrl(clientModel.getBaseUrl());
|
rep.setBaseUrl(clientModel.getBaseUrl());
|
||||||
|
|
|
@ -181,6 +181,8 @@ public class RepresentationToModel {
|
||||||
// Application role may already exists (for example if it is defaultRole)
|
// 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());
|
RoleModel role = roleRep.getId()!=null ? client.addRole(roleRep.getId(), roleRep.getName()) : client.addRole(roleRep.getName());
|
||||||
role.setDescription(roleRep.getDescription());
|
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) {
|
public static void createRole(RealmModel newRealm, RoleRepresentation roleRep) {
|
||||||
RoleModel role = roleRep.getId()!=null ? newRealm.addRole(roleRep.getId(), roleRep.getName()) : newRealm.addRole(roleRep.getName());
|
RoleModel role = roleRep.getId()!=null ? newRealm.addRole(roleRep.getId(), roleRep.getName()) : newRealm.addRole(roleRep.getName());
|
||||||
if (roleRep.getDescription() != null) role.setDescription(roleRep.getDescription());
|
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) {
|
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.isBearerOnly() != null) client.setBearerOnly(resourceRep.isBearerOnly());
|
||||||
if (resourceRep.isConsentRequired() != null) client.setConsentRequired(resourceRep.isConsentRequired());
|
if (resourceRep.isConsentRequired() != null) client.setConsentRequired(resourceRep.isConsentRequired());
|
||||||
if (resourceRep.isServiceAccountsEnabled() != null) client.setServiceAccountsEnabled(resourceRep.isServiceAccountsEnabled());
|
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.isDirectGrantsOnly() != null) client.setDirectGrantsOnly(resourceRep.isDirectGrantsOnly());
|
||||||
if (resourceRep.isPublicClient() != null) client.setPublicClient(resourceRep.isPublicClient());
|
if (resourceRep.isPublicClient() != null) client.setPublicClient(resourceRep.isPublicClient());
|
||||||
if (resourceRep.isFrontchannelLogout() != null) client.setFrontchannelLogout(resourceRep.isFrontchannelLogout());
|
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.isBearerOnly() != null) resource.setBearerOnly(rep.isBearerOnly());
|
||||||
if (rep.isConsentRequired() != null) resource.setConsentRequired(rep.isConsentRequired());
|
if (rep.isConsentRequired() != null) resource.setConsentRequired(rep.isConsentRequired());
|
||||||
if (rep.isServiceAccountsEnabled() != null) resource.setServiceAccountsEnabled(rep.isServiceAccountsEnabled());
|
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.isDirectGrantsOnly() != null) resource.setDirectGrantsOnly(rep.isDirectGrantsOnly());
|
||||||
if (rep.isPublicClient() != null) resource.setPublicClient(rep.isPublicClient());
|
if (rep.isPublicClient() != null) resource.setPublicClient(rep.isPublicClient());
|
||||||
if (rep.isFullScopeAllowed() != null) resource.setFullScopeAllowed(rep.isFullScopeAllowed());
|
if (rep.isFullScopeAllowed() != null) resource.setFullScopeAllowed(rep.isFullScopeAllowed());
|
||||||
|
|
|
@ -461,16 +461,6 @@ public class ClientAdapter implements ClientModel {
|
||||||
entity.setServiceAccountsEnabled(serviceAccountsEnabled);
|
entity.setServiceAccountsEnabled(serviceAccountsEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOfflineTokensEnabled() {
|
|
||||||
return entity.isOfflineTokensEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setOfflineTokensEnabled(boolean offlineTokensEnabled) {
|
|
||||||
entity.setOfflineTokensEnabled(offlineTokensEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isDirectGrantsOnly() {
|
public boolean isDirectGrantsOnly() {
|
||||||
return entity.isDirectGrantsOnly();
|
return entity.isDirectGrantsOnly();
|
||||||
|
|
|
@ -88,6 +88,16 @@ public class RoleAdapter implements RoleModel {
|
||||||
role.setDescription(description);
|
role.setDescription(description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isScopeParamRequired() {
|
||||||
|
return role.isScopeParamRequired();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setScopeParamRequired(boolean scopeParamRequired) {
|
||||||
|
role.setScopeParamRequired(scopeParamRequired);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isComposite() {
|
public boolean isComposite() {
|
||||||
return role.getCompositeRoleIds() != null && role.getCompositeRoleIds().size() > 0;
|
return role.getCompositeRoleIds() != null && role.getCompositeRoleIds().size() > 0;
|
||||||
|
|
|
@ -430,18 +430,6 @@ public class ClientAdapter implements ClientModel {
|
||||||
updated.setServiceAccountsEnabled(serviceAccountsEnabled);
|
updated.setServiceAccountsEnabled(serviceAccountsEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOfflineTokensEnabled() {
|
|
||||||
if (updated != null) return updated.isOfflineTokensEnabled();
|
|
||||||
return cached.isOfflineTokensEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setOfflineTokensEnabled(boolean offlineTokensEnabled) {
|
|
||||||
getDelegateForUpdate();
|
|
||||||
updated.setOfflineTokensEnabled(offlineTokensEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RoleModel getRole(String name) {
|
public RoleModel getRole(String name) {
|
||||||
if (updated != null) return updated.getRole(name);
|
if (updated != null) return updated.getRole(name);
|
||||||
|
|
|
@ -59,6 +59,18 @@ public class RoleAdapter implements RoleModel {
|
||||||
updated.setDescription(description);
|
updated.setDescription(description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isScopeParamRequired() {
|
||||||
|
if (updated != null) return updated.isScopeParamRequired();
|
||||||
|
return cached.isScopeParamRequired();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setScopeParamRequired(boolean scopeParamRequired) {
|
||||||
|
getDelegateForUpdate();
|
||||||
|
updated.setScopeParamRequired(scopeParamRequired);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
if (updated != null) return updated.getId();
|
if (updated != null) return updated.getId();
|
||||||
|
|
|
@ -47,7 +47,6 @@ public class CachedClient implements Serializable {
|
||||||
private boolean bearerOnly;
|
private boolean bearerOnly;
|
||||||
private boolean consentRequired;
|
private boolean consentRequired;
|
||||||
private boolean serviceAccountsEnabled;
|
private boolean serviceAccountsEnabled;
|
||||||
private boolean offlineTokensEnabled;
|
|
||||||
private Map<String, String> roles = new HashMap<String, String>();
|
private Map<String, String> roles = new HashMap<String, String>();
|
||||||
private int nodeReRegistrationTimeout;
|
private int nodeReRegistrationTimeout;
|
||||||
private Map<String, Integer> registeredNodes;
|
private Map<String, Integer> registeredNodes;
|
||||||
|
@ -82,7 +81,6 @@ public class CachedClient implements Serializable {
|
||||||
bearerOnly = model.isBearerOnly();
|
bearerOnly = model.isBearerOnly();
|
||||||
consentRequired = model.isConsentRequired();
|
consentRequired = model.isConsentRequired();
|
||||||
serviceAccountsEnabled = model.isServiceAccountsEnabled();
|
serviceAccountsEnabled = model.isServiceAccountsEnabled();
|
||||||
offlineTokensEnabled = model.isOfflineTokensEnabled();
|
|
||||||
for (RoleModel role : model.getRoles()) {
|
for (RoleModel role : model.getRoles()) {
|
||||||
roles.put(role.getName(), role.getId());
|
roles.put(role.getName(), role.getId());
|
||||||
cache.addCachedRole(new CachedClientRole(id, role, realm));
|
cache.addCachedRole(new CachedClientRole(id, role, realm));
|
||||||
|
@ -191,10 +189,6 @@ public class CachedClient implements Serializable {
|
||||||
return serviceAccountsEnabled;
|
return serviceAccountsEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOfflineTokensEnabled() {
|
|
||||||
return offlineTokensEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, String> getRoles() {
|
public Map<String, String> getRoles() {
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ public class CachedRole implements Serializable {
|
||||||
final protected String name;
|
final protected String name;
|
||||||
final protected String realm;
|
final protected String realm;
|
||||||
final protected String description;
|
final protected String description;
|
||||||
|
final protected Boolean scopeParamRequired;
|
||||||
final protected boolean composite;
|
final protected boolean composite;
|
||||||
final protected Set<String> composites = new HashSet<String>();
|
final protected Set<String> composites = new HashSet<String>();
|
||||||
|
|
||||||
|
@ -25,6 +26,7 @@ public class CachedRole implements Serializable {
|
||||||
description = model.getDescription();
|
description = model.getDescription();
|
||||||
id = model.getId();
|
id = model.getId();
|
||||||
name = model.getName();
|
name = model.getName();
|
||||||
|
scopeParamRequired = model.isScopeParamRequired();
|
||||||
this.realm = realm.getId();
|
this.realm = realm.getId();
|
||||||
if (composite) {
|
if (composite) {
|
||||||
for (RoleModel child : model.getComposites()) {
|
for (RoleModel child : model.getComposites()) {
|
||||||
|
@ -50,6 +52,10 @@ public class CachedRole implements Serializable {
|
||||||
return description;
|
return description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean isScopeParamRequired() {
|
||||||
|
return scopeParamRequired;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isComposite() {
|
public boolean isComposite() {
|
||||||
return composite;
|
return composite;
|
||||||
}
|
}
|
||||||
|
|
|
@ -481,16 +481,6 @@ public class ClientAdapter implements ClientModel {
|
||||||
entity.setServiceAccountsEnabled(serviceAccountsEnabled);
|
entity.setServiceAccountsEnabled(serviceAccountsEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOfflineTokensEnabled() {
|
|
||||||
return entity.isOfflineTokensEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setOfflineTokensEnabled(boolean offlineTokensEnabled) {
|
|
||||||
entity.setOfflineTokensEnabled(offlineTokensEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isDirectGrantsOnly() {
|
public boolean isDirectGrantsOnly() {
|
||||||
return entity.isDirectGrantsOnly();
|
return entity.isDirectGrantsOnly();
|
||||||
|
|
|
@ -49,6 +49,16 @@ public class RoleAdapter implements RoleModel {
|
||||||
role.setDescription(description);
|
role.setDescription(description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isScopeParamRequired() {
|
||||||
|
return role.isScopeParamRequired();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setScopeParamRequired(boolean scopeParamRequired) {
|
||||||
|
role.setScopeParamRequired(scopeParamRequired);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getId() {
|
public String getId() {
|
||||||
return role.getId();
|
return role.getId();
|
||||||
|
|
|
@ -100,9 +100,6 @@ public class ClientEntity {
|
||||||
@Column(name="SERVICE_ACCOUNTS_ENABLED")
|
@Column(name="SERVICE_ACCOUNTS_ENABLED")
|
||||||
private boolean serviceAccountsEnabled;
|
private boolean serviceAccountsEnabled;
|
||||||
|
|
||||||
@Column(name="OFFLINE_TOKENS_ENABLED")
|
|
||||||
private boolean offlineTokensEnabled;
|
|
||||||
|
|
||||||
@Column(name="NODE_REREG_TIMEOUT")
|
@Column(name="NODE_REREG_TIMEOUT")
|
||||||
private int nodeReRegistrationTimeout;
|
private int nodeReRegistrationTimeout;
|
||||||
|
|
||||||
|
@ -319,14 +316,6 @@ public class ClientEntity {
|
||||||
this.serviceAccountsEnabled = serviceAccountsEnabled;
|
this.serviceAccountsEnabled = serviceAccountsEnabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isOfflineTokensEnabled() {
|
|
||||||
return offlineTokensEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOfflineTokensEnabled(boolean offlineTokensEnabled) {
|
|
||||||
this.offlineTokensEnabled = offlineTokensEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDirectGrantsOnly() {
|
public boolean isDirectGrantsOnly() {
|
||||||
return directGrantsOnly;
|
return directGrantsOnly;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,8 @@ public class RoleEntity {
|
||||||
private String name;
|
private String name;
|
||||||
@Column(name = "DESCRIPTION")
|
@Column(name = "DESCRIPTION")
|
||||||
private String description;
|
private String description;
|
||||||
|
@Column(name = "SCOPE_PARAM_REQUIRED")
|
||||||
|
private boolean scopeParamRequired;
|
||||||
|
|
||||||
// hax! couldn't get constraint to work properly
|
// hax! couldn't get constraint to work properly
|
||||||
@Column(name = "REALM_ID")
|
@Column(name = "REALM_ID")
|
||||||
|
@ -93,6 +95,14 @@ public class RoleEntity {
|
||||||
this.description = description;
|
this.description = description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isScopeParamRequired() {
|
||||||
|
return scopeParamRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setScopeParamRequired(boolean scopeParamRequired) {
|
||||||
|
this.scopeParamRequired = scopeParamRequired;
|
||||||
|
}
|
||||||
|
|
||||||
public Collection<RoleEntity> getCompositeRoles() {
|
public Collection<RoleEntity> getCompositeRoles() {
|
||||||
return compositeRoles;
|
return compositeRoles;
|
||||||
}
|
}
|
||||||
|
|
|
@ -483,17 +483,6 @@ public class ClientAdapter extends AbstractMongoAdapter<MongoClientEntity> imple
|
||||||
updateMongoEntity();
|
updateMongoEntity();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isOfflineTokensEnabled() {
|
|
||||||
return getMongoEntity().isOfflineTokensEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setOfflineTokensEnabled(boolean offlineTokensEnabled) {
|
|
||||||
getMongoEntity().setOfflineTokensEnabled(offlineTokensEnabled);
|
|
||||||
updateMongoEntity();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isDirectGrantsOnly() {
|
public boolean isDirectGrantsOnly() {
|
||||||
return getMongoEntity().isDirectGrantsOnly();
|
return getMongoEntity().isDirectGrantsOnly();
|
||||||
|
|
|
@ -68,6 +68,17 @@ public class RoleAdapter extends AbstractMongoAdapter<MongoRoleEntity> implement
|
||||||
updateRole();
|
updateRole();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isScopeParamRequired() {
|
||||||
|
return role.isScopeParamRequired();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setScopeParamRequired(boolean scopeParamRequired) {
|
||||||
|
role.setScopeParamRequired(scopeParamRequired);
|
||||||
|
updateRole();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isComposite() {
|
public boolean isComposite() {
|
||||||
return role.getCompositeRoleIds() != null && role.getCompositeRoleIds().size() > 0;
|
return role.getCompositeRoleIds() != null && role.getCompositeRoleIds().size() > 0;
|
||||||
|
|
|
@ -2,6 +2,7 @@ package org.keycloak.protocol.oidc;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.ClientConnection;
|
import org.keycloak.ClientConnection;
|
||||||
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.OAuthErrorException;
|
import org.keycloak.OAuthErrorException;
|
||||||
import org.keycloak.events.Details;
|
import org.keycloak.events.Details;
|
||||||
import org.keycloak.events.Errors;
|
import org.keycloak.events.Errors;
|
||||||
|
@ -30,7 +31,7 @@ import org.keycloak.representations.RefreshToken;
|
||||||
import org.keycloak.services.ErrorResponseException;
|
import org.keycloak.services.ErrorResponseException;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.ClientSessionCode;
|
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.RefreshTokenUtil;
|
||||||
import org.keycloak.util.Time;
|
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.Response;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -92,13 +96,9 @@ public class TokenManager {
|
||||||
UserSessionModel userSession = null;
|
UserSessionModel userSession = null;
|
||||||
ClientSessionModel clientSession = null;
|
ClientSessionModel clientSession = null;
|
||||||
if (RefreshTokenUtil.TOKEN_TYPE_OFFLINE.equals(oldToken.getType())) {
|
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();
|
userSession = clientSession.getUserSession();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -136,7 +136,8 @@ public class TokenManager {
|
||||||
|
|
||||||
|
|
||||||
// recreate token.
|
// 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);
|
AccessToken newToken = createClientAccessToken(session, requestedRoles, realm, client, user, userSession, clientSession);
|
||||||
verifyAccess(oldToken, newToken);
|
verifyAccess(oldToken, newToken);
|
||||||
|
|
||||||
|
@ -233,7 +234,8 @@ public class TokenManager {
|
||||||
clientSession.setUserSession(session);
|
clientSession.setUserSession(session);
|
||||||
Set<String> requestedRoles = new HashSet<String>();
|
Set<String> requestedRoles = new HashSet<String>();
|
||||||
// todo scope param protocol independent
|
// 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());
|
requestedRoles.add(r.getId());
|
||||||
}
|
}
|
||||||
clientSession.setRoles(requestedRoles);
|
clientSession.setRoles(requestedRoles);
|
||||||
|
@ -269,12 +271,14 @@ public class TokenManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Set<RoleModel> getAccess(String scopeParam, ClientModel client, UserModel user) {
|
public static Set<RoleModel> getAccess(String scopeParam, boolean applyScopeParam, ClientModel client, UserModel user) {
|
||||||
// todo scopeParam is ignored until we figure out a scheme that fits with openid connect
|
|
||||||
Set<RoleModel> requestedRoles = new HashSet<RoleModel>();
|
Set<RoleModel> requestedRoles = new HashSet<RoleModel>();
|
||||||
|
|
||||||
Set<RoleModel> roleMappings = user.getRoleMappings();
|
Set<RoleModel> roleMappings = user.getRoleMappings();
|
||||||
if (client.isFullScopeAllowed()) return roleMappings;
|
|
||||||
|
if (client.isFullScopeAllowed()) {
|
||||||
|
requestedRoles = roleMappings;
|
||||||
|
} else {
|
||||||
|
|
||||||
Set<RoleModel> scopeMappings = client.getScopeMappings();
|
Set<RoleModel> scopeMappings = client.getScopeMappings();
|
||||||
scopeMappings.addAll(client.getRoles());
|
scopeMappings.addAll(client.getRoles());
|
||||||
|
@ -285,10 +289,44 @@ public class TokenManager {
|
||||||
applyScope(role, desiredRole, visited, requestedRoles);
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
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 {
|
public void verifyAccess(AccessToken token, AccessToken newToken) throws OAuthErrorException {
|
||||||
if (token.getRealmAccess() != null) {
|
if (token.getRealmAccess() != null) {
|
||||||
if (newToken.getRealmAccess() == null) throw new OAuthErrorException(OAuthErrorException.INVALID_SCOPE, "User no long has permission for realm roles");
|
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() {
|
public AccessTokenResponseBuilder generateAccessToken() {
|
||||||
UserModel user = userSession.getUser();
|
UserModel user = userSession.getUser();
|
||||||
String scopeParam = clientSession.getNote(OIDCLoginProtocol.SCOPE_PARAM);
|
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);
|
accessToken = createClientAccessToken(session, requestedRoles, realm, client, user, userSession, clientSession);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -450,14 +488,14 @@ public class TokenManager {
|
||||||
String scopeParam = clientSession.getNote(OIDCLoginProtocol.SCOPE_PARAM);
|
String scopeParam = clientSession.getNote(OIDCLoginProtocol.SCOPE_PARAM);
|
||||||
boolean offlineTokenRequested = RefreshTokenUtil.isOfflineTokenRequested(scopeParam);
|
boolean offlineTokenRequested = RefreshTokenUtil.isOfflineTokenRequested(scopeParam);
|
||||||
if (offlineTokenRequested) {
|
if (offlineTokenRequested) {
|
||||||
if (!clientSession.getClient().isOfflineTokensEnabled()) {
|
if (!OfflineTokenUtils.isOfflineTokenAllowed(realm, clientSession)) {
|
||||||
event.error(Errors.INVALID_CLIENT);
|
event.error(Errors.NOT_ALLOWED);
|
||||||
throw new ErrorResponseException("invalid_client", "Offline tokens not allowed for the client", Response.Status.BAD_REQUEST);
|
throw new ErrorResponseException("not_allowed", "Offline tokens not allowed for the user or client", Response.Status.BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshToken = new RefreshToken(accessToken);
|
refreshToken = new RefreshToken(accessToken);
|
||||||
refreshToken.type(RefreshTokenUtil.TOKEN_TYPE_OFFLINE);
|
refreshToken.type(RefreshTokenUtil.TOKEN_TYPE_OFFLINE);
|
||||||
new OfflineUserSessionManager().persistOfflineSession(clientSession, userSession);
|
OfflineTokenUtils.persistOfflineSession(clientSession, userSession);
|
||||||
} else {
|
} else {
|
||||||
refreshToken = new RefreshToken(accessToken);
|
refreshToken = new RefreshToken(accessToken);
|
||||||
refreshToken.expiration(Time.currentTime() + realm.getSsoSessionIdleTimeout());
|
refreshToken.expiration(Time.currentTime() + realm.getSsoSessionIdleTimeout());
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.idm.OAuthClientRepresentation;
|
import org.keycloak.representations.idm.OAuthClientRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
import org.keycloak.representations.idm.RoleRepresentation;
|
||||||
import org.keycloak.timer.TimerProvider;
|
import org.keycloak.timer.TimerProvider;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -95,6 +96,7 @@ public class RealmManager implements RealmImporter {
|
||||||
setupImpersonationService(realm);
|
setupImpersonationService(realm);
|
||||||
setupAuthenticationFlows(realm);
|
setupAuthenticationFlows(realm);
|
||||||
setupRequiredActions(realm);
|
setupRequiredActions(realm);
|
||||||
|
setupOfflineTokens(realm);
|
||||||
|
|
||||||
return realm;
|
return realm;
|
||||||
}
|
}
|
||||||
|
@ -107,6 +109,10 @@ public class RealmManager implements RealmImporter {
|
||||||
if (realm.getRequiredActionProviders().size() == 0) DefaultRequiredActions.addActions(realm);
|
if (realm.getRequiredActionProviders().size() == 0) DefaultRequiredActions.addActions(realm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void setupOfflineTokens(RealmModel realm) {
|
||||||
|
KeycloakModelUtils.setupOfflineTokens(realm);
|
||||||
|
}
|
||||||
|
|
||||||
protected void setupAdminConsole(RealmModel realm) {
|
protected void setupAdminConsole(RealmModel realm) {
|
||||||
ClientModel adminConsole = realm.getClientByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID);
|
ClientModel adminConsole = realm.getClientByClientId(Constants.ADMIN_CONSOLE_CLIENT_ID);
|
||||||
if (adminConsole == null) adminConsole = new ClientManager(this).createClient(realm, 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);
|
RoleModel createRealmRole = realm.addRole(AdminRoles.CREATE_REALM);
|
||||||
adminRole.addCompositeRole(createRealmRole);
|
adminRole.addCompositeRole(createRealmRole);
|
||||||
createRealmRole.setDescription("${role_"+AdminRoles.CREATE_REALM+"}");
|
createRealmRole.setDescription("${role_" + AdminRoles.CREATE_REALM + "}");
|
||||||
|
createRealmRole.setScopeParamRequired(false);
|
||||||
} else {
|
} else {
|
||||||
adminRealm = model.getRealmByName(Config.getAdminRealm());
|
adminRealm = model.getRealmByName(Config.getAdminRealm());
|
||||||
adminRole = adminRealm.getRole(AdminRoles.ADMIN);
|
adminRole = adminRealm.getRole(AdminRoles.ADMIN);
|
||||||
}
|
}
|
||||||
adminRole.setDescription("${role_"+AdminRoles.ADMIN+"}");
|
adminRole.setDescription("${role_"+AdminRoles.ADMIN+"}");
|
||||||
|
adminRole.setScopeParamRequired(false);
|
||||||
|
|
||||||
ClientModel realmAdminApp = KeycloakModelUtils.createClient(adminRealm, KeycloakModelUtils.getMasterRealmAdminApplicationClientId(realm.getName()));
|
ClientModel realmAdminApp = KeycloakModelUtils.createClient(adminRealm, KeycloakModelUtils.getMasterRealmAdminApplicationClientId(realm.getName()));
|
||||||
// No localized name for now
|
// No localized name for now
|
||||||
|
@ -232,6 +240,7 @@ public class RealmManager implements RealmImporter {
|
||||||
for (String r : AdminRoles.ALL_REALM_ROLES) {
|
for (String r : AdminRoles.ALL_REALM_ROLES) {
|
||||||
RoleModel role = realmAdminApp.addRole(r);
|
RoleModel role = realmAdminApp.addRole(r);
|
||||||
role.setDescription("${role_"+r+"}");
|
role.setDescription("${role_"+r+"}");
|
||||||
|
role.setScopeParamRequired(false);
|
||||||
adminRole.addCompositeRole(role);
|
adminRole.addCompositeRole(role);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -249,12 +258,14 @@ public class RealmManager implements RealmImporter {
|
||||||
}
|
}
|
||||||
RoleModel adminRole = realmAdminClient.addRole(AdminRoles.REALM_ADMIN);
|
RoleModel adminRole = realmAdminClient.addRole(AdminRoles.REALM_ADMIN);
|
||||||
adminRole.setDescription("${role_" + AdminRoles.REALM_ADMIN + "}");
|
adminRole.setDescription("${role_" + AdminRoles.REALM_ADMIN + "}");
|
||||||
|
adminRole.setScopeParamRequired(false);
|
||||||
realmAdminClient.setBearerOnly(true);
|
realmAdminClient.setBearerOnly(true);
|
||||||
realmAdminClient.setFullScopeAllowed(false);
|
realmAdminClient.setFullScopeAllowed(false);
|
||||||
|
|
||||||
for (String r : AdminRoles.ALL_REALM_ROLES) {
|
for (String r : AdminRoles.ALL_REALM_ROLES) {
|
||||||
RoleModel role = realmAdminClient.addRole(r);
|
RoleModel role = realmAdminClient.addRole(r);
|
||||||
role.setDescription("${role_"+r+"}");
|
role.setDescription("${role_"+r+"}");
|
||||||
|
role.setScopeParamRequired(false);
|
||||||
adminRole.addCompositeRole(role);
|
adminRole.addCompositeRole(role);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -274,7 +285,9 @@ public class RealmManager implements RealmImporter {
|
||||||
|
|
||||||
for (String role : AccountRoles.ALL) {
|
for (String role : AccountRoles.ALL) {
|
||||||
client.addDefaultRole(role);
|
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);
|
client.setFullScopeAllowed(false);
|
||||||
|
|
||||||
for (String role : Constants.BROKER_SERVICE_ROLES) {
|
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 (!hasBrokerClient(rep)) setupBrokerService(realm);
|
||||||
if (!hasAdminConsoleClient(rep)) setupAdminConsole(realm);
|
if (!hasAdminConsoleClient(rep)) setupAdminConsole(realm);
|
||||||
|
if (!hasRealmRole(rep, Constants.OFFLINE_ACCESS_ROLE)) setupOfflineTokens(realm);
|
||||||
|
|
||||||
RepresentationToModel.importRealm(session, rep, realm);
|
RepresentationToModel.importRealm(session, rep, realm);
|
||||||
|
|
||||||
|
@ -409,6 +425,20 @@ public class RealmManager implements RealmImporter {
|
||||||
return false;
|
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:
|
* Query users based on a search string:
|
||||||
* <p/>
|
* <p/>
|
||||||
|
|
|
@ -3,18 +3,17 @@ package org.keycloak.services.offline;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ClientSessionModel;
|
import org.keycloak.models.ClientSessionModel;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.ModelException;
|
import org.keycloak.models.ModelException;
|
||||||
import org.keycloak.models.OfflineClientSessionModel;
|
import org.keycloak.models.OfflineClientSessionModel;
|
||||||
import org.keycloak.models.OfflineUserSessionModel;
|
import org.keycloak.models.OfflineUserSessionModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.util.JsonSerialization;
|
import org.keycloak.util.JsonSerialization;
|
||||||
|
@ -24,11 +23,11 @@ import org.keycloak.util.JsonSerialization;
|
||||||
*
|
*
|
||||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
*/
|
*/
|
||||||
public class OfflineUserSessionManager {
|
public class OfflineTokenUtils {
|
||||||
|
|
||||||
protected static Logger logger = Logger.getLogger(OfflineUserSessionManager.class);
|
protected static Logger logger = Logger.getLogger(OfflineTokenUtils.class);
|
||||||
|
|
||||||
public void persistOfflineSession(ClientSessionModel clientSession, UserSessionModel userSession) {
|
public static void persistOfflineSession(ClientSessionModel clientSession, UserSessionModel userSession) {
|
||||||
UserModel user = userSession.getUser();
|
UserModel user = userSession.getUser();
|
||||||
ClientModel client = clientSession.getClient();
|
ClientModel client = clientSession.getClient();
|
||||||
|
|
||||||
|
@ -61,7 +60,7 @@ public class OfflineUserSessionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// userSessionId is provided from offline token. It's used just to verify if it match the ID from clientSession representation
|
// userSessionId is provided from offline token. It's used just to verify if it match the ID from clientSession representation
|
||||||
public ClientSessionModel findOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId, String userSessionId) {
|
public static ClientSessionModel findOfflineClientSession(RealmModel realm, UserModel user, String clientSessionId, String userSessionId) {
|
||||||
OfflineClientSessionModel clientSession = user.getOfflineClientSession(clientSessionId);
|
OfflineClientSessionModel clientSession = user.getOfflineClientSession(clientSessionId);
|
||||||
if (clientSession == null) {
|
if (clientSession == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -85,7 +84,7 @@ public class OfflineUserSessionManager {
|
||||||
return clientSessionAdapter;
|
return clientSessionAdapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<ClientModel> findClientsWithOfflineToken(RealmModel realm, UserModel user) {
|
public static Set<ClientModel> findClientsWithOfflineToken(RealmModel realm, UserModel user) {
|
||||||
Collection<OfflineClientSessionModel> clientSessions = user.getOfflineClientSessions();
|
Collection<OfflineClientSessionModel> clientSessions = user.getOfflineClientSessions();
|
||||||
Set<ClientModel> clients = new HashSet<>();
|
Set<ClientModel> clients = new HashSet<>();
|
||||||
for (OfflineClientSessionModel clientSession : clientSessions) {
|
for (OfflineClientSessionModel clientSession : clientSessions) {
|
||||||
|
@ -95,7 +94,7 @@ public class OfflineUserSessionManager {
|
||||||
return clients;
|
return clients;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean revokeOfflineToken(UserModel user, ClientModel client) {
|
public static boolean revokeOfflineToken(UserModel user, ClientModel client) {
|
||||||
Collection<OfflineClientSessionModel> clientSessions = user.getOfflineClientSessions();
|
Collection<OfflineClientSessionModel> clientSessions = user.getOfflineClientSessions();
|
||||||
boolean anyRemoved = false;
|
boolean anyRemoved = false;
|
||||||
for (OfflineClientSessionModel clientSession : clientSessions) {
|
for (OfflineClientSessionModel clientSession : clientSessions) {
|
||||||
|
@ -114,7 +113,17 @@ public class OfflineUserSessionManager {
|
||||||
return anyRemoved;
|
return anyRemoved;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createOfflineUserSession(UserModel user, UserSessionModel userSession) {
|
public static boolean isOfflineTokenAllowed(RealmModel realm, ClientSessionModel clientSession) {
|
||||||
|
RoleModel offlineAccessRole = realm.getRole(Constants.OFFLINE_ACCESS_ROLE);
|
||||||
|
if (offlineAccessRole == null) {
|
||||||
|
logger.warnf("Role '%s' not available in realm", Constants.OFFLINE_ACCESS_ROLE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return clientSession.getRoles().contains(offlineAccessRole.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void createOfflineUserSession(UserModel user, UserSessionModel userSession) {
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.tracef("Creating new offline user session. UserSessionID: '%s' , Username: '%s'", userSession.getId(), user.getUsername());
|
logger.tracef("Creating new offline user session. UserSessionID: '%s' , Username: '%s'", userSession.getId(), user.getUsername());
|
||||||
}
|
}
|
||||||
|
@ -138,7 +147,7 @@ public class OfflineUserSessionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createOfflineClientSession(UserModel user, ClientSessionModel clientSession, UserSessionModel userSession) {
|
private static void createOfflineClientSession(UserModel user, ClientSessionModel clientSession, UserSessionModel userSession) {
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.tracef("Creating new offline token client session. ClientSessionId: '%s', UserSessionID: '%s' , Username: '%s', Client: '%s'" ,
|
logger.tracef("Creating new offline token client session. ClientSessionId: '%s', UserSessionID: '%s' , Username: '%s', Client: '%s'" ,
|
||||||
clientSession.getId(), userSession.getId(), user.getUsername(), clientSession.getClient().getClientId());
|
clientSession.getId(), userSession.getId(), user.getUsername(), clientSession.getClient().getClientId());
|
||||||
|
@ -165,7 +174,7 @@ public class OfflineUserSessionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if userSession has any offline clientSessions attached to it. Remove userSession if not
|
// Check if userSession has any offline clientSessions attached to it. Remove userSession if not
|
||||||
private void checkUserSessionHasClientSessions(UserModel user, String userSessionId) {
|
private static void checkUserSessionHasClientSessions(UserModel user, String userSessionId) {
|
||||||
Collection<OfflineClientSessionModel> clientSessions = user.getOfflineClientSessions();
|
Collection<OfflineClientSessionModel> clientSessions = user.getOfflineClientSessions();
|
||||||
|
|
||||||
for (OfflineClientSessionModel clientSession : clientSessions) {
|
for (OfflineClientSessionModel clientSession : clientSessions) {
|
|
@ -46,7 +46,6 @@ import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.utils.CredentialValidation;
|
import org.keycloak.models.utils.CredentialValidation;
|
||||||
import org.keycloak.models.utils.FormMessage;
|
import org.keycloak.models.utils.FormMessage;
|
||||||
import org.keycloak.models.utils.ModelToRepresentation;
|
import org.keycloak.models.utils.ModelToRepresentation;
|
||||||
import org.keycloak.models.utils.TimeBasedOTP;
|
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
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.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.ClientSessionCode;
|
import org.keycloak.services.managers.ClientSessionCode;
|
||||||
import org.keycloak.services.messages.Messages;
|
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.util.ResolveRelative;
|
||||||
import org.keycloak.services.validation.Validation;
|
import org.keycloak.services.validation.Validation;
|
||||||
import org.keycloak.util.UriUtils;
|
import org.keycloak.util.UriUtils;
|
||||||
|
@ -487,7 +486,7 @@ public class AccountService extends AbstractSecuredLocalService {
|
||||||
// Revoke grant in UserModel
|
// Revoke grant in UserModel
|
||||||
UserModel user = auth.getUser();
|
UserModel user = auth.getUser();
|
||||||
user.revokeConsentForClient(client.getId());
|
user.revokeConsentForClient(client.getId());
|
||||||
new OfflineUserSessionManager().revokeOfflineToken(user, client);
|
OfflineTokenUtils.revokeOfflineToken(user, client);
|
||||||
|
|
||||||
// Logout clientSessions for this user and client
|
// Logout clientSessions for this user and client
|
||||||
AuthenticationManager.backchannelUserFromClient(session, realm, user, client, uriInfo, headers);
|
AuthenticationManager.backchannelUserFromClient(session, realm, user, client, uriInfo, headers);
|
||||||
|
|
|
@ -82,6 +82,8 @@ public class RoleContainerResource extends RoleResource {
|
||||||
try {
|
try {
|
||||||
RoleModel role = roleContainer.addRole(rep.getName());
|
RoleModel role = roleContainer.addRole(rep.getName());
|
||||||
role.setDescription(rep.getDescription());
|
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();
|
adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo, role.getId()).representation(rep).success();
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ public abstract class RoleResource {
|
||||||
protected void updateRole(RoleRepresentation rep, RoleModel role) {
|
protected void updateRole(RoleRepresentation rep, RoleModel role) {
|
||||||
role.setName(rep.getName());
|
role.setName(rep.getName());
|
||||||
role.setDescription(rep.getDescription());
|
role.setDescription(rep.getDescription());
|
||||||
|
if (rep.isScopeParamRequired() != null) role.setScopeParamRequired(rep.isScopeParamRequired());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addComposites(AdminEventBuilder adminEvent, UriInfo uriInfo, List<RoleRepresentation> roles, RoleModel role) {
|
protected void addComposites(AdminEventBuilder adminEvent, UriInfo uriInfo, List<RoleRepresentation> roles, RoleModel role) {
|
||||||
|
|
|
@ -668,7 +668,8 @@ public class AccountTest {
|
||||||
Assert.assertTrue(accountEntry.getProtocolMappersGranted().contains("Full Access"));
|
Assert.assertTrue(accountEntry.getProtocolMappersGranted().contains("Full Access"));
|
||||||
|
|
||||||
AccountApplicationsPage.AppEntry testAppEntry = apps.get("test-app");
|
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.getRolesGranted().contains("Full Access"));
|
||||||
Assert.assertTrue(testAppEntry.getProtocolMappersGranted().contains("Full Access"));
|
Assert.assertTrue(testAppEntry.getProtocolMappersGranted().contains("Full Access"));
|
||||||
|
|
||||||
|
|
|
@ -85,7 +85,7 @@ public class AdminAPITest {
|
||||||
ClientSessionModel clientSession = session.sessions().createClientSession(adminRealm, adminConsole);
|
ClientSessionModel clientSession = session.sessions().createClientSession(adminRealm, adminConsole);
|
||||||
clientSession.setNote(OIDCLoginProtocol.ISSUER, "http://localhost:8081/auth/realms/master");
|
clientSession.setNote(OIDCLoginProtocol.ISSUER, "http://localhost:8081/auth/realms/master");
|
||||||
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false, null, null);
|
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);
|
return tm.encodeToken(adminRealm, token);
|
||||||
} finally {
|
} finally {
|
||||||
keycloakRule.stopSession(session, true);
|
keycloakRule.stopSession(session, true);
|
||||||
|
|
|
@ -108,7 +108,7 @@ public class ClientTest extends AbstractClientTest {
|
||||||
response.close();
|
response.close();
|
||||||
String id = ApiUtil.getCreatedId(response);
|
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);
|
realm.clients().get(id).roles().create(role);
|
||||||
|
|
||||||
rep = realm.clients().get(id).toRepresentation();
|
rep = realm.clients().get(id).toRepresentation();
|
||||||
|
|
|
@ -123,7 +123,7 @@ public class ImpersonationTest {
|
||||||
ClientSessionModel clientSession = session.sessions().createClientSession(adminRealm, adminConsole);
|
ClientSessionModel clientSession = session.sessions().createClientSession(adminRealm, adminConsole);
|
||||||
clientSession.setNote(OIDCLoginProtocol.ISSUER, "http://localhost:8081/auth/realms/" + realm);
|
clientSession.setNote(OIDCLoginProtocol.ISSUER, "http://localhost:8081/auth/realms/" + realm);
|
||||||
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false, null, null);
|
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);
|
return tm.encodeToken(adminRealm, token);
|
||||||
} finally {
|
} finally {
|
||||||
keycloakRule.stopSession(session, true);
|
keycloakRule.stopSession(session, true);
|
||||||
|
|
|
@ -112,7 +112,7 @@ public class RealmTest extends AbstractClientTest {
|
||||||
@Test
|
@Test
|
||||||
// KEYCLOAK-1110
|
// KEYCLOAK-1110
|
||||||
public void deleteDefaultRole() {
|
public void deleteDefaultRole() {
|
||||||
RoleRepresentation role = new RoleRepresentation("test", "test");
|
RoleRepresentation role = new RoleRepresentation("test", "test", false);
|
||||||
realm.roles().create(role);
|
realm.roles().create(role);
|
||||||
|
|
||||||
assertNotNull(realm.roles().get("test").toRepresentation());
|
assertNotNull(realm.roles().get("test").toRepresentation());
|
||||||
|
|
|
@ -117,6 +117,7 @@ public class AbstractModelTest {
|
||||||
Assert.assertEquals(expected.getId(), actual.getId());
|
Assert.assertEquals(expected.getId(), actual.getId());
|
||||||
Assert.assertEquals(expected.getName(), actual.getName());
|
Assert.assertEquals(expected.getName(), actual.getName());
|
||||||
Assert.assertEquals(expected.getDescription(), actual.getDescription());
|
Assert.assertEquals(expected.getDescription(), actual.getDescription());
|
||||||
|
Assert.assertEquals(expected.isScopeParamRequired(), actual.isScopeParamRequired());
|
||||||
Assert.assertEquals(expected.getContainer(), actual.getContainer());
|
Assert.assertEquals(expected.getContainer(), actual.getContainer());
|
||||||
Assert.assertEquals(expected.getComposites().size(), actual.getComposites().size());
|
Assert.assertEquals(expected.getComposites().size(), actual.getComposites().size());
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,8 +68,8 @@ public class AdapterTest extends AbstractModelTest {
|
||||||
Assert.assertEquals(realmModel.getName(), "JUGGLER");
|
Assert.assertEquals(realmModel.getName(), "JUGGLER");
|
||||||
Assert.assertArrayEquals(realmModel.getPrivateKey().getEncoded(), keyPair.getPrivate().getEncoded());
|
Assert.assertArrayEquals(realmModel.getPrivateKey().getEncoded(), keyPair.getPrivate().getEncoded());
|
||||||
Assert.assertArrayEquals(realmModel.getPublicKey().getEncoded(), keyPair.getPublic().getEncoded());
|
Assert.assertArrayEquals(realmModel.getPublicKey().getEncoded(), keyPair.getPublic().getEncoded());
|
||||||
Assert.assertEquals(1, realmModel.getDefaultRoles().size());
|
Assert.assertEquals(2, realmModel.getDefaultRoles().size());
|
||||||
Assert.assertEquals("foo", realmModel.getDefaultRoles().get(0));
|
Assert.assertTrue(realmModel.getDefaultRoles().contains("foo"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -94,8 +94,8 @@ public class AdapterTest extends AbstractModelTest {
|
||||||
Assert.assertEquals(realmModel.getName(), "JUGGLER");
|
Assert.assertEquals(realmModel.getName(), "JUGGLER");
|
||||||
Assert.assertArrayEquals(realmModel.getPrivateKey().getEncoded(), keyPair.getPrivate().getEncoded());
|
Assert.assertArrayEquals(realmModel.getPrivateKey().getEncoded(), keyPair.getPrivate().getEncoded());
|
||||||
Assert.assertArrayEquals(realmModel.getPublicKey().getEncoded(), keyPair.getPublic().getEncoded());
|
Assert.assertArrayEquals(realmModel.getPublicKey().getEncoded(), keyPair.getPublic().getEncoded());
|
||||||
Assert.assertEquals(1, realmModel.getDefaultRoles().size());
|
Assert.assertEquals(2, realmModel.getDefaultRoles().size());
|
||||||
Assert.assertEquals("foo", realmModel.getDefaultRoles().get(0));
|
Assert.assertTrue(realmModel.getDefaultRoles().contains("foo"));
|
||||||
|
|
||||||
realmModel.getId();
|
realmModel.getId();
|
||||||
|
|
||||||
|
@ -444,7 +444,7 @@ public class AdapterTest extends AbstractModelTest {
|
||||||
realmModel.addRole("admin");
|
realmModel.addRole("admin");
|
||||||
realmModel.addRole("user");
|
realmModel.addRole("user");
|
||||||
Set<RoleModel> roles = realmModel.getRoles();
|
Set<RoleModel> roles = realmModel.getRoles();
|
||||||
Assert.assertEquals(3, roles.size());
|
Assert.assertEquals(4, roles.size());
|
||||||
UserModel user = realmManager.getSession().users().addUser(realmModel, "bburke");
|
UserModel user = realmManager.getSession().users().addUser(realmModel, "bburke");
|
||||||
RoleModel realmUserRole = realmModel.getRole("user");
|
RoleModel realmUserRole = realmModel.getRole("user");
|
||||||
user.grantRole(realmUserRole);
|
user.grantRole(realmUserRole);
|
||||||
|
@ -470,7 +470,7 @@ public class AdapterTest extends AbstractModelTest {
|
||||||
user.grantRole(application.getRole("user"));
|
user.grantRole(application.getRole("user"));
|
||||||
|
|
||||||
roles = user.getRealmRoleMappings();
|
roles = user.getRealmRoleMappings();
|
||||||
Assert.assertEquals(roles.size(), 2);
|
Assert.assertEquals(roles.size(), 3);
|
||||||
assertRolesContains(realmUserRole, roles);
|
assertRolesContains(realmUserRole, roles);
|
||||||
Assert.assertTrue(user.hasRole(realmUserRole));
|
Assert.assertTrue(user.hasRole(realmUserRole));
|
||||||
// Role "foo" is default realm role
|
// 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'
|
// Test that application role 'user' don't clash with realm role 'user'
|
||||||
Assert.assertNotEquals(realmModel.getRole("user").getId(), application.getRole("user").getId());
|
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
|
// Revoke some roles
|
||||||
user.deleteRoleMapping(realmModel.getRole("foo"));
|
user.deleteRoleMapping(realmModel.getRole("foo"));
|
||||||
user.deleteRoleMapping(appBarRole);
|
user.deleteRoleMapping(appBarRole);
|
||||||
roles = user.getRoleMappings();
|
roles = user.getRoleMappings();
|
||||||
Assert.assertEquals(4, roles.size());
|
Assert.assertEquals(5, roles.size());
|
||||||
assertRolesContains(realmUserRole, roles);
|
assertRolesContains(realmUserRole, roles);
|
||||||
assertRolesContains(application.getRole("user"), roles);
|
assertRolesContains(application.getRole("user"), roles);
|
||||||
Assert.assertFalse(user.hasRole(appBarRole));
|
Assert.assertFalse(user.hasRole(appBarRole));
|
||||||
|
|
|
@ -79,7 +79,7 @@ public class ImportTest extends AbstractModelTest {
|
||||||
Assert.assertEquals(1, creds.size());
|
Assert.assertEquals(1, creds.size());
|
||||||
RequiredCredentialModel cred = creds.get(0);
|
RequiredCredentialModel cred = creds.get(0);
|
||||||
Assert.assertEquals("password", cred.getFormLabel());
|
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("foo"));
|
||||||
Assert.assertNotNull(realm.getRole("bar"));
|
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(application.getRole("app-admin")));
|
||||||
Assert.assertTrue(allRoles.contains(otherApp.getRole("otherapp-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);
|
UserModel wburke = session.users().getUserByUsername("wburke", realm);
|
||||||
// user with creation timestamp in import
|
// user with creation timestamp in import
|
||||||
Assert.assertEquals(new Long(123654), wburke.getCreatedTimestamp());
|
Assert.assertEquals(new Long(123654), wburke.getCreatedTimestamp());
|
||||||
|
@ -326,8 +330,6 @@ public class ImportTest extends AbstractModelTest {
|
||||||
// Test service accounts
|
// Test service accounts
|
||||||
Assert.assertFalse(application.isServiceAccountsEnabled());
|
Assert.assertFalse(application.isServiceAccountsEnabled());
|
||||||
Assert.assertTrue(otherApp.isServiceAccountsEnabled());
|
Assert.assertTrue(otherApp.isServiceAccountsEnabled());
|
||||||
Assert.assertFalse(application.isOfflineTokensEnabled());
|
|
||||||
Assert.assertTrue(otherApp.isOfflineTokensEnabled());
|
|
||||||
Assert.assertNull(session.users().getUserByServiceAccountClient(application));
|
Assert.assertNull(session.users().getUserByServiceAccountClient(application));
|
||||||
UserModel linked = session.users().getUserByServiceAccountClient(otherApp);
|
UserModel linked = session.users().getUserByServiceAccountClient(otherApp);
|
||||||
Assert.assertNotNull(linked);
|
Assert.assertNotNull(linked);
|
||||||
|
|
|
@ -34,6 +34,7 @@ import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.ProtocolMapperModel;
|
import org.keycloak.models.ProtocolMapperModel;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.UserConsentModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
import org.keycloak.protocol.oidc.OIDCLoginProtocol;
|
||||||
import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory;
|
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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.keycloak.events.Event;
|
||||||
import org.keycloak.events.EventType;
|
import org.keycloak.events.EventType;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
@ -37,6 +38,7 @@ import org.keycloak.testsuite.AssertEvents;
|
||||||
import org.keycloak.testsuite.OAuthClient;
|
import org.keycloak.testsuite.OAuthClient;
|
||||||
import org.keycloak.testsuite.pages.AccountApplicationsPage;
|
import org.keycloak.testsuite.pages.AccountApplicationsPage;
|
||||||
import org.keycloak.testsuite.pages.LoginPage;
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
|
import org.keycloak.testsuite.pages.OAuthGrantPage;
|
||||||
import org.keycloak.testsuite.rule.KeycloakRule;
|
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||||
import org.keycloak.testsuite.rule.WebResource;
|
import org.keycloak.testsuite.rule.WebResource;
|
||||||
import org.keycloak.testsuite.rule.WebRule;
|
import org.keycloak.testsuite.rule.WebRule;
|
||||||
|
@ -74,7 +76,6 @@ public class OfflineTokenTest {
|
||||||
RoleModel customerUserRole = appRealm.getClientByClientId("test-app").getRole("customer-user");
|
RoleModel customerUserRole = appRealm.getClientByClientId("test-app").getRole("customer-user");
|
||||||
serviceAccountUser.grantRole(customerUserRole);
|
serviceAccountUser.grantRole(customerUserRole);
|
||||||
|
|
||||||
app.setOfflineTokensEnabled(true);
|
|
||||||
userId = manager.getSession().users().getUserByUsername("test-user@localhost", appRealm).getId();
|
userId = manager.getSession().users().getUserByUsername("test-user@localhost", appRealm).getId();
|
||||||
|
|
||||||
URL url = getClass().getResource("/oidc/offline-client-keycloak.json");
|
URL url = getClass().getResource("/oidc/offline-client-keycloak.json");
|
||||||
|
@ -101,6 +102,9 @@ public class OfflineTokenTest {
|
||||||
@WebResource
|
@WebResource
|
||||||
protected LoginPage loginPage;
|
protected LoginPage loginPage;
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected OAuthGrantPage oauthGrantPage;
|
||||||
|
|
||||||
@WebResource
|
@WebResource
|
||||||
protected AccountApplicationsPage accountAppPage;
|
protected AccountApplicationsPage accountAppPage;
|
||||||
|
|
||||||
|
@ -115,23 +119,80 @@ public class OfflineTokenTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void offlineTokenDisabledForClient() throws Exception {
|
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.scope(OAuth2Constants.OFFLINE_ACCESS);
|
||||||
|
oauth.clientId("offline-client");
|
||||||
|
oauth.redirectUri(offlineClientAppUri);
|
||||||
oauth.doLogin("test-user@localhost", "password");
|
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 sessionId = loginEvent.getSessionId();
|
||||||
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
|
String codeId = loginEvent.getDetails().get(Details.CODE_ID);
|
||||||
|
|
||||||
String code = oauth.getCurrentQuery().get(OAuth2Constants.CODE);
|
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(400, tokenResponse.getStatusCode());
|
||||||
assertEquals("invalid_client", tokenResponse.getError());
|
assertEquals("not_allowed", tokenResponse.getError());
|
||||||
|
|
||||||
events.expectCodeToToken(codeId, sessionId)
|
events.expectCodeToToken(codeId, sessionId)
|
||||||
.error("invalid_client")
|
.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, "secret1");
|
||||||
|
|
||||||
|
assertEquals(400, tokenResponse.getStatusCode());
|
||||||
|
assertEquals("not_allowed", tokenResponse.getError());
|
||||||
|
|
||||||
|
events.expectCodeToToken(codeId, sessionId)
|
||||||
|
.client("offline-client")
|
||||||
|
.user(userId)
|
||||||
|
.error("not_allowed")
|
||||||
.clearDetails()
|
.clearDetails()
|
||||||
.assertEvent();
|
.assertEvent();
|
||||||
}
|
}
|
||||||
|
@ -206,8 +267,9 @@ public class OfflineTokenTest {
|
||||||
|
|
||||||
Assert.assertEquals(userId, refreshedToken.getSubject());
|
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("user"));
|
||||||
|
Assert.assertTrue(refreshedToken.getRealmAccess().isUserInRole(Constants.OFFLINE_ACCESS_ROLE));
|
||||||
|
|
||||||
Assert.assertEquals(1, refreshedToken.getResourceAccess("test-app").getRoles().size());
|
Assert.assertEquals(1, refreshedToken.getResourceAccess("test-app").getRoles().size());
|
||||||
Assert.assertTrue(refreshedToken.getResourceAccess("test-app").isUserInRole("customer-user"));
|
Assert.assertTrue(refreshedToken.getResourceAccess("test-app").isUserInRole("customer-user"));
|
||||||
|
@ -374,7 +436,7 @@ public class OfflineTokenTest {
|
||||||
accountAppPage.open();
|
accountAppPage.open();
|
||||||
List<String> additionalGrants = accountAppPage.getApplications().get("offline-client").getAdditionalGrants();
|
List<String> additionalGrants = accountAppPage.getApplications().get("offline-client").getAdditionalGrants();
|
||||||
Assert.assertEquals(additionalGrants.size(), 1);
|
Assert.assertEquals(additionalGrants.size(), 1);
|
||||||
Assert.assertEquals(additionalGrants.get(0), "Offline Access");
|
Assert.assertEquals(additionalGrants.get(0), "Offline Token");
|
||||||
accountAppPage.revokeGrant("offline-client");
|
accountAppPage.revokeGrant("offline-client");
|
||||||
Assert.assertEquals(accountAppPage.getApplications().get("offline-client").getAdditionalGrants().size(), 0);
|
Assert.assertEquals(accountAppPage.getApplications().get("offline-client").getAdditionalGrants().size(), 0);
|
||||||
|
|
||||||
|
@ -389,6 +451,55 @@ public class OfflineTokenTest {
|
||||||
Time.setOffset(0);
|
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 {
|
public static class OfflineTokenServlet extends HttpServlet {
|
||||||
|
|
||||||
private static TokenInfo tokenInfo;
|
private static TokenInfo tokenInfo;
|
||||||
|
|
|
@ -457,7 +457,7 @@ public class SamlBindingTest {
|
||||||
ClientSessionModel clientSession = session.sessions().createClientSession(adminRealm, adminConsole);
|
ClientSessionModel clientSession = session.sessions().createClientSession(adminRealm, adminConsole);
|
||||||
clientSession.setNote(OIDCLoginProtocol.ISSUER, "http://localhost:8081/auth/realms/master");
|
clientSession.setNote(OIDCLoginProtocol.ISSUER, "http://localhost:8081/auth/realms/master");
|
||||||
UserSessionModel userSession = session.sessions().createUserSession(adminRealm, admin, "admin", null, "form", false, null, null);
|
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);
|
return tm.encodeToken(adminRealm, token);
|
||||||
} finally {
|
} finally {
|
||||||
keycloakRule.stopSession(session, true);
|
keycloakRule.stopSession(session, true);
|
||||||
|
|
|
@ -164,7 +164,6 @@
|
||||||
"name": "Other Application",
|
"name": "Other Application",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"serviceAccountsEnabled": true,
|
"serviceAccountsEnabled": true,
|
||||||
"offlineTokensEnabled": true,
|
|
||||||
"clientAuthenticatorType": "client-jwt",
|
"clientAuthenticatorType": "client-jwt",
|
||||||
"protocolMappers" : [
|
"protocolMappers" : [
|
||||||
{
|
{
|
||||||
|
@ -199,7 +198,8 @@
|
||||||
"application" : {
|
"application" : {
|
||||||
"Application" : [
|
"Application" : [
|
||||||
{
|
{
|
||||||
"name": "app-admin"
|
"name": "app-admin",
|
||||||
|
"scopeParamRequired": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "app-user"
|
"name": "app-user"
|
||||||
|
@ -207,7 +207,8 @@
|
||||||
],
|
],
|
||||||
"OtherApp" : [
|
"OtherApp" : [
|
||||||
{
|
{
|
||||||
"name": "otherapp-admin"
|
"name": "otherapp-admin",
|
||||||
|
"scopeParamRequired": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "otherapp-user"
|
"name": "otherapp-user"
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
{ "type" : "password",
|
{ "type" : "password",
|
||||||
"value" : "password" }
|
"value" : "password" }
|
||||||
],
|
],
|
||||||
"realmRoles": ["user"],
|
"realmRoles": ["user", "offline_access"],
|
||||||
"clientRoles": {
|
"clientRoles": {
|
||||||
"test-app": [ "customer-user" ],
|
"test-app": [ "customer-user" ],
|
||||||
"account": [ "view-profile", "manage-account" ]
|
"account": [ "view-profile", "manage-account" ]
|
||||||
|
|
Loading…
Reference in a new issue