Merge pull request #1185 from mposolda/master

KEYCLOAK-1070 and other fixes
This commit is contained in:
Marek Posolda 2015-04-28 10:16:56 +02:00
commit 8224f3ba48
31 changed files with 382 additions and 142 deletions

View file

@ -40,7 +40,8 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro
"org.keycloak.models.entities.UserFederationProviderEntity",
"org.keycloak.models.entities.ProtocolMapperEntity",
"org.keycloak.models.entities.IdentityProviderMapperEntity",
"org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity"
"org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity",
"org.keycloak.models.mongo.keycloak.entities.MongoMigrationModelEntity"
};
private static final Logger logger = Logger.getLogger(DefaultMongoConnectionFactoryProvider.class);

View file

@ -1,28 +1,52 @@
package org.keycloak.representations.idm;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class UserConsentRepresentation {
protected List<String> grantedRoles; // points to roleIds
protected List<String> grantedProtocolMappers; // points to protocolMapperIds
protected String clientId;
public List<String> getGrantedRoles() {
return grantedRoles;
// Key is protocol, Value is list of granted consents for this protocol
protected Map<String, List<String>> grantedProtocolMappers;
protected List<String> grantedRealmRoles;
// Key is clientId, Value is list of granted roles of this client
protected Map<String, List<String>> grantedClientRoles;
public String getClientId() {
return clientId;
}
public void setGrantedRoles(List<String> grantedRoles) {
this.grantedRoles = grantedRoles;
public void setClientId(String clientId) {
this.clientId = clientId;
}
public List<String> getGrantedProtocolMappers() {
public Map<String, List<String>> getGrantedProtocolMappers() {
return grantedProtocolMappers;
}
public void setGrantedProtocolMappers(List<String> grantedProtocolMappers) {
public void setGrantedProtocolMappers(Map<String, List<String>> grantedProtocolMappers) {
this.grantedProtocolMappers = grantedProtocolMappers;
}
public List<String> getGrantedRealmRoles() {
return grantedRealmRoles;
}
public void setGrantedRealmRoles(List<String> grantedRealmRoles) {
this.grantedRealmRoles = grantedRealmRoles;
}
public Map<String, List<String>> getGrantedClientRoles() {
return grantedClientRoles;
}
public void setGrantedClientRoles(Map<String, List<String>> grantedClientRoles) {
this.grantedClientRoles = grantedClientRoles;
}
}

View file

@ -27,7 +27,7 @@ public class UserRepresentation {
protected List<FederatedIdentityRepresentation> federatedIdentities;
protected List<String> realmRoles;
protected Map<String, List<String>> clientRoles;
protected Map<String, UserConsentRepresentation> clientConsents;
protected List<UserConsentRepresentation> clientConsents;
@Deprecated
protected Map<String, List<String>> applicationRoles;
@ -177,11 +177,11 @@ public class UserRepresentation {
this.clientRoles = clientRoles;
}
public Map<String, UserConsentRepresentation> getClientConsents() {
public List<UserConsentRepresentation> getClientConsents() {
return clientConsents;
}
public void setClientConsents(Map<String, UserConsentRepresentation> clientConsents) {
public void setClientConsents(List<UserConsentRepresentation> clientConsents) {
this.clientConsents = clientConsents;
}

View file

@ -5,6 +5,7 @@ import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import org.keycloak.events.Event;
import org.keycloak.events.EventQuery;
import org.keycloak.events.EventType;
@ -66,7 +67,9 @@ public class MongoEventQuery implements EventQuery {
} catch (ParseException e) {
e.printStackTrace();
}
query.put("time", BasicDBObjectBuilder.start("$gte", from).get());
BasicDBObject time = query.containsField("time") ? (BasicDBObject) query.get("time") : new BasicDBObject();
time.append("$gte", from);
query.put("time", time);
return this;
}
@ -79,7 +82,9 @@ public class MongoEventQuery implements EventQuery {
} catch (ParseException e) {
e.printStackTrace();
}
query.put("time", BasicDBObjectBuilder.start("$lte", to).get());
BasicDBObject time = query.containsField("time") ? (BasicDBObject) query.get("time") : new BasicDBObject();
time.append("$lte", to);
query.put("time", time);
return this;
}

View file

@ -1,10 +1,15 @@
package org.keycloak.example.oauth;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.KeycloakSecurityContext;
import org.keycloak.representations.AccessToken;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import java.util.ArrayList;
import java.util.List;
@ -14,10 +19,19 @@ import java.util.List;
*/
@Path("customers")
public class CustomerService {
@Context
private HttpRequest httpRequest;
@GET
@Produces("application/json")
@NoCache
public List<String> getCustomers() {
// Just to show how to user info from access token in REST endpoint
KeycloakSecurityContext securityContext = (KeycloakSecurityContext) httpRequest.getAttribute(KeycloakSecurityContext.class.getName());
AccessToken accessToken = securityContext.getToken();
System.out.println(String.format("User '%s' with email '%s' made request to CustomerService REST endpoint", accessToken.getPreferredUsername(), accessToken.getEmail()));
ArrayList<String> rtn = new ArrayList<String>();
rtn.add("Bill Burke");
rtn.add("Stian Thorgersen");

View file

@ -287,29 +287,11 @@ public class ExportUtils {
// Grants
List<UserConsentModel> consents = user.getConsents();
Map<String, UserConsentRepresentation> consentReps = new HashMap<String, UserConsentRepresentation>();
LinkedList<UserConsentRepresentation> consentReps = new LinkedList<UserConsentRepresentation>();
for (UserConsentModel consent : consents) {
String clientId = consent.getClient().getClientId();
List<String> grantedProtocolMappers = new LinkedList<String>();
for (ProtocolMapperModel protocolMapper : consent.getGrantedProtocolMappers()) {
grantedProtocolMappers.add(protocolMapper.getId());
}
List<String> grantedRoles = new LinkedList<String>();
for (RoleModel role : consent.getGrantedRoles()) {
grantedRoles.add(role.getId());
}
if (grantedRoles.size() > 0 || grantedProtocolMappers.size() > 0) {
UserConsentRepresentation consentRep = new UserConsentRepresentation();
if (grantedRoles.size() > 0) consentRep.setGrantedRoles(grantedRoles);
if (grantedProtocolMappers.size() > 0) consentRep.setGrantedProtocolMappers(grantedProtocolMappers);
consentReps.put(clientId, consentRep);
}
UserConsentRepresentation consentRep = ModelToRepresentation.toRepresentation(consent);
consentReps.add(consentRep);
}
if (consentReps.size() > 0) {
userRep.setClientConsents(consentReps);
}

View file

@ -9,6 +9,7 @@
<form action="${url.revokeClientUrl}" method="post">
<input type="hidden" id="stateChecker" name="stateChecker" value="${stateChecker}">
<input type="hidden" id="referrer" name="referrer" value="${stateChecker}">
<table class="table table-striped table-bordered">
<thead>

View file

@ -51,9 +51,11 @@ role_manage-clients=Manage clients
role_manage-events=Manage events
role_view-profile=View profile
role_manage-account=Manage account
role_read-token=Read token
client_account=Account
client_security-admin-console=Security Admin Console
client_realm-management=Realm Management
client_broker=Broker
requiredFields=Required fields

View file

@ -399,6 +399,21 @@ module.config([ '$routeProvider', function($routeProvider) {
},
controller : 'UserFederatedIdentityCtrl'
})
.when('/realms/:realm/users/:user/consents', {
templateUrl : resourceUrl + '/partials/user-consents.html',
resolve : {
realm : function(RealmLoader) {
return RealmLoader();
},
user : function(UserLoader) {
return UserLoader();
},
userConsents : function(UserConsentsLoader) {
return UserConsentsLoader();
}
},
controller : 'UserConsentsCtrl'
})
.when('/realms/:realm/users', {
templateUrl : resourceUrl + '/partials/user-list.html',
resolve : {
@ -1418,6 +1433,15 @@ module.directive('kcNavigationClient', function () {
}
});
module.directive('kcNavigationUser', function () {
return {
scope: true,
restrict: 'E',
replace: true,
templateUrl: resourceUrl + '/templates/kc-navigation-user.html'
}
});
/*
* Used to select the element (invoke $(elem).select()) on specified action list.
* Usages kc-select-action="click mouseover"

View file

@ -135,6 +135,24 @@ module.controller('UserFederatedIdentityCtrl', function($scope, realm, user, fed
$scope.federatedIdentities = federatedIdentities;
});
module.controller('UserConsentsCtrl', function($scope, realm, user, userConsents, UserConsents, Notifications) {
$scope.realm = realm;
$scope.user = user;
$scope.userConsents = userConsents;
$scope.revokeConsent = function(clientId) {
UserConsents.delete({realm : realm.realm, user: user.username, client: clientId }, function () {
UserConsents.query({realm: realm.realm, user: user.username}, function(updated) {
$scope.userConsents = updated;
})
Notifications.success('Consent revoked successfully');
}, function() {
Notifications.error("Consent couldn't be revoked");
});
console.log("Revoke consent " + clientId);
}
});
module.controller('UserListCtrl', function($scope, realm, User) {
$scope.realm = realm;

View file

@ -144,6 +144,14 @@ module.factory('UserFederatedIdentityLoader', function(Loader, UserFederatedIden
});
});
module.factory('UserConsentsLoader', function(Loader, UserConsents, $route, $q) {
return Loader.query(UserConsents, function() {
return {
realm : $route.current.params.realm,
user : $route.current.params.user
}
});
});

View file

@ -268,6 +268,13 @@ module.factory('UserFederatedIdentity', function($resource) {
user : '@user'
});
});
module.factory('UserConsents', function($resource) {
return $resource(authUrl + '/admin/realms/:realm/users/:user/consents/:client', {
realm : '@realm',
user : '@user',
client: '@client'
});
});
module.factory('UserCredentials', function($resource) {
var credentials = {};

View file

@ -1,12 +1,6 @@
<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="resourceUrl + '/partials/realm-menu.html'"></div>
<div id="content-area" class="col-md-9" role="main">
<ul class="nav nav-tabs nav-tabs-pf" data-ng-show="!create">
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}">Attributes</a></li>
<li data-ng-show="access.manageUsers"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/user-credentials">Credentials</a></li>
<li class="active"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/role-mappings">Role Mappings</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/sessions">Sessions</a></li>
<li data-ng-show="realm.identityFederationEnabled"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/social-links">Federated Identities</a></li>
</ul>
<kc-navigation-user></kc-navigation-user>
<div id="content">
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/users">Users</a></li>

View file

@ -0,0 +1,47 @@
<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="resourceUrl + '/partials/realm-menu.html'"></div>
<div id="content-area" class="col-md-9" role="main">
<kc-navigation-user></kc-navigation-user>
<div id="content">
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/users">Users</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}">{{user.username}}</a></li>
<li class="active">Consents</li>
</ol>
<h2>User <span>{{user.username}}</span> Consents <span tooltip-placement="right" tooltip="This page shows you all the consents, which user granted permissions" class="fa fa-info-circle"></span></h2>
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>Client</th>
<th>Granted Roles</th>
<th>Granted Protocol Mappers</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="consent in userConsents">
<td>{{consent.clientId}}</td>
<td>
<span data-ng-repeat="realmRole in consent.grantedRealmRoles">
<span ng-if="!$first">, </span>{{realmRole}}
</span>
<span data-ng-repeat="(clientId, clientRoles) in consent.grantedClientRoles">
<span data-ng-repeat="clientRole in clientRoles">
<span ng-if="!$first || consent.grantedRealmRoles.length > 0">, </span>{{clientRole}} in {{clientId}}
</span>
</span>
</td>
<td>
<span data-ng-repeat="protocol in consent.grantedProtocolMappers">
<span data-ng-repeat="protocolMapper in protocol">
<span ng-if="!$first">, </span>{{protocolMapper}}
</span>
</span>
</td>
<td>
<button class="btn btn-danger" ng-click="revokeConsent(consent.clientId)">Revoke consent</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>

View file

@ -1,13 +1,6 @@
<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="resourceUrl + '/partials/realm-menu.html'"></div>
<div id="content-area" class="col-md-9" role="main">
<ul class="nav nav-tabs nav-tabs-pf" data-ng-show="!create">
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}">Attributes</a></li>
<li class="active"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/user-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/role-mappings">Role Mappings</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/sessions">Sessions</a></li>
<li data-ng-show="realm.identityFederationEnabled"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/federated-identity">Federated Identities</a></li>
</ul>
<kc-navigation-user></kc-navigation-user>
<div id="content">
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/users">Users</a></li>

View file

@ -1,13 +1,6 @@
<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="resourceUrl + '/partials/realm-menu.html'"></div>
<div id="content-area" class="col-md-9" role="main">
<ul class="nav nav-tabs nav-tabs-pf" data-ng-show="!create">
<li class="active"><a href="#/realms/{{realm.realm}}/users/{{user.username}}">Attributes</a></li>
<li data-ng-show="access.manageUsers"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/user-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/role-mappings">Role Mappings</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/sessions">Sessions</a></li>
<li data-ng-show="realm.identityFederationEnabled"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/federated-identity">Federated Identities</a></li>
</ul>
<kc-navigation-user></kc-navigation-user>
<ul class="nav nav-tabs nav-tabs-pf" data-ng-show="create">
<li class="active"><a href="">User List</a></li>
<li><a href="#/realms/{{realm.realm}}/user-federation">Federation</a></li>

View file

@ -1,12 +1,6 @@
<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="resourceUrl + '/partials/realm-menu.html'"></div>
<div id="content-area" class="col-md-9" role="main">
<ul class="nav nav-tabs nav-tabs-pf">
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}">Attributes</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/user-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/role-mappings">Role Mappings</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/sessions">Sessions</a></li>
<li class="active"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/federated-identity">Federated Identities</a></li>
</ul>
<kc-navigation-user></kc-navigation-user>
<div id="content">
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/users">Users</a></li>

View file

@ -1,12 +1,6 @@
<div class="bs-sidebar col-md-3 clearfix" data-ng-include data-src="resourceUrl + '/partials/realm-menu.html'"></div>
<div id="content-area" class="col-md-9" role="main">
<ul class="nav nav-tabs nav-tabs-pf">
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}">Attributes</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/user-credentials">Credentials</a></li>
<li><a href="#/realms/{{realm.realm}}/users/{{user.username}}/role-mappings">Role Mappings</a></li>
<li class="active"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/sessions">Sessions</a></li>
<li data-ng-show="realm.identityFederationEnabled"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/federated-identity">Federated Identities</a></li>
</ul>
<kc-navigation-user></kc-navigation-user>
<div id="content">
<ol class="breadcrumb">
<li><a href="#/realms/{{realm.realm}}/users">Users</a></li>

View file

@ -0,0 +1,8 @@
<ul class="nav nav-tabs nav-tabs-pf" data-ng-hide="create">
<li ng-class="{active: !path[4]}"><a href="#/realms/{{realm.realm}}/users/{{user.username}}">Attributes</a></li>
<li ng-class="{active: path[4] == 'user-credentials'}"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/user-credentials">Credentials</a></li>
<li ng-class="{active: path[4] == 'role-mappings'}"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/role-mappings">Role Mappings</a></li>
<li ng-class="{active: path[4] == 'sessions'}"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/sessions">Sessions</a></li>
<li ng-class="{active: path[4] == 'federated-identity'}" data-ng-show="realm.identityFederationEnabled"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/federated-identity">Federated Identities</a></li>
<li ng-class="{active: path[4] == 'consents'}"><a href="#/realms/{{realm.realm}}/users/{{user.username}}/consents">Consents</a></li>
</ul>

View file

@ -84,9 +84,11 @@ role_manage-clients=Manage clients
role_manage-events=Manage events
role_view-profile=View profile
role_manage-account=Manage account
role_read-token=Read token
client_account=Account
client_security-admin-console=Security Admin Console
client_realm-management=Realm Management
client_broker=Broker
invalidUserMessage=Invalid username or password.
invalidEmailMessage=Invalid email address.

View file

@ -8,6 +8,7 @@ import org.keycloak.models.RealmModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.List;
import java.util.Map;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -21,17 +22,33 @@ public class MigrationTo1_2_0_RC1 {
if (client == null) {
client = KeycloakModelUtils.createClient(realm, Constants.BROKER_SERVICE_CLIENT_ID);
client.setEnabled(true);
client.setName("${client_" + Constants.BROKER_SERVICE_CLIENT_ID + "}");
client.setFullScopeAllowed(false);
for (String role : Constants.BROKER_SERVICE_ROLES) {
client.addRole(role).setDescription("${role_"+role+"}");
client.addRole(role).setDescription("${role_"+ role.toLowerCase().replaceAll("_", "-") +"}");
}
}
}
private void setupClientNames(RealmModel realm) {
Map<String, ClientModel> clients = realm.getClientNameMap();
setupClientName(clients, Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
setupClientName(clients, Constants.ADMIN_CONSOLE_CLIENT_ID);
setupClientName(clients, Constants.REALM_MANAGEMENT_CLIENT_ID);
}
private void setupClientName(Map<String, ClientModel> clients, String clientId) {
ClientModel client = clients.get(clientId);
if (client != null && client.getName() == null) client.setName("${client_" + clientId + "}");
}
public void migrate(KeycloakSession session) {
List<RealmModel> realms = session.realms().getRealms();
for (RealmModel realm : realms) {
setupBrokerService(realm);
setupClientNames(realm);
}
}

View file

@ -9,6 +9,7 @@ public interface Constants {
String ACCOUNT_MANAGEMENT_CLIENT_ID = "account";
String BROKER_SERVICE_CLIENT_ID = "broker";
String REALM_MANAGEMENT_CLIENT_ID = "realm-management";
String INSTALLED_APP_URN = "urn:ietf:wg:oauth:2.0:oob";
String INSTALLED_APP_URL = "http://localhost";

View file

@ -9,6 +9,7 @@ import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserModel;
@ -22,6 +23,7 @@ import org.keycloak.representations.idm.ProtocolMapperRepresentation;
import org.keycloak.representations.idm.RealmEventsConfigRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserConsentRepresentation;
import org.keycloak.representations.idm.UserFederationProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.UserSessionRepresentation;
@ -325,4 +327,45 @@ public class ModelToRepresentation {
return rep;
}
public static UserConsentRepresentation toRepresentation(UserConsentModel model) {
String clientId = model.getClient().getClientId();
Map<String, List<String>> grantedProtocolMappers = new HashMap<String, List<String>>();
for (ProtocolMapperModel protocolMapper : model.getGrantedProtocolMappers()) {
String protocol = protocolMapper.getProtocol();
List<String> currentProtocolMappers = grantedProtocolMappers.get(protocol);
if (currentProtocolMappers == null) {
currentProtocolMappers = new LinkedList<String>();
grantedProtocolMappers.put(protocol, currentProtocolMappers);
}
currentProtocolMappers.add(protocolMapper.getName());
}
List<String> grantedRealmRoles = new LinkedList<String>();
Map<String, List<String>> grantedClientRoles = new HashMap<String, List<String>>();
for (RoleModel role : model.getGrantedRoles()) {
if (role.getContainer() instanceof RealmModel) {
grantedRealmRoles.add(role.getName());
} else {
ClientModel client2 = (ClientModel) role.getContainer();
String clientId2 = client2.getClientId();
List<String> currentClientRoles = grantedClientRoles.get(clientId2);
if (currentClientRoles == null) {
currentClientRoles = new LinkedList<String>();
grantedClientRoles.put(clientId2, currentClientRoles);
}
currentClientRoles.add(role.getName());
}
}
UserConsentRepresentation consentRep = new UserConsentRepresentation();
consentRep.setClientId(clientId);
consentRep.setGrantedProtocolMappers(grantedProtocolMappers);
consentRep.setGrantedRealmRoles(grantedRealmRoles);
consentRep.setGrantedClientRoles(grantedClientRoles);
return consentRep;
}
}

View file

@ -788,33 +788,8 @@ public class RepresentationToModel {
}
}
if (userRep.getClientConsents() != null) {
for (Map.Entry<String, UserConsentRepresentation> entry : userRep.getClientConsents().entrySet()) {
ClientModel client = clientMap.get(entry.getKey());
if (client == null) {
throw new RuntimeException("Unable to find client consent mappings for client: " + entry.getKey());
}
UserConsentModel consentModel = new UserConsentModel(client);
UserConsentRepresentation consentRep = entry.getValue();
if (consentRep.getGrantedRoles() != null) {
for (String roleId : consentRep.getGrantedRoles()) {
RoleModel role = newRealm.getRoleById(roleId);
if (role == null) {
throw new RuntimeException("Unable to find realm role referenced in consent mappings of user " + user.getUsername() + ". Role ID: " + roleId);
}
consentModel.addGrantedRole(role);
}
}
if (consentRep.getGrantedProtocolMappers() != null) {
for (String mapperId : consentRep.getGrantedProtocolMappers()) {
ProtocolMapperModel protocolMapper = client.getProtocolMapperById(mapperId);
if (protocolMapper == null) {
throw new RuntimeException("Unable to find protocol mapper referenced in consent mappings of user " + user.getUsername() + ". Protocol mapper ID: " + mapperId);
}
consentModel.addGrantedProtocolMapper(protocolMapper);
}
}
for (UserConsentRepresentation consentRep : userRep.getClientConsents()) {
UserConsentModel consentModel = toModel(newRealm, consentRep);
user.addConsent(consentModel);
}
}
@ -917,4 +892,53 @@ public class RepresentationToModel {
return model;
}
public static UserConsentModel toModel(RealmModel newRealm, UserConsentRepresentation consentRep) {
ClientModel client = newRealm.getClientByClientId(consentRep.getClientId());
if (client == null) {
throw new RuntimeException("Unable to find client consent mappings for client: " + consentRep.getClientId());
}
UserConsentModel consentModel = new UserConsentModel(client);
if (consentRep.getGrantedRealmRoles() != null) {
for (String roleName : consentRep.getGrantedRealmRoles()) {
RoleModel role = newRealm.getRole(roleName);
if (role == null) {
throw new RuntimeException("Unable to find realm role referenced in consent mappings of user. Role name: " + roleName);
}
consentModel.addGrantedRole(role);
}
}
if (consentRep.getGrantedClientRoles() != null) {
for (Map.Entry<String, List<String>> entry : consentRep.getGrantedClientRoles().entrySet()) {
String clientId2 = entry.getKey();
ClientModel client2 = newRealm.getClientByClientId(clientId2);
if (client2 == null) {
throw new RuntimeException("Unable to find client referenced in consent mappings. Client ID: " + clientId2);
}
for (String clientRoleName : entry.getValue()) {
RoleModel clientRole = client2.getRole(clientRoleName);
if (clientRole == null) {
throw new RuntimeException("Unable to find client role referenced in consent mappings of user. Role name: " + clientRole + ", Client: " + clientId2);
}
consentModel.addGrantedRole(clientRole);
}
}
}
if (consentRep.getGrantedProtocolMappers() != null) {
for (Map.Entry<String, List<String>> protocolEntry : consentRep.getGrantedProtocolMappers().entrySet()) {
String protocol = protocolEntry.getKey();
for (String protocolMapperName : protocolEntry.getValue()) {
ProtocolMapperModel protocolMapper = client.getProtocolMapperByName(protocol, protocolMapperName);
if (protocolMapper == null) {
throw new RuntimeException("Unable to find protocol mapper for protocol " + protocol + ", mapper name " + protocolMapperName);
}
consentModel.addGrantedProtocolMapper(protocolMapper);
}
}
}
return consentModel;
}
}

View file

@ -1,5 +1,6 @@
package org.keycloak.models.mongo.keycloak.entities;
import org.keycloak.connections.mongo.api.MongoCollection;
import org.keycloak.connections.mongo.api.MongoIdentifiableEntity;
import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
@ -7,6 +8,7 @@ import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@MongoCollection(collectionName = "migrationModel")
public class MongoMigrationModelEntity implements MongoIdentifiableEntity {
public static final String MIGRATION_MODEL_ID = "VERSION";
private String id = MIGRATION_MODEL_ID;

View file

@ -32,34 +32,7 @@ public class UserSessionNoteMapper extends AbstractOIDCProtocolMapper implements
property.setHelpText(ProtocolMapperUtils.USER_SESSION_MODEL_NOTE_HELP_TEXT);
property.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(property);
property = new ProviderConfigProperty();
property.setName(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME);
property.setLabel(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME_LABEL);
property.setType(ProviderConfigProperty.STRING_TYPE);
property.setHelpText("Name of the claim to insert into the token. This can be a fully qualified name like 'address.street'. In this case, a nested json object will be created.");
configProperties.add(property);
property = new ProviderConfigProperty();
property.setName(OIDCAttributeMapperHelper.JSON_TYPE);
property.setLabel(OIDCAttributeMapperHelper.JSON_TYPE);
property.setType(ProviderConfigProperty.STRING_TYPE);
property.setDefaultValue(ProviderConfigProperty.STRING_TYPE);
property.setHelpText("JSON type that should be used to populate the json claim in the token. long, int, boolean, and String are valid values.");
configProperties.add(property);
property = new ProviderConfigProperty();
property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN);
property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_LABEL);
property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
property.setDefaultValue("true");
property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN_HELP_TEXT);
configProperties.add(property);
property = new ProviderConfigProperty();
property.setName(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN);
property.setLabel(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_LABEL);
property.setType(ProviderConfigProperty.BOOLEAN_TYPE);
property.setDefaultValue("true");
property.setHelpText(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN_HELP_TEXT);
configProperties.add(property);
OIDCAttributeMapperHelper.addAttributeConfig(configProperties);
}
public static final String PROVIDER_ID = "oidc-usersessionmodel-note-mapper";

View file

@ -113,11 +113,11 @@ public class RealmManager {
}
public String getRealmAdminClientId(RealmModel realm) {
return "realm-management";
return Constants.REALM_MANAGEMENT_CLIENT_ID;
}
public String getRealmAdminClientId(RealmRepresentation realm) {
return "realm-management";
return Constants.REALM_MANAGEMENT_CLIENT_ID;
}
@ -223,10 +223,11 @@ public class RealmManager {
if (client == null) {
client = new ClientManager(this).createClient(realm, Constants.BROKER_SERVICE_CLIENT_ID);
client.setEnabled(true);
client.setName("${client_" + Constants.BROKER_SERVICE_CLIENT_ID + "}");
client.setFullScopeAllowed(false);
for (String role : Constants.BROKER_SERVICE_ROLES) {
client.addRole(role).setDescription("${role_"+role+"}");
client.addRole(role).setDescription("${role_"+ role.toLowerCase().replaceAll("_", "-") +"}");
}
}
}

View file

@ -531,7 +531,14 @@ public class AccountService {
event.event(EventType.REVOKE_GRANT).client(auth.getClient()).user(auth.getUser()).detail(Details.REVOKED_CLIENT, client.getClientId()).success();
setReferrerOnPage();
return account.setSuccess(Messages.SUCCESS_GRANT_REVOKED).createResponse(AccountPages.APPLICATIONS);
UriBuilder builder = Urls.accountBase(uriInfo.getBaseUri()).path(AccountService.class, "applicationsPage");
String referrer = uriInfo.getQueryParameters().getFirst("referrer");
if (referrer != null) {
builder.queryParam("referrer", referrer);
}
URI location = builder.build(realm.getName());
return Response.seeOther(location).build();
}
/**

View file

@ -17,6 +17,7 @@ import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelReadOnlyException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserConsentModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
@ -30,6 +31,7 @@ import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.MappingsRepresentation;
import org.keycloak.representations.idm.RoleRepresentation;
import org.keycloak.representations.idm.UserConsentRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.UserSessionRepresentation;
import org.keycloak.services.managers.AuthenticationManager;
@ -310,6 +312,56 @@ public class UsersResource {
}
}
/**
* List set of consents granted by this user.
*
* @param username
* @return
*/
@Path("{username}/consents")
@GET
@NoCache
@Produces(MediaType.APPLICATION_JSON)
public List<UserConsentRepresentation> getConsents(final @PathParam("username") String username) {
auth.requireView();
UserModel user = session.users().getUserByUsername(username, realm);
if (user == null) {
throw new NotFoundException("User not found");
}
List<UserConsentModel> consents = user.getConsents();
List<UserConsentRepresentation> result = new ArrayList<UserConsentRepresentation>();
for (UserConsentModel consent : consents) {
UserConsentRepresentation rep = ModelToRepresentation.toRepresentation(consent);
result.add(rep);
}
return result;
}
/**
* Revoke consent for particular client
*
* @param username
* @param clientId
*/
@Path("{username}/consents/{client}")
@DELETE
@NoCache
public void revokeConsent(final @PathParam("username") String username, final @PathParam("client") String clientId) {
auth.requireManage();
UserModel user = session.users().getUserByUsername(username, realm);
if (user == null) {
throw new NotFoundException("User not found");
}
ClientModel client = realm.getClientByClientId(clientId);
boolean revoked = user.revokeConsentForClient(client.getId());
if (!revoked) {
throw new NotFoundException("Consent not found for user " + username + " and client " + clientId);
}
}
/**
* Remove all user sessions associated with this user. And, for all client that have an admin URL, tell
* them to invalidate the sessions for this particular user.

View file

@ -5,6 +5,7 @@ import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.admin.client.resource.IdentityProviderResource;
import org.keycloak.admin.client.resource.UserResource;
import org.keycloak.representations.idm.ErrorRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.IdentityProviderRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
@ -43,6 +44,10 @@ public class UserTest extends AbstractClientTest {
fail("Expected failure");
} catch (ClientErrorException e) {
assertEquals(409, e.getResponse().getStatus());
// Just to show how to retrieve underlying error message
ErrorRepresentation error = e.getResponse().readEntity(ErrorRepresentation.class);
Assert.assertEquals("User exists with same username", error.getErrorMessage());
}
}

View file

@ -75,15 +75,22 @@
"Application": [ "app-admin" ],
"OtherApp": [ "otherapp-admin" ]
},
"clientConsents": {
"Application": {
"grantedRoles": [ "456", "789" ]
"clientConsents": [
{
"clientId": "Application",
"grantedRealmRoles": [ "admin" ],
"grantedClientRoles": {
"Application": [ "app-admin" ]
}
},
"OtherApp": {
"grantedProtocolMappers": [ "123" ],
"grantedRoles": [ "456" ]
{
"clientId": "OtherApp",
"grantedRealmRoles": [ "admin" ],
"grantedProtocolMappers": {
"openid-connect": [ "gss delegation credential" ]
}
}
}
]
},
{
"username": "mySocialUser",
@ -124,7 +131,6 @@
"enabled": true,
"protocolMappers" : [
{
"id": "123",
"name" : "gss delegation credential",
"protocol" : "openid-connect",
"protocolMapper" : "oidc-usersessionmodel-note-mapper",
@ -150,14 +156,12 @@
"roles" : {
"realm" : [
{
"id": "456",
"name": "admin"
}
],
"application" : {
"Application" : [
{
"id": "789",
"name": "app-admin"
},
{