Merge pull request #240 from stianst/admin-access

KEYCLOAK-292 Fine-grained admin control
This commit is contained in:
Stian Thorgersen 2014-02-25 12:59:31 +00:00
commit fa39ec1c98
35 changed files with 781 additions and 589 deletions

View file

@ -7,6 +7,17 @@ module.controller('GlobalCtrl', function($scope, $http, Auth, Current, $location
$http.get('/auth/rest/admin/whoami').success(function(data, status) {
Auth.user = data;
Auth.loggedIn = true;
Auth.hasAccess = function(realm, role) {
var realmAccess = Auth.user['realm_access'];
if (realmAccess) {
realmAccess = realmAccess[realm];
if (realmAccess) {
return realmAccess.indexOf(role) >= 0;
}
}
return false;
}
})
.error(function(data, status) {
Auth.loggedIn = false;
@ -62,7 +73,7 @@ module.controller('RealmDropdownCtrl', function($scope, Realm, Current, Auth, $l
}
});
module.controller('RealmCreateCtrl', function($scope, Current, Realm, $upload, $http, $location, Dialog, Notifications) {
module.controller('RealmCreateCtrl', function($scope, Current, Realm, $upload, $http, $location, Dialog, Notifications, Auth) {
console.log('RealmCreateCtrl');
Current.realm = null;
@ -131,8 +142,14 @@ module.controller('RealmCreateCtrl', function($scope, Current, Realm, $upload, $
Current.realm = Current.realms[i];
}
}
$location.url("/realms/" + realmCopy.realm);
Notifications.success("The realm has been created.");
$http.get('/auth/rest/admin/whoami').success(function(data, status) {
Auth.user = data;
console.log("reloaded auth");
$location.url("/realms/" + realmCopy.realm);
Notifications.success("The realm has been created.");
});
});
});
};

View file

@ -33,7 +33,7 @@
</li>
</ul>
</li>
<li class="active pull-right" data-ng-show="auth.loggedIn">
<li class="active pull-right" data-ng-show="auth.loggedIn && auth.user.admin">
<a class="button primary" href="#/create/realm" data-ng-class="path[0] == 'create' && path[1] == 'realm' && 'active'"
data-ng-show="auth.loggedIn">Add Realm</a>
</li>

View file

@ -7,98 +7,104 @@
<li><a href="#/realms/{{realm.realm}}">Settings</a></li>
<li class="active">General</li>
</ol>
<h2 class="pull-left" data-ng-show="createRealm">Add Realm</h2>
<h2 data-ng-hide="createRealm"><span>{{realm.realm}}</span> General Settings</h2>
<p class="subtitle" data-ng-show="createRealm"><span class="required">*</span> Required fields</p>
<form class="form-horizontal" name="realmForm" novalidate>
<fieldset>
<legend class="aj-collapse open"><span class="text">Required Settings</span></legend>
<div class="form-group">
<label class="col-sm-2 control-label" for="name">Name <span class="required" data-ng-show="createRealm">*</span></label>
<div data-ng-show="auth.hasAccess(realm.realm, 'manage-realm')">
<h2 class="pull-left" data-ng-show="createRealm">Add Realm</h2>
<h2 data-ng-hide="createRealm"><span>{{realm.realm}}</span> General Settings</h2>
<p class="subtitle" data-ng-show="createRealm"><span class="required">*</span> Required fields</p>
<form class="form-horizontal" name="realmForm" novalidate>
<fieldset>
<legend class="aj-collapse open"><span class="text">Required Settings</span></legend>
<div class="form-group">
<label class="col-sm-2 control-label" for="name">Name <span class="required" data-ng-show="createRealm">*</span></label>
<div class="col-sm-4">
<input class="form-control" type="text" id="name" name="name" data-ng-model="realm.realm" autofocus required>
<div class="col-sm-4">
<input class="form-control" type="text" id="name" name="name" data-ng-model="realm.realm" autofocus required>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="enabled">Enabled</label>
<div class="col-sm-4">
<input ng-model="realm.enabled" name="enabled" id="enabled" onoffswitch />
<div class="form-group">
<label class="col-sm-2 control-label" for="enabled">Enabled</label>
<div class="col-sm-4">
<input ng-model="realm.enabled" name="enabled" id="enabled" onoffswitch />
</div>
</div>
</div>
</fieldset>
<fieldset>
<legend><span class="text">Login Options</span></legend>
<div class="form-group">
<label class="col-sm-2 control-label" for="social">Social login</label>
<div class="col-sm-4">
<input ng-model="realm.social" name="social" id="social" onoffswitch />
</fieldset>
<fieldset>
<legend><span class="text">Login Options</span></legend>
<div class="form-group">
<label class="col-sm-2 control-label" for="social">Social login</label>
<div class="col-sm-4">
<input ng-model="realm.social" name="social" id="social" onoffswitch />
</div>
</div>
</div>
<div class="form-group" data-ng-show="realm.social">
<label class="col-sm-2 control-label" for="updateProfileOnInitialSocialLogin">Update profile on first social login</label>
<div class="col-sm-4">
<input ng-model="realm.updateProfileOnInitialSocialLogin" name="updateProfileOnInitialSocialLogin" id="updateProfileOnInitialSocialLogin" onoffswitch />
<div class="form-group" data-ng-show="realm.social">
<label class="col-sm-2 control-label" for="updateProfileOnInitialSocialLogin">Update profile on first social login</label>
<div class="col-sm-4">
<input ng-model="realm.updateProfileOnInitialSocialLogin" name="updateProfileOnInitialSocialLogin" id="updateProfileOnInitialSocialLogin" onoffswitch />
</div>
</div>
</div>
<div class="form-group">
<label for="registrationAllowed" class="col-sm-2 control-label">User registration</label>
<div class="col-sm-4">
<input ng-model="realm.registrationAllowed" name="registrationAllowed" id="registrationAllowed" onoffswitch />
<div class="form-group">
<label for="registrationAllowed" class="col-sm-2 control-label">User registration</label>
<div class="col-sm-4">
<input ng-model="realm.registrationAllowed" name="registrationAllowed" id="registrationAllowed" onoffswitch />
</div>
</div>
</div>
<div class="form-group">
<label for="resetPasswordAllowed" class="col-sm-2 control-label">Reset password</label>
<div class="col-sm-4">
<input ng-model="realm.resetPasswordAllowed" name="resetPasswordAllowed" id="resetPasswordAllowed" onoffswitch />
<div class="form-group">
<label for="resetPasswordAllowed" class="col-sm-2 control-label">Reset password</label>
<div class="col-sm-4">
<input ng-model="realm.resetPasswordAllowed" name="resetPasswordAllowed" id="resetPasswordAllowed" onoffswitch />
</div>
</div>
</div>
<div class="form-group">
<label for="verifyEmail" class="col-sm-2 control-label">Verify email</label>
<div class="col-sm-4">
<input ng-model="realm.verifyEmail" name="verifyEmail" id="verifyEmail" onoffswitch />
<div class="form-group">
<label for="verifyEmail" class="col-sm-2 control-label">Verify email</label>
<div class="col-sm-4">
<input ng-model="realm.verifyEmail" name="verifyEmail" id="verifyEmail" onoffswitch />
</div>
</div>
</div>
<div class="form-group">
<label for="requireSsl" class="col-sm-2 control-label">Require SSL</label>
<div class="col-sm-4">
<input ng-model="realm.requireSsl" name="requireSsl" id="requireSsl" onoffswitch />
<div class="form-group">
<label for="requireSsl" class="col-sm-2 control-label">Require SSL</label>
<div class="col-sm-4">
<input ng-model="realm.requireSsl" name="requireSsl" id="requireSsl" onoffswitch />
</div>
</div>
</div>
</fieldset>
<fieldset>
<legend><span class="text">Optional Settings</span></legend>
<div class="form-group">
<label class="col-sm-2 control-label" for="loginTheme">Login Theme</label>
<div class="col-sm-4">
<select kc-select id="loginTheme"
data-kc-placeholder="Select one..."
data-kc-model="realm.loginTheme"
data-kc-options="serverInfo.themes.login">
</select>
</fieldset>
<fieldset>
<legend><span class="text">Optional Settings</span></legend>
<div class="form-group">
<label class="col-sm-2 control-label" for="loginTheme">Login Theme</label>
<div class="col-sm-4">
<select kc-select id="loginTheme"
data-kc-placeholder="Select one..."
data-kc-model="realm.loginTheme"
data-kc-options="serverInfo.themes.login">
</select>
</div>
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="accountTheme">Account Theme</label>
<div class="col-sm-4">
<select kc-select id="accountTheme"
data-kc-placeholder="Select one..."
data-kc-model="realm.accountTheme"
data-kc-options="serverInfo.themes.account">
</select>
<div class="form-group">
<label class="col-sm-2 control-label" for="accountTheme">Account Theme</label>
<div class="col-sm-4">
<select kc-select id="accountTheme"
data-kc-placeholder="Select one..."
data-kc-model="realm.accountTheme"
data-kc-options="serverInfo.themes.account">
</select>
</div>
</div>
</div>
</fieldset>
</fieldset>
<div class="pull-right form-actions" data-ng-show="createRealm">
<button kc-cancel data-ng-click="cancel()">Cancel</button>
<button kc-save data-ng-show="changed">Save</button>
</div>
<div class="pull-right form-actions" data-ng-show="!createRealm">
<button kc-reset data-ng-show="changed">Clear changes</button>
<button kc-save data-ng-show="changed">Save</button>
<button kc-delete data-ng-click="remove()" data-ng-hide="changed">Delete</button>
</div>
</form>
</div>
<div data-ng-hide="auth.hasAccess(realm.realm, 'manage-realm')">
<h2 ><span>{{realm.realm}}</span></h2>
</div>
<div class="pull-right form-actions" data-ng-show="createRealm">
<button kc-cancel data-ng-click="cancel()">Cancel</button>
<button kc-save data-ng-show="changed">Save</button>
</div>
<div class="pull-right form-actions" data-ng-show="!createRealm">
<button kc-reset data-ng-show="changed">Clear changes</button>
<button kc-save data-ng-show="changed">Save</button>
<button kc-delete data-ng-click="remove()" data-ng-hide="changed">Delete</button>
</div>
</form>
</div>
</div>
</div>

View file

@ -1,10 +1,9 @@
<ul data-ng-hide="createRealm">
<li data-ng-class="((!path[2] || path[1] == 'role' || path[2] == 'roles' || path[2] == 'token-settings' ||
<li data-ng-show="auth.hasAccess(realm.realm, 'manage-realm')" data-ng-class="((!path[2] || path[1] == 'role' || path[2] == 'roles' || path[2] == 'token-settings' ||
path[2] == 'social-settings' || path[2] == 'required-credentials' || path[2] == 'default-roles' || path[2] == 'registration-settings' ||
path[2] == 'keys-settings' || path[2] == 'smtp-settings') && path[3] != 'applications') && 'active'"><a href="#/realms/{{realm.realm}}">Settings</a></li>
<li data-ng-class="(path[2] == 'users' || path[1] == 'user') && 'active'"><a href="#/realms/{{realm.realm}}/users">Users</a>
<li data-ng-show="auth.hasAccess(realm.realm, 'manage-users')" data-ng-class="(path[2] == 'users' || path[1] == 'user') && 'active'"><a href="#/realms/{{realm.realm}}/users">Users</a>
</li>
<li data-ng-class="(path[2] == 'applications' || path[1] == 'application' || path[3] == 'applications') && 'active'"><a href="#/realms/{{realm.realm}}/applications">Applications</a></li>
<li data-ng-class="(path[2] == 'oauth-clients' || path[1] == 'oauth-client') && 'active'"><a href="#/realms/{{realm.realm}}/oauth-clients">OAuth Clients</a></li>
</ul>
<li data-ng-show="auth.hasAccess(realm.realm, 'manage-applications')" data-ng-class="(path[2] == 'applications' || path[1] == 'application' || path[3] == 'applications') && 'active'"><a href="#/realms/{{realm.realm}}/applications">Applications</a></li>
<li data-ng-show="auth.hasAccess(realm.realm, 'manage-clients')" data-ng-class="(path[2] == 'oauth-clients' || path[1] == 'oauth-client') && 'active'"><a href="#/realms/{{realm.realm}}/oauth-clients">OAuth Clients</a></li>
</ul>

View file

@ -1,10 +1,10 @@
<ul class="nav nav-tabs nav-tabs-pf">
<li ng-class="{active: kcCurrent == 'general'}"><a href="#/realms/{{kcRealm}}">General</a></li>
<li ng-class="{active: kcCurrent == 'social'}" data-ng-show="kcSocial"><a href="#/realms/{{kcRealm}}/social-settings">Social</a></li>
<li ng-class="{active: kcCurrent == 'roles'}"><a href="#/realms/{{kcRealm}}/roles">Roles</a></li>
<li ng-class="{active: kcCurrent == 'defRoles'}"><a href="#/realms/{{kcRealm}}/default-roles">Default Roles</a></li>
<li ng-class="{active: kcCurrent == 'credentials'}"><a href="#/realms/{{kcRealm}}/required-credentials">Credentials</a></li>
<li ng-class="{active: kcCurrent == 'token'}"><a href="#/realms/{{kcRealm}}/token-settings">Token</a></li>
<li ng-class="{active: kcCurrent == 'keys'}"><a href="#/realms/{{kcRealm}}/keys-settings">Keys</a></li>
<li ng-class="{active: kcCurrent == 'email'}"><a href="#/realms/{{kcRealm}}/smtp-settings">Email</a></li>
</ul>
<li ng-class="{active: kcCurrent == 'social'}" data-ng-show="kcSocial && auth.hasAccess(realm.realm, 'manage-realm')"><a href="#/realms/{{kcRealm}}/social-settings">Social</a></li>
<li ng-class="{active: kcCurrent == 'roles'}" data-ng-show="auth.hasAccess(realm.realm, 'manage-realm')"><a href="#/realms/{{kcRealm}}/roles">Roles</a></li>
<li ng-class="{active: kcCurrent == 'defRoles'}" data-ng-show="auth.hasAccess(realm.realm, 'manage-realm')"><a href="#/realms/{{kcRealm}}/default-roles">Default Roles</a></li>
<li ng-class="{active: kcCurrent == 'credentials'}" data-ng-show="auth.hasAccess(realm.realm, 'manage-realm')"><a href="#/realms/{{kcRealm}}/required-credentials">Credentials</a></li>
<li ng-class="{active: kcCurrent == 'token'}" data-ng-show="auth.hasAccess(realm.realm, 'manage-realm')"><a href="#/realms/{{kcRealm}}/token-settings">Token</a></li>
<li ng-class="{active: kcCurrent == 'keys'}" data-ng-show="auth.hasAccess(realm.realm, 'manage-realm')"><a href="#/realms/{{kcRealm}}/keys-settings">Keys</a></li>
<li ng-class="{active: kcCurrent == 'email'}" data-ng-show="auth.hasAccess(realm.realm, 'manage-realm')"><a href="#/realms/{{kcRealm}}/smtp-settings">Email</a></li>
</ul>

View file

@ -0,0 +1,13 @@
package org.keycloak.models;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public interface AccountRoles {
String VIEW_PROFILE = "view-profile";
String MANAGE_ACCOUNT = "manage-account";
String[] ALL = {VIEW_PROFILE, MANAGE_ACCOUNT};
}

View file

@ -0,0 +1,23 @@
package org.keycloak.models;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class AdminRoles {
public static String APP_SUFFIX = "-realm";
public static String ADMIN = "admin";
public static String MANAGE_REALM = "manage-realm";
public static String MANAGE_USERS = "manage-users";
public static String MANAGE_APPLICATIONS = "manage-applications";
public static String MANAGE_CLIENTS = "manage-clients";
public static String[] ALL_REALM_ROLES = {MANAGE_REALM, MANAGE_USERS, MANAGE_APPLICATIONS, MANAGE_CLIENTS};
public static String getAdminApp(RealmModel realm) {
return realm.getName() + APP_SUFFIX;
}
}

View file

@ -5,14 +5,12 @@ package org.keycloak.models;
* @version $Revision: 1 $
*/
public interface Constants {
String INTERNAL_ROLE = "KEYCLOAK_";
String ADMIN_REALM = "keycloak-admin";
String ADMIN_CONSOLE_APPLICATION = "admin-console";
String ADMIN_CONSOLE_ADMIN_ROLE = "admin";
String INTERNAL_ROLE = "KEYCLOAK_";
String APPLICATION_ROLE = INTERNAL_ROLE + "_APPLICATION";
String IDENTITY_REQUESTER_ROLE = INTERNAL_ROLE + "_IDENTITY_REQUESTER";
String ACCOUNT_APPLICATION = "account";
String ACCOUNT_PROFILE_ROLE = "view-profile";
String ACCOUNT_MANAGE_ROLE = "manage-account";
String ACCOUNT_MANAGEMENT_APP = "account";
}

View file

@ -13,7 +13,7 @@ public interface KeycloakSession {
RealmModel createRealm(String id, String name);
RealmModel getRealm(String id);
RealmModel getRealmByName(String name);
List<RealmModel> getRealms(UserModel admin);
List<RealmModel> getRealms();
boolean removeRealm(String id);
void close();

View file

@ -49,7 +49,7 @@ public class JpaKeycloakSession implements KeycloakSession {
}
@Override
public List<RealmModel> getRealms(UserModel admin) {
public List<RealmModel> getRealms() {
TypedQuery<RealmEntity> query = em.createNamedQuery("getAllRealms", RealmEntity.class);
List<RealmEntity> entities = query.getResultList();
List<RealmModel> realms = new ArrayList<RealmModel>();

View file

@ -67,7 +67,7 @@ public class MongoKeycloakSession implements KeycloakSession {
}
@Override
public List<RealmModel> getRealms(UserModel admin) {
public List<RealmModel> getRealms() {
DBObject query = new BasicDBObject();
List<RealmEntity> realms = getMongoStore().loadEntities(RealmEntity.class, query, invocationContext);

View file

@ -13,6 +13,7 @@ import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.RoleModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.ApplianceBootstrap;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.util.JsonSerialization;
@ -34,6 +35,8 @@ public class AbstractModelTest {
identitySession = factory.createSession();
identitySession.getTransaction().begin();
realmManager = new RealmManager(identitySession);
new ApplianceBootstrap().bootstrap(identitySession);
}
@After

View file

@ -1,6 +1,7 @@
package org.keycloak.model.test;
import org.junit.Assert;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
@ -32,12 +33,6 @@ import java.util.Set;
public class AdapterTest extends AbstractModelTest {
private RealmModel realmModel;
@Test
public void installTest() throws Exception {
new ApplianceBootstrap().bootstrap(identitySession);
}
@Test
public void test1CreateRealm() throws Exception {
realmModel = realmManager.createRealm("JUGGLER");
@ -79,7 +74,6 @@ public class AdapterTest extends AbstractModelTest {
realmModel.setUpdateProfileOnInitialSocialLogin(true);
realmModel.addDefaultRole("foo");
System.out.println(realmModel.getId());
realmModel = realmManager.getRealm(realmModel.getId());
Assert.assertNotNull(realmModel);
Assert.assertEquals(realmModel.getAccessCodeLifespan(), 100);
@ -93,13 +87,11 @@ public class AdapterTest extends AbstractModelTest {
Assert.assertEquals(1, realmModel.getDefaultRoles().size());
Assert.assertEquals("foo", realmModel.getDefaultRoles().get(0));
String id = realmModel.getId();
System.out.println("id: " + id);
realmModel.getId();
commit();
List<RealmModel> realms = identitySession.getRealms(null);
System.out.println("num realms: " + realms.size());
Assert.assertEquals(realms.size(), 1);
List<RealmModel> realms = identitySession.getRealms();
Assert.assertEquals(realms.size(), 2);
}

View file

@ -4,6 +4,7 @@ import org.junit.Assert;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;
import org.keycloak.models.AccountRoles;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.Constants;
import org.keycloak.models.RealmModel;
@ -56,7 +57,7 @@ public class ImportTest extends AbstractModelTest {
// Test applications imported
ApplicationModel application = realm.getApplicationByName("Application");
ApplicationModel otherApp = realm.getApplicationByName("OtherApp");
ApplicationModel accountApp = realm.getApplicationByName(Constants.ACCOUNT_APPLICATION);
ApplicationModel accountApp = realm.getApplicationByName(Constants.ACCOUNT_MANAGEMENT_APP);
ApplicationModel nonExisting = realm.getApplicationByName("NonExisting");
Assert.assertNotNull(application);
Assert.assertNotNull(otherApp);
@ -80,8 +81,8 @@ public class ImportTest extends AbstractModelTest {
Assert.assertTrue(allRoles.contains(realm.getRole("admin")));
Assert.assertTrue(allRoles.contains(application.getRole("app-admin")));
Assert.assertTrue(allRoles.contains(otherApp.getRole("otherapp-admin")));
Assert.assertTrue(allRoles.contains(accountApp.getRole(Constants.ACCOUNT_PROFILE_ROLE)));
Assert.assertTrue(allRoles.contains(accountApp.getRole(Constants.ACCOUNT_MANAGE_ROLE)));
Assert.assertTrue(allRoles.contains(accountApp.getRole(AccountRoles.VIEW_PROFILE)));
Assert.assertTrue(allRoles.contains(accountApp.getRole(AccountRoles.MANAGE_ACCOUNT)));
UserModel wburke = realm.getUser("wburke");
allRoles = realm.getRoleMappings(wburke);

View file

@ -0,0 +1,176 @@
package org.keycloak.services.managers;
import org.jboss.resteasy.logging.Logger;
import org.jboss.resteasy.spi.HttpResponse;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.RSATokenVerifier;
import org.keycloak.VerificationException;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.AccessToken;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.NewCookie;
import java.net.URI;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class AppAuthManager extends AuthenticationManager {
protected static Logger logger = Logger.getLogger(AuthenticationManager.class);
private String cookieName;
private TokenManager tokenManager;
public AppAuthManager(String cookieName, TokenManager tokenManager) {
this.cookieName = cookieName;
this.tokenManager = tokenManager;
}
public NewCookie createCookie(RealmModel realm, UserModel client, String code, URI uri) {
JWSInput input = new JWSInput(code);
boolean verifiedCode = false;
try {
verifiedCode = RSAProvider.verify(input, realm.getPublicKey());
} catch (Exception ignored) {
logger.debug("Failed to verify signature", ignored);
}
if (!verifiedCode) {
logger.debug("unverified access code");
throw new BadRequestException();
}
String key = input.readContentAsString();
AccessCodeEntry accessCode = tokenManager.pullAccessCode(key);
if (accessCode == null) {
logger.debug("bad access code");
throw new BadRequestException();
}
if (accessCode.isExpired()) {
logger.debug("access code expired");
throw new BadRequestException();
}
if (!accessCode.getToken().isActive()) {
logger.debug("access token expired");
throw new BadRequestException();
}
if (!accessCode.getRealm().getId().equals(realm.getId())) {
logger.debug("bad realm");
throw new BadRequestException();
}
if (!client.getLoginName().equals(accessCode.getClient().getLoginName())) {
logger.debug("bad client");
throw new BadRequestException();
}
return createLoginCookie(realm, accessCode.getUser(), accessCode.getClient(), cookieName, uri.getRawPath(), false);
}
public NewCookie createRefreshCookie(RealmModel realm, UserModel user, UserModel client, URI uri) {
return createLoginCookie(realm, user, client, cookieName, uri.getRawPath(), false);
}
public void expireCookie(URI uri) {
expireCookie(cookieName, uri.getRawPath());
}
public Auth authenticateCookie(RealmModel realm, HttpHeaders headers) {
return authenticateCookie(realm, headers, cookieName, true);
}
public Auth authenticate(RealmModel realm, HttpHeaders headers) {
Auth auth = authenticateCookie(realm, headers);
if (auth != null) return auth;
return authenticateBearerToken(realm, headers);
}
private Auth authenticateCookie(RealmModel realm, HttpHeaders headers, String cookieName, boolean checkActive) {
logger.info("authenticateCookie");
Cookie cookie = headers.getCookies().get(cookieName);
if (cookie == null) {
logger.info("authenticateCookie could not find cookie: {0}", cookieName);
return null;
}
String tokenString = cookie.getValue();
try {
AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), realm.getName(), checkActive);
logger.info("token verified");
if (checkActive && !token.isActive()) {
logger.info("cookie expired");
expireCookie(cookie.getName(), cookie.getPath());
return null;
}
UserModel user = realm.getUserById(token.getSubject());
if (user == null || !user.isEnabled()) {
logger.info("Unknown user in cookie");
expireCookie(cookie.getName(), cookie.getPath());
return null;
}
UserModel client = null;
if (token.getIssuedFor() != null) {
client = realm.getUser(token.getIssuedFor());
if (client == null || !client.isEnabled()) {
logger.info("Unknown client in cookie");
expireCookie(cookie.getName(), cookie.getPath());
return null;
}
}
return new Auth(realm, user, client);
} catch (VerificationException e) {
logger.info("Failed to verify cookie", e);
expireCookie(cookie.getName(), cookie.getPath());
}
return null;
}
private Auth authenticateBearerToken(RealmModel realm, HttpHeaders headers) {
String tokenString;
String authHeader = headers.getHeaderString(HttpHeaders.AUTHORIZATION);
if (authHeader == null) {
return null;
} else {
String[] split = authHeader.trim().split("\\s+");
if (split == null || split.length != 2) throw new NotAuthorizedException("Bearer");
if (!split[0].equalsIgnoreCase("Bearer")) throw new NotAuthorizedException("Bearer");
tokenString = split[1];
}
try {
AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), realm.getName());
if (!token.isActive()) {
throw new NotAuthorizedException("token_expired");
}
UserModel user = realm.getUserById(token.getSubject());
if (user == null || !user.isEnabled()) {
throw new NotAuthorizedException("invalid_user");
}
UserModel client = null;
if (token.getIssuedFor() != null) {
client = realm.getUser(token.getIssuedFor());
if (client == null || !client.isEnabled()) {
throw new NotAuthorizedException("invalid_user");
}
}
return new Auth(token, user, client);
} catch (VerificationException e) {
logger.error("Failed to verify token", e);
throw new NotAuthorizedException("invalid_token");
}
}
}

View file

@ -1,6 +1,7 @@
package org.keycloak.services.managers;
import org.jboss.resteasy.logging.Logger;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
@ -62,7 +63,9 @@ public class ApplianceBootstrap {
adminConsole.setBaseUrl("/auth/admin/index.html");
adminConsole.setEnabled(true);
RoleModel adminRole = adminConsole.addRole(Constants.ADMIN_CONSOLE_ADMIN_ROLE);
RoleModel adminRole = realm.getRole(AdminRoles.ADMIN);
adminConsole.addScope(adminRole);
UserModel adminUser = realm.addUser("admin");
adminUser.setEnabled(true);
@ -74,7 +77,7 @@ public class ApplianceBootstrap {
realm.grantRole(adminUser, adminRole);
ApplicationModel accountApp = realm.getApplicationNameMap().get(Constants.ACCOUNT_APPLICATION);
ApplicationModel accountApp = realm.getApplicationNameMap().get(Constants.ACCOUNT_MANAGEMENT_APP);
for (String r : accountApp.getDefaultRoles()) {
realm.grantRole(adminUser, accountApp.getRole(r));
}

View file

@ -0,0 +1,138 @@
package org.keycloak.services.managers;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.Constants;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.AccessToken;
import javax.ws.rs.ForbiddenException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
*/
public class Auth {
private final boolean cookie;
private final RealmModel realm;
private final AccessToken token;
private final UserModel user;
private final UserModel client;
public Auth(RealmModel realm, UserModel user, UserModel client) {
this.cookie = true;
this.realm = realm;
this.token = null;
this.user = user;
this.client = client;
}
public Auth(AccessToken token, UserModel user, UserModel client) {
this.cookie = false;
this.token = token;
this.realm = null;
this.user = user;
this.client = client;
}
public boolean isCookie() {
return cookie;
}
public RealmModel getRealm() {
return realm;
}
public UserModel getUser() {
return user;
}
public UserModel getClient() {
return client;
}
public AccessToken getToken() {
return token;
}
public void require(String role) {
if (!has(role)) {
throw new ForbiddenException();
}
}
public void require(String app, String role) {
if (!has(app, role)) {
throw new ForbiddenException();
}
}
public void require(ApplicationModel app, String role) {
if (!has(app, role)) {
throw new ForbiddenException();
}
}
public void requireOneOf(String app, String... roles) {
if(!hasOneOf(app, roles)) {
throw new ForbiddenException();
}
}
public void requireOneOf(ApplicationModel app, String... roles) {
if(!hasOneOf(app, roles)) {
throw new ForbiddenException();
}
}
public boolean has(String role) {
if (cookie) {
return realm.hasRole(user, realm.getRole(role));
} else {
return token.getRealmAccess() != null && token.getRealmAccess().isUserInRole(role);
}
}
public boolean has(String app, String role) {
if (cookie) {
return realm.hasRole(user, realm.getApplicationByName(app).getRole(role));
} else {
AccessToken.Access access = token.getResourceAccess(app);
return access != null && access.isUserInRole(role);
}
}
public boolean has(ApplicationModel app, String role) {
if (cookie) {
return realm.hasRole(user, app.getRole(role));
} else {
AccessToken.Access access = token.getResourceAccess(app.getName());
return access != null && access.isUserInRole(role);
}
}
public boolean hasOneOf(String app, String... roles) {
for (String r : roles) {
if (has(app, r)) {
return true;
}
}
return false;
}
public boolean hasOneOf(ApplicationModel app, String... roles) {
for (String r : roles) {
if (has(app, r)) {
return true;
}
}
return false;
}
}

View file

@ -14,11 +14,8 @@ import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.resources.AccountService;
import org.keycloak.services.resources.admin.AdminService;
import org.keycloak.services.resources.RealmsResource;
import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.core.Cookie;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap;
@ -59,19 +56,6 @@ public class AuthenticationManager {
return createLoginCookie(realm, user, null, cookieName, cookiePath, rememberMe);
}
public NewCookie createSaasIdentityCookie(RealmModel realm, UserModel user, UriInfo uriInfo) {
String cookieName = AdminService.SAAS_IDENTITY_COOKIE;
URI uri = AdminService.saasCookiePath(uriInfo).build();
String cookiePath = uri.getRawPath();
return createLoginCookie(realm, user, null, cookieName, cookiePath, false);
}
public NewCookie createAccountIdentityCookie(RealmModel realm, UserModel user, UserModel client, URI uri) {
String cookieName = AccountService.ACCOUNT_IDENTITY_COOKIE;
String cookiePath = uri.getRawPath();
return createLoginCookie(realm, user, client, cookieName, cookiePath, false);
}
protected NewCookie createLoginCookie(RealmModel realm, UserModel user, UserModel client, String cookieName, String cookiePath, boolean rememberMe) {
AccessToken identityToken = createIdentityToken(realm, user);
if (client != null) {
@ -104,7 +88,6 @@ public class AuthenticationManager {
return encodedToken;
}
public void expireIdentityCookie(RealmModel realm, UriInfo uriInfo) {
logger.debug("Expiring identity cookie");
String path = getIdentityCookiePath(realm, uriInfo);
@ -123,17 +106,6 @@ public class AuthenticationManager {
return uri.getRawPath();
}
public void expireSaasIdentityCookie(UriInfo uriInfo) {
URI uri = AdminService.saasCookiePath(uriInfo).build();
String cookiePath = uri.getRawPath();
expireCookie(AdminService.SAAS_IDENTITY_COOKIE, cookiePath);
}
public void expireAccountIdentityCookie(URI uri) {
String cookiePath = uri.getRawPath();
expireCookie(AccountService.ACCOUNT_IDENTITY_COOKIE, cookiePath);
}
public void expireCookie(String cookieName, String path) {
HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class);
if (response == null) {
@ -149,42 +121,13 @@ public class AuthenticationManager {
return authenticateIdentityCookie(realm, uriInfo, headers, true);
}
public UserModel authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers, boolean checkActive) {
logger.info("authenticateIdentityCookie");
String cookieName = KEYCLOAK_IDENTITY_COOKIE;
Auth auth = authenticateIdentityCookie(realm, uriInfo, headers, cookieName, checkActive);
return auth != null ? auth.getUser() : null;
return authenticateIdentityCookie(realm, uriInfo, headers, cookieName, checkActive);
}
public UserModel authenticateSaasIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
String cookieName = AdminService.SAAS_IDENTITY_COOKIE;
Auth auth = authenticateIdentityCookie(realm, uriInfo, headers, cookieName, true);
return auth != null ? auth.getUser() : null;
}
public Auth authenticateAccountIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
String cookieName = AccountService.ACCOUNT_IDENTITY_COOKIE;
return authenticateIdentityCookie(realm, uriInfo, headers, cookieName, true);
}
public UserModel authenticateSaasIdentity(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
UserModel user = authenticateSaasIdentityCookie(realm, uriInfo, headers);
if (user != null) return user;
Auth auth = authenticateBearerToken(realm, headers);
return auth != null ? auth.getUser() : null;
}
public Auth authenticateAccountIdentity(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
Auth auth = authenticateAccountIdentityCookie(realm, uriInfo, headers);
if (auth != null) return auth;
return authenticateBearerToken(realm, headers);
}
protected Auth authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers, String cookieName, boolean checkActive) {
protected UserModel authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers, String cookieName, boolean checkActive) {
logger.info("authenticateIdentityCookie");
Cookie cookie = headers.getCookies().get(cookieName);
if (cookie == null) {
@ -202,27 +145,14 @@ public class AuthenticationManager {
return null;
}
Auth auth = new Auth(token);
UserModel user = realm.getUserById(token.getSubject());
if (user == null || !user.isEnabled()) {
logger.info("Unknown user in identity cookie");
expireIdentityCookie(realm, uriInfo);
return null;
}
auth.setUser(user);
if (token.getIssuedFor() != null) {
UserModel client = realm.getUser(token.getIssuedFor());
if (client == null || !client.isEnabled()) {
logger.info("Unknown client in identity cookie");
expireIdentityCookie(realm, uriInfo);
return null;
}
auth.setClient(client);
}
return auth;
return user;
} catch (VerificationException e) {
logger.info("Failed to verify identity cookie", e);
expireCookie(cookie.getName(), cookie.getPath());
@ -230,49 +160,6 @@ public class AuthenticationManager {
return null;
}
public Auth authenticateBearerToken(RealmModel realm, HttpHeaders headers) {
String tokenString = null;
String authHeader = headers.getHeaderString(HttpHeaders.AUTHORIZATION);
if (authHeader == null) {
return null;
} else {
String[] split = authHeader.trim().split("\\s+");
if (split == null || split.length != 2) throw new NotAuthorizedException("Bearer");
if (!split[0].equalsIgnoreCase("Bearer")) throw new NotAuthorizedException("Bearer");
tokenString = split[1];
}
try {
AccessToken token = RSATokenVerifier.verifyToken(tokenString, realm.getPublicKey(), realm.getName());
if (!token.isActive()) {
throw new NotAuthorizedException("token_expired");
}
Auth auth = new Auth(token);
UserModel user = realm.getUserById(token.getSubject());
if (user == null || !user.isEnabled()) {
throw new NotAuthorizedException("invalid_user");
}
auth.setUser(user);
if (token.getIssuedFor() != null) {
UserModel client = realm.getUser(token.getIssuedFor());
if (client == null || !client.isEnabled()) {
throw new NotAuthorizedException("invalid_user");
}
auth.setClient(client);
}
return auth;
} catch (VerificationException e) {
logger.error("Failed to verify token", e);
throw new NotAuthorizedException("invalid_token");
}
}
public AuthenticationStatus authenticateForm(RealmModel realm, UserModel user, MultivaluedMap<String, String> formData) {
if (user == null) {
logger.debug("Not Authenticated! Incorrect user name");
@ -356,34 +243,4 @@ public class AuthenticationManager {
SUCCESS, ACCOUNT_DISABLED, ACTIONS_REQUIRED, INVALID_USER, INVALID_CREDENTIALS, MISSING_PASSWORD, MISSING_TOTP, FAILED
}
public static class Auth {
private AccessToken token;
private UserModel user;
private UserModel client;
public Auth(AccessToken token) {
this.token = token;
}
public AccessToken getToken() {
return token;
}
public UserModel getUser() {
return user;
}
public UserModel getClient() {
return client;
}
void setUser(UserModel user) {
this.user = user;
}
void setClient(UserModel client) {
this.client = client;
}
}
}

View file

@ -85,7 +85,7 @@ public class ModelToRepresentation {
rep.setPasswordPolicy(realm.getPasswordPolicy().toString());
}
ApplicationModel accountManagementApplication = realm.getApplicationNameMap().get(Constants.ACCOUNT_APPLICATION);
ApplicationModel accountManagementApplication = realm.getApplicationNameMap().get(Constants.ACCOUNT_MANAGEMENT_APP);
List<String> defaultRoles = realm.getDefaultRoles();
if (!defaultRoles.isEmpty()) {

View file

@ -1,6 +1,8 @@
package org.keycloak.services.managers;
import org.jboss.resteasy.logging.Logger;
import org.keycloak.models.AccountRoles;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
@ -59,7 +61,6 @@ public class RealmManager {
return identitySession.getRealmByName(name);
}
public RealmModel createRealm(String name) {
return createRealm(name, name);
}
@ -71,13 +72,31 @@ public class RealmManager {
realm.addRole(Constants.APPLICATION_ROLE);
realm.addRole(Constants.IDENTITY_REQUESTER_ROLE);
setupAdminManagement(realm);
setupAccountManagement(realm);
realm.addRequiredOAuthClientCredential(UserCredentialModel.SECRET);
realm.addRequiredResourceCredential(UserCredentialModel.SECRET);
return realm;
}
public boolean removeRealm(RealmModel realm) {
boolean removed = identitySession.removeRealm(realm.getId());
RealmModel adminRealm = getKeycloakAdminstrationRealm();
RoleModel adminRole = adminRealm.getRole(AdminRoles.ADMIN);
ApplicationModel realmAdminApp = adminRealm.getApplicationByName(AdminRoles.getAdminApp(realm));
for (RoleModel r : realmAdminApp.getRoles()) {
adminRole.removeCompositeRole(r);
}
adminRealm.removeApplication(realmAdminApp.getId());
return removed;
}
public void generateRealmKeys(RealmModel realm) {
KeyPair keyPair = null;
try {
@ -134,19 +153,41 @@ public class RealmManager {
}
}
private void setupAccountManagement(RealmModel realm) {
ApplicationModel application = realm.getApplicationNameMap().get(Constants.ACCOUNT_APPLICATION);
if (application == null) {
application = new ApplicationManager(this).createApplication(realm, Constants.ACCOUNT_APPLICATION);
application.setEnabled(true);
private void setupAdminManagement(RealmModel realm) {
RealmModel adminRealm;
RoleModel adminRole;
application.addDefaultRole(Constants.ACCOUNT_PROFILE_ROLE);
application.addDefaultRole(Constants.ACCOUNT_MANAGE_ROLE);
if (realm.getName().equals(Constants.ADMIN_REALM)) {
adminRealm = realm;
adminRole = realm.addRole(AdminRoles.ADMIN);
} else {
adminRealm = identitySession.getRealmByName(Constants.ADMIN_REALM);
adminRole = adminRealm.getRole(AdminRoles.ADMIN);
}
ApplicationManager applicationManager = new ApplicationManager(new RealmManager(identitySession));
ApplicationModel realmAdminApp = applicationManager.createApplication(adminRealm, AdminRoles.getAdminApp(realm));
for (String r : AdminRoles.ALL_REALM_ROLES) {
RoleModel role = realmAdminApp.addRole(r);
adminRole.addCompositeRole(role);
}
}
public RealmModel importRealm(RealmRepresentation rep, UserModel realmCreator) {
private void setupAccountManagement(RealmModel realm) {
ApplicationModel application = realm.getApplicationNameMap().get(Constants.ACCOUNT_MANAGEMENT_APP);
if (application == null) {
application = new ApplicationManager(this).createApplication(realm, Constants.ACCOUNT_MANAGEMENT_APP);
application.setEnabled(true);
for (String role : AccountRoles.ALL) {
application.addDefaultRole(role);
}
}
}
public RealmModel importRealm(RealmRepresentation rep) {
String id = rep.getId();
if (id == null) {
id = KeycloakModelUtils.generateId();
@ -299,7 +340,6 @@ public class RealmManager {
}
if (rep.getRoleMappings() != null) {
for (UserRoleMappingRepresentation mapping : rep.getRoleMappings()) {
UserModel user = userMap.get(mapping.getUsername());

View file

@ -23,7 +23,6 @@ package org.keycloak.services.resources;
import org.jboss.resteasy.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.AbstractOAuthClient;
import org.keycloak.account.Account;
import org.keycloak.account.AccountLoader;
import org.keycloak.account.AccountPages;
@ -32,10 +31,10 @@ import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.*;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.services.managers.AccessCodeEntry;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.Auth;
import org.keycloak.services.managers.ModelToRepresentation;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.messages.Messages;
@ -68,23 +67,23 @@ public class AccountService {
@Context
private UriInfo uriInfo;
private AuthenticationManager authManager = new AuthenticationManager();
private AppAuthManager authManager;
private ApplicationModel application;
private TokenManager tokenManager;
public AccountService(RealmModel realm, ApplicationModel application, TokenManager tokenManager) {
this.realm = realm;
this.application = application;
this.tokenManager = tokenManager;
this.authManager = new AppAuthManager("KEYCLOAK_ACCOUNT_IDENTITY", tokenManager);
}
private Response forwardToPage(String path, AccountPages page) {
AuthenticationManager.Auth auth = getAuth(false);
Auth auth = getAuth(false);
if (auth != null) {
if (!hasAccess(auth)) {
return noAccess();
try {
auth.require(application, AccountRoles.MANAGE_ACCOUNT);
} catch (ForbiddenException e) {
return Flows.forms(realm, request, uriInfo).setError("No access").createErrorPage();
}
Account account = AccountLoader.load().createAccount(uriInfo).setRealm(realm).setUser(auth.getUser());
@ -100,10 +99,6 @@ public class AccountService {
}
}
private Response noAccess() {
return Flows.forms(realm, request, uriInfo).setError("No access").createErrorPage();
}
@Path("/")
@OPTIONS
public Response accountPreflight() {
@ -117,10 +112,9 @@ public class AccountService {
if (types.contains(MediaType.WILDCARD_TYPE) || (types.contains(MediaType.TEXT_HTML_TYPE))) {
return forwardToPage(null, AccountPages.ACCOUNT);
} else if (types.contains(MediaType.APPLICATION_JSON_TYPE)) {
AuthenticationManager.Auth auth = getAuth(true);
if (!hasAccess(auth, Constants.ACCOUNT_PROFILE_ROLE)) {
return Response.status(Response.Status.FORBIDDEN).build();
}
Auth auth = getAuth(true);
auth.requireOneOf(application, AccountRoles.MANAGE_ACCOUNT, AccountRoles.VIEW_PROFILE);
return Cors.add(request, Response.ok(ModelToRepresentation.toRepresentation(auth.getUser()))).auth().allowedOrigins(auth.getClient()).build();
} else {
return Response.notAcceptable(Variant.VariantListBuilder.newInstance().mediaTypes(MediaType.TEXT_HTML_TYPE, MediaType.APPLICATION_JSON_TYPE).build()).build();
@ -143,10 +137,8 @@ public class AccountService {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processAccountUpdate(final MultivaluedMap<String, String> formData) {
AuthenticationManager.Auth auth = getAuth(true);
if (!hasAccess(auth)) {
return noAccess();
}
Auth auth = getAuth(true);
auth.require(application, AccountRoles.MANAGE_ACCOUNT);
UserModel user = auth.getUser();
@ -167,10 +159,8 @@ public class AccountService {
@Path("totp-remove")
@GET
public Response processTotpRemove() {
AuthenticationManager.Auth auth = getAuth(true);
if (!hasAccess(auth)) {
return noAccess();
}
Auth auth = getAuth(true);
auth.require(application, AccountRoles.MANAGE_ACCOUNT);
UserModel user = auth.getUser();
user.setTotp(false);
@ -183,10 +173,8 @@ public class AccountService {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processTotpUpdate(final MultivaluedMap<String, String> formData) {
AuthenticationManager.Auth auth = getAuth(true);
if (!hasAccess(auth)) {
return noAccess();
}
Auth auth = getAuth(true);
auth.require(application, AccountRoles.MANAGE_ACCOUNT);
UserModel user = auth.getUser();
@ -215,10 +203,8 @@ public class AccountService {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processPasswordUpdate(final MultivaluedMap<String, String> formData) {
AuthenticationManager.Auth auth = getAuth(true);
if (!hasAccess(auth)) {
return noAccess();
}
Auth auth = getAuth(true);
auth.require(application, AccountRoles.MANAGE_ACCOUNT);
UserModel user = auth.getUser();
@ -285,61 +271,25 @@ public class AccountService {
throw new BadRequestException();
}
JWSInput input = new JWSInput(code);
boolean verifiedCode = false;
try {
verifiedCode = RSAProvider.verify(input, realm.getPublicKey());
} catch (Exception ignored) {
logger.debug("Failed to verify signature", ignored);
}
if (!verifiedCode) {
logger.debug("unverified access code");
throw new BadRequestException();
}
String key = input.readContentAsString();
AccessCodeEntry accessCode = tokenManager.pullAccessCode(key);
if (accessCode == null) {
logger.debug("bad access code");
throw new BadRequestException();
}
if (accessCode.isExpired()) {
logger.debug("access code expired");
throw new BadRequestException();
}
if (!accessCode.getToken().isActive()) {
logger.debug("access token expired");
throw new BadRequestException();
}
if (!accessCode.getRealm().getId().equals(realm.getId())) {
logger.debug("bad realm");
throw new BadRequestException();
}
if (!client.getLoginName().equals(accessCode.getClient().getLoginName())) {
logger.debug("bad client");
throw new BadRequestException();
}
URI accountUri = Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName());
URI redirectUri = path != null ? accountUri.resolve(path) : accountUri;
if (referrer != null) {
redirectUri = redirectUri.resolve("?referrer=" + referrer);
}
NewCookie cookie = authManager.createAccountIdentityCookie(realm, accessCode.getUser(), client, Urls.accountBase(uriInfo.getBaseUri()).build(realm.getName()));
NewCookie cookie = authManager.createCookie(realm, client, code, Urls.accountBase(uriInfo.getBaseUri()).build(realm.getName()));
return Response.status(302).cookie(cookie).location(redirectUri).build();
} finally {
authManager.expireCookie(AbstractOAuthClient.OAUTH_TOKEN_REQUEST_STATE, uriInfo.getAbsolutePath().getRawPath());
authManager.expireCookie(Urls.accountBase(uriInfo.getBaseUri()).build(realm.getName()));
}
}
@Path("logout")
@GET
public Response logout() {
// TODO Should use single-sign out via TokenService
URI baseUri = Urls.accountBase(uriInfo.getBaseUri()).build(realm.getName());
authManager.expireIdentityCookie(realm, uriInfo);
authManager.expireAccountIdentityCookie(baseUri);
authManager.expireCookie(baseUri);
return Response.status(302).location(baseUri).build();
}
@ -348,7 +298,7 @@ public class AccountService {
String authUrl = Urls.realmLoginPage(uriInfo.getBaseUri(), realm.getName()).toString();
oauth.setAuthUrl(authUrl);
oauth.setClientId(Constants.ACCOUNT_APPLICATION);
oauth.setClientId(Constants.ACCOUNT_MANAGEMENT_APP);
UriBuilder uriBuilder = Urls.accountPageBuilder(uriInfo.getBaseUri()).path(AccountService.class, "loginRedirect");
@ -368,42 +318,14 @@ public class AccountService {
return oauth.redirect(uriInfo, accountUri.toString());
}
private AuthenticationManager.Auth getAuth(boolean error) {
AuthenticationManager.Auth auth = authManager.authenticateAccountIdentity(realm, uriInfo, headers);
private Auth getAuth(boolean error) {
Auth auth = authManager.authenticate(realm, headers);
if (auth == null && error) {
throw new ForbiddenException();
}
return auth;
}
private boolean hasAccess(AuthenticationManager.Auth auth) {
return hasAccess(auth, null);
}
private boolean hasAccess(AuthenticationManager.Auth auth, String role) {
UserModel client = auth.getClient();
if (realm.hasRole(client, realm.getRole(Constants.APPLICATION_ROLE))) {
// Tokens from cookies don't have roles
UserModel user = auth.getUser();
if (hasRole(user, Constants.ACCOUNT_MANAGE_ROLE) || (role != null && hasRole(user, role))) {
return true;
}
}
AccessToken.Access access = auth.getToken().getResourceAccess(application.getName());
if (access != null) {
if (access.isUserInRole(Constants.ACCOUNT_MANAGE_ROLE) || (role != null && access.isUserInRole(role))) {
return true;
}
}
return false;
}
private boolean hasRole(UserModel user, String role) {
return realm.hasRole(user, application.getRole(role));
}
private String getReferrer() {
String referrer = uriInfo.getQueryParameters().getFirst("referrer");
if (referrer != null) {

View file

@ -69,7 +69,7 @@ public class RealmsResource {
RealmManager realmManager = new RealmManager(session);
RealmModel realm = locateRealm(name, realmManager);
ApplicationModel application = realm.getApplicationNameMap().get(Constants.ACCOUNT_APPLICATION);
ApplicationModel application = realm.getApplicationNameMap().get(Constants.ACCOUNT_MANAGEMENT_APP);
if (application == null || !application.isEnabled()) {
logger.debug("account management not enabled");
throw new NotFoundException();

View file

@ -1,49 +1,44 @@
package org.keycloak.services.resources.admin;
import org.codehaus.jackson.annotate.JsonProperty;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.HttpResponse;
import org.jboss.resteasy.spi.NotImplementedYetException;
import org.keycloak.AbstractOAuthClient;
import org.keycloak.jaxrs.JaxrsOAuthClient;
import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.crypto.RSAProvider;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserModel;
import org.keycloak.services.managers.AccessCodeEntry;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.AuthenticationManager.AuthenticationStatus;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.Auth;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.messages.Messages;
import org.keycloak.services.resources.TokenService;
import org.keycloak.services.resources.flows.Flows;
import org.keycloak.services.resources.flows.OAuthFlows;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.container.ResourceContext;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.Providers;
import java.net.URI;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -52,8 +47,6 @@ import java.net.URI;
@Path("/admin")
public class AdminService {
protected static final Logger logger = Logger.getLogger(AdminService.class);
public static final String REALM_CREATOR_ROLE = "realm-creator";
public static final String SAAS_IDENTITY_COOKIE = "KEYCLOAK_SAAS_IDENTITY";
@Context
protected UriInfo uriInfo;
@ -73,25 +66,32 @@ public class AdminService {
@Context
protected Providers providers;
protected String adminPath = "/admin/index.html";
protected AuthenticationManager authManager = new AuthenticationManager();
protected AppAuthManager authManager;
protected TokenManager tokenManager;
public AdminService(TokenManager tokenManager) {
this.tokenManager = tokenManager;
this.authManager = new AppAuthManager("KEYCLOAK_ADMIN_CONSOLE_IDENTITY", tokenManager);
}
public static class WhoAmI {
protected String userId;
protected String displayName;
@JsonProperty("admin")
protected boolean admin;
@JsonProperty("realm_access")
protected Map<String, Set<String>> realmAccess = new HashMap<String, Set<String>>();
public WhoAmI() {
}
public WhoAmI(String userId, String displayName) {
public WhoAmI(String userId, String displayName, boolean admin, Map<String, Set<String>> realmAccess) {
this.userId = userId;
this.displayName = displayName;
this.admin = admin;
this.realmAccess = realmAccess;
}
public String getUserId() {
@ -109,6 +109,22 @@ public class AdminService {
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
public boolean isAdmin() {
return admin;
}
public void setAdmin(boolean admin) {
this.admin = admin;
}
public Map<String, Set<String>> getRealmAccess() {
return realmAccess;
}
public void setRealmAccess(Map<String, Set<String>> realmAccess) {
this.realmAccess = realmAccess;
}
}
@Path("keepalive")
@ -120,11 +136,11 @@ public class AdminService {
RealmModel realm = getAdminstrationRealm(realmManager);
if (realm == null)
throw new NotFoundException();
UserModel user = authManager.authenticateSaasIdentityCookie(realm, uriInfo, headers);
if (user == null) {
Auth auth = authManager.authenticateCookie(realm, headers);
if (auth == null) {
return Response.status(401).build();
}
NewCookie refreshCookie = authManager.createSaasIdentityCookie(realm, user, uriInfo);
NewCookie refreshCookie = authManager.createRefreshCookie(realm, auth.getUser(), auth.getClient(), AdminService.saasCookiePath(uriInfo).build());
return Response.noContent().cookie(refreshCookie).build();
}
@ -137,15 +153,50 @@ public class AdminService {
RealmModel realm = getAdminstrationRealm(realmManager);
if (realm == null)
throw new NotFoundException();
UserModel user = authManager.authenticateSaasIdentityCookie(realm, uriInfo, headers);
Auth auth = authManager.authenticateCookie(realm, headers);
UserModel user = auth.getUser();
if (user == null) {
return Response.status(401).build();
}
// keycloak is bootstrapped with an admin user with no first/last name, so use login name as display name
return Response.ok(new WhoAmI(user.getLoginName(), user.getLoginName())).build();
String displayName;
if (user.getFirstName() != null || user.getLastName() != null) {
displayName = user.getFirstName();
if (user.getLastName() != null) {
displayName = displayName != null ? displayName + " " + user.getLastName() : user.getLastName();
}
} else {
displayName = user.getLoginName();
}
boolean admin = realm.hasRole(user, realm.getRole("admin"));
Map<String, Set<String>> realmAccess = new HashMap<String, Set<String>>();
addRealmAdminAccess(realmAccess, auth.getRealm().getRoleMappings(auth.getUser()));
return Response.ok(new WhoAmI(user.getId(), displayName, admin, realmAccess)).build();
}
@Path("isLoggedIn.js")
private void addRealmAdminAccess(Map<String, Set<String>> realmAdminAccess, Set<RoleModel> roles) {
for (RoleModel r : roles) {
if (r.getContainer() instanceof ApplicationModel) {
ApplicationModel app = (ApplicationModel) r.getContainer();
if (app.getName().endsWith(AdminRoles.APP_SUFFIX)) {
String realm = app.getName().substring(0, app.getName().length() - AdminRoles.APP_SUFFIX.length());
if (!realmAdminAccess.containsKey(realm)) {
realmAdminAccess.put(realm, new HashSet<String>());
}
realmAdminAccess.get(realm).add(r.getName());
}
}
if (r.isComposite()) {
addRealmAdminAccess(realmAdminAccess, r.getComposites());
}
}
}
@Path("isLoggedIn.js")
@GET
@Produces("application/javascript")
@NoCache
@ -157,7 +208,7 @@ public class AdminService {
return "var keycloakCookieLoggedIn = false;";
}
UserModel user = authManager.authenticateSaasIdentityCookie(realm, uriInfo, headers);
UserModel user = authManager.authenticateCookie(realm, headers).getUser();
if (user == null) {
return "var keycloakCookieLoggedIn = false;";
}
@ -176,23 +227,14 @@ public class AdminService {
@Path("realms")
public RealmsAdminResource getRealmsAdmin(@Context final HttpHeaders headers) {
RealmManager realmManager = new RealmManager(session);
RealmModel saasRealm = getAdminstrationRealm(realmManager);
if (saasRealm == null)
RealmModel adminRealm = getAdminstrationRealm(realmManager);
if (adminRealm == null)
throw new NotFoundException();
UserModel admin = authManager.authenticateSaasIdentity(saasRealm, uriInfo, headers);
if (admin == null) {
Auth auth = authManager.authenticate(adminRealm, headers);
if (auth == null) {
throw new NotAuthorizedException("Bearer");
}
ApplicationModel adminConsole = saasRealm.getApplicationNameMap().get(Constants.ADMIN_CONSOLE_APPLICATION);
if (adminConsole == null) {
throw new NotFoundException();
}
RoleModel adminRole = adminConsole.getRole(Constants.ADMIN_CONSOLE_ADMIN_ROLE);
if (!saasRealm.hasRole(admin, adminRole)) {
logger.warn("not a Realm admin");
throw new NotAuthorizedException("Bearer");
}
RealmsAdminResource adminResource = new RealmsAdminResource(admin, tokenManager);
RealmsAdminResource adminResource = new RealmsAdminResource(auth, tokenManager);
resourceContext.initResource(adminResource);
return adminResource;
}
@ -200,35 +242,33 @@ public class AdminService {
@Path("serverinfo")
public ServerInfoAdminResource getServerInfo(@Context final HttpHeaders headers) {
RealmManager realmManager = new RealmManager(session);
RealmModel saasRealm = getAdminstrationRealm(realmManager);
if (saasRealm == null)
RealmModel adminRealm = getAdminstrationRealm(realmManager);
if (adminRealm == null)
throw new NotFoundException();
UserModel admin = authManager.authenticateSaasIdentity(saasRealm, uriInfo, headers);
Auth auth = authManager.authenticate(adminRealm, headers);
UserModel admin = auth.getUser();
if (admin == null) {
throw new NotAuthorizedException("Bearer");
}
ApplicationModel adminConsole = saasRealm.getApplicationNameMap().get(Constants.ADMIN_CONSOLE_APPLICATION);
ApplicationModel adminConsole = adminRealm.getApplicationNameMap().get(Constants.ADMIN_CONSOLE_APPLICATION);
if (adminConsole == null) {
throw new NotFoundException();
}
RoleModel adminRole = adminConsole.getRole(Constants.ADMIN_CONSOLE_ADMIN_ROLE);
if (!saasRealm.hasRole(admin, adminRole)) {
logger.warn("not a Realm admin");
throw new NotAuthorizedException("Bearer");
}
ServerInfoAdminResource adminResource = new ServerInfoAdminResource();
resourceContext.initResource(adminResource);
return adminResource;
}
private void expireCookie() {
authManager.expireCookie(AdminService.saasCookiePath(uriInfo).build());
}
@Path("login")
@GET
@NoCache
public Response loginPage(@QueryParam("path") String path) {
logger.debug("loginPage ********************** <---");
RealmManager realmManager = new RealmManager(session);
RealmModel realm = getAdminstrationRealm(realmManager);
authManager.expireSaasIdentityCookie(uriInfo);
expireCookie();
JaxrsOAuthClient oauth = new JaxrsOAuthClient();
String authUrl = TokenService.loginPageUrl(uriInfo).build(Constants.ADMIN_REALM).toString();
@ -259,7 +299,6 @@ public class AdminService {
URI uri = uriInfo.getBaseUriBuilder().path(AdminService.class).path(AdminService.class, "errorOnLoginRedirect").queryParam("error", message).build();
URI logout = TokenService.logoutUrl(uriInfo).queryParam("redirect_uri", uri.toString()).build(Constants.ADMIN_REALM);
return Response.status(302).location(logout).build();
}
@Path("login-redirect")
@ -279,12 +318,12 @@ public class AdminService {
return redirectOnLoginError(error);
}
RealmManager realmManager = new RealmManager(session);
RealmModel realm = getAdminstrationRealm(realmManager);
if (!realm.isEnabled()) {
RealmModel adminRealm = getAdminstrationRealm(realmManager);
if (!adminRealm.isEnabled()) {
logger.debug("realm not enabled");
return redirectOnLoginError("realm not enabled");
}
ApplicationModel adminConsole = realm.getApplicationNameMap().get(Constants.ADMIN_CONSOLE_APPLICATION);
ApplicationModel adminConsole = adminRealm.getApplicationNameMap().get(Constants.ADMIN_CONSOLE_APPLICATION);
UserModel adminConsoleUser = adminConsole.getApplicationUser();
if (!adminConsole.isEnabled() || !adminConsoleUser.isEnabled()) {
logger.debug("admin app not enabled");
@ -301,47 +340,8 @@ public class AdminService {
}
new JaxrsOAuthClient().checkStateCookie(uriInfo, headers);
JWSInput input = new JWSInput(code);
boolean verifiedCode = false;
try {
verifiedCode = RSAProvider.verify(input, realm.getPublicKey());
} catch (Exception ignored) {
logger.debug("Failed to verify signature", ignored);
}
if (!verifiedCode) {
logger.debug("unverified access code");
return redirectOnLoginError("invalid login data");
}
String key = input.readContentAsString();
AccessCodeEntry accessCode = tokenManager.pullAccessCode(key);
if (accessCode == null) {
logger.debug("bad access code");
return redirectOnLoginError("invalid login data");
}
if (accessCode.isExpired()) {
logger.debug("access code expired");
return redirectOnLoginError("invalid login data");
}
if (!accessCode.getToken().isActive()) {
logger.debug("access token expired");
return redirectOnLoginError("invalid login data");
}
if (!accessCode.getRealm().getId().equals(realm.getId())) {
logger.debug("bad realm");
return redirectOnLoginError("invalid login data");
}
if (!adminConsoleUser.getLoginName().equals(accessCode.getClient().getLoginName())) {
logger.debug("bad client");
return redirectOnLoginError("invalid login data");
}
RoleModel adminConsoleAdminRole = adminConsole.getRole(Constants.ADMIN_CONSOLE_ADMIN_ROLE);
if (!realm.hasRole(accessCode.getUser(), adminConsoleAdminRole)) {
logger.debug("not allowed");
return redirectOnLoginError("No permission to access console");
}
logger.debug("loginRedirect SUCCESS");
NewCookie cookie = authManager.createSaasIdentityCookie(realm, accessCode.getUser(), uriInfo);
NewCookie cookie = authManager.createCookie(adminRealm, adminConsoleUser, code, AdminService.saasCookiePath(uriInfo).build());
URI redirectUri = contextRoot(uriInfo).path(adminPath).build();
if (path != null) {
@ -349,7 +349,7 @@ public class AdminService {
}
return Response.status(302).cookie(cookie).location(redirectUri).build();
} finally {
authManager.expireCookie(AbstractOAuthClient.OAUTH_TOKEN_REQUEST_STATE, uriInfo.getAbsolutePath().getPath());
expireCookie();
}
}
@ -359,7 +359,7 @@ public class AdminService {
public Response logout() {
RealmManager realmManager = new RealmManager(session);
RealmModel realm = getAdminstrationRealm(realmManager);
authManager.expireSaasIdentityCookie(uriInfo);
expireCookie();
authManager.expireIdentityCookie(realm, uriInfo);
return Response.status(302).location(uriInfo.getBaseUriBuilder().path(AdminService.class).path(AdminService.class, "loginPage").build()).build();
@ -370,42 +370,7 @@ public class AdminService {
@NoCache
public void logoutCookie() {
logger.debug("*** logoutCookie");
authManager.expireSaasIdentityCookie(uriInfo);
}
@Path("login")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processLogin(final MultivaluedMap<String, String> formData) {
logger.debug("processLogin start");
RealmManager realmManager = new RealmManager(session);
RealmModel realm = getAdminstrationRealm(realmManager);
if (realm == null)
throw new NotFoundException();
ApplicationModel adminConsole = realm.getApplicationNameMap().get(Constants.ADMIN_CONSOLE_APPLICATION);
UserModel adminConsoleUser = adminConsole.getApplicationUser();
if (!realm.isEnabled()) {
throw new NotImplementedYetException();
}
String username = formData.getFirst("username");
UserModel user = realm.getUser(username);
AuthenticationStatus status = authManager.authenticateForm(realm, user, formData);
OAuthFlows oauth = Flows.oauth(realm, request, uriInfo, authManager, tokenManager);
switch (status) {
case SUCCESS:
NewCookie cookie = authManager.createSaasIdentityCookie(realm, user, uriInfo);
return Response.status(302).cookie(cookie).location(contextRoot(uriInfo).path(adminPath).build()).build();
case ACCOUNT_DISABLED:
return Flows.forms(realm, request, uriInfo).setError(Messages.ACCOUNT_DISABLED).setFormData(formData).createLogin();
case ACTIONS_REQUIRED:
return oauth.processAccessCode(null, "n", contextRoot(uriInfo).path(adminPath).build().toString(), adminConsoleUser, user);
default:
return Flows.forms(realm, request, uriInfo).setError(Messages.INVALID_USER).setFormData(formData).createLogin();
}
expireCookie();
}
protected RealmModel getAdminstrationRealm(RealmManager realmManager) {

View file

@ -2,10 +2,11 @@ package org.keycloak.services.resources.admin;
import org.jboss.resteasy.annotations.cache.NoCache;
import org.jboss.resteasy.logging.Logger;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.Auth;
import org.keycloak.services.managers.ModelToRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.TokenManager;
@ -20,7 +21,7 @@ import javax.ws.rs.core.Context;
*/
public class RealmAdminResource extends RoleContainerResource {
protected static final Logger logger = Logger.getLogger(RealmAdminResource.class);
protected UserModel admin;
protected Auth auth;
protected RealmModel realm;
private TokenManager tokenManager;
@ -30,15 +31,17 @@ public class RealmAdminResource extends RoleContainerResource {
@Context
protected KeycloakSession session;
public RealmAdminResource(UserModel admin, RealmModel realm, TokenManager tokenManager) {
public RealmAdminResource(Auth auth, RealmModel realm, TokenManager tokenManager) {
super(realm, realm);
this.admin = admin;
this.auth = auth;
this.realm = realm;
this.tokenManager = tokenManager;
}
@Path("applications")
public ApplicationsResource getApplications() {
auth.require(AdminRoles.getAdminApp(realm), AdminRoles.MANAGE_APPLICATIONS);
ApplicationsResource applicationsResource = new ApplicationsResource(realm);
resourceContext.initResource(applicationsResource);
return applicationsResource;
@ -46,6 +49,8 @@ public class RealmAdminResource extends RoleContainerResource {
@Path("oauth-clients")
public OAuthClientsResource getOAuthClients() {
auth.require(AdminRoles.getAdminApp(realm), AdminRoles.MANAGE_CLIENTS);
OAuthClientsResource oauth = new OAuthClientsResource(realm, session);
resourceContext.initResource(oauth);
return oauth;
@ -55,26 +60,42 @@ public class RealmAdminResource extends RoleContainerResource {
@NoCache
@Produces("application/json")
public RealmRepresentation getRealm() {
return ModelToRepresentation.toRepresentation(realm);
}
String realmAdminApp = AdminRoles.getAdminApp(realm);
if (auth.has(realmAdminApp, AdminRoles.MANAGE_REALM)) {
return ModelToRepresentation.toRepresentation(realm);
} else {
auth.requireOneOf(AdminRoles.getAdminApp(realm), AdminRoles.ALL_REALM_ROLES);
RealmRepresentation rep = new RealmRepresentation();
rep.setId(realm.getId());
rep.setRealm(realm.getName());
return rep;
}
}
@PUT
@Consumes("application/json")
public void updateRealm(final RealmRepresentation rep) {
auth.require(AdminRoles.getAdminApp(realm), AdminRoles.MANAGE_REALM);
logger.debug("updating realm: " + realm.getName());
new RealmManager(session).updateRealm(rep, realm);
}
@DELETE
public void deleteRealms() {
if (!session.removeRealm(realm.getId())) {
auth.require(AdminRoles.getAdminApp(realm), AdminRoles.MANAGE_REALM);
if (!new RealmManager(session).removeRealm(realm)) {
throw new NotFoundException();
}
}
@Path("users")
public UsersResource users() {
auth.require(AdminRoles.getAdminApp(realm), AdminRoles.MANAGE_USERS);
UsersResource users = new UsersResource(realm, tokenManager);
resourceContext.initResource(users);
return users;
@ -82,12 +103,11 @@ public class RealmAdminResource extends RoleContainerResource {
@Path("roles-by-id")
public RoleByIdResource rolesById() {
auth.require(AdminRoles.getAdminApp(realm), AdminRoles.MANAGE_REALM);
RoleByIdResource resource = new RoleByIdResource(realm);
resourceContext.initResource(resource);
return resource;
}
}

View file

@ -5,10 +5,13 @@ import org.jboss.resteasy.logging.Logger;
import org.jboss.resteasy.plugins.providers.multipart.InputPart;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;
import org.jboss.resteasy.util.GenericType;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.Auth;
import org.keycloak.services.managers.ModelToRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.TokenManager;
@ -35,11 +38,11 @@ import java.util.Map;
*/
public class RealmsAdminResource {
protected static final Logger logger = Logger.getLogger(RealmsAdminResource.class);
protected UserModel admin;
protected Auth auth;
protected TokenManager tokenManager;
public RealmsAdminResource(UserModel admin, TokenManager tokenManager) {
this.admin = admin;
public RealmsAdminResource(Auth auth, TokenManager tokenManager) {
this.auth = auth;
this.tokenManager = tokenManager;
}
@ -59,11 +62,19 @@ public class RealmsAdminResource {
@Produces("application/json")
public List<RealmRepresentation> getRealms() {
logger.debug(("getRealms()"));
RealmManager realmManager = new RealmManager(session);
List<RealmModel> realms = session.getRealms(admin);
List<RealmModel> realms = session.getRealms();
List<RealmRepresentation> reps = new ArrayList<RealmRepresentation>();
for (RealmModel realm : realms) {
reps.add(ModelToRepresentation.toRepresentation(realm));
String realmAdminApp = AdminRoles.getAdminApp(realm);
if (auth.has(realmAdminApp, AdminRoles.MANAGE_REALM)) {
reps.add(ModelToRepresentation.toRepresentation(realm));
} else if (auth.hasOneOf(realmAdminApp, AdminRoles.ALL_REALM_ROLES)) {
RealmRepresentation rep = new RealmRepresentation();
rep.setId(realm.getId());
rep.setRealm(realm.getName());
reps.add(rep);
}
}
return reps;
}
@ -79,13 +90,15 @@ public class RealmsAdminResource {
@POST
@Consumes("application/json")
public Response importRealm(@Context final UriInfo uriInfo, final RealmRepresentation rep) {
auth.require(AdminRoles.ADMIN);
logger.debug("importRealm: {0}", rep.getRealm());
RealmManager realmManager = new RealmManager(session);
if (realmManager.getRealmByName(rep.getRealm()) != null) {
return Flows.errors().exists("Realm " + rep.getRealm() + " already exists");
}
RealmModel realm = realmManager.importRealm(rep, admin);
RealmModel realm = realmManager.importRealm(rep);
URI location = realmUrl(uriInfo).build(realm.getName());
logger.debug("imported realm success, sending back: {0}", location.toString());
return Response.created(location).build();
@ -94,6 +107,8 @@ public class RealmsAdminResource {
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response uploadRealm(MultipartFormDataInput input) throws IOException {
auth.require(AdminRoles.ADMIN);
Map<String, List<InputPart>> uploadForm = input.getFormDataMap();
List<InputPart> inputParts = uploadForm.get("file");
@ -101,7 +116,7 @@ public class RealmsAdminResource {
for (InputPart inputPart : inputParts) {
inputPart.setMediaType(MediaType.APPLICATION_JSON_TYPE);
RealmRepresentation rep = inputPart.getBody(new GenericType<RealmRepresentation>(){});
realmManager.importRealm(rep, admin);
realmManager.importRealm(rep);
}
return Response.noContent().build();
}
@ -113,8 +128,11 @@ public class RealmsAdminResource {
RealmModel realm = realmManager.getRealmByName(name);
if (realm == null) throw new NotFoundException("{realm} = " + name);
RealmAdminResource adminResource = new RealmAdminResource(admin, realm, tokenManager);
auth.requireOneOf(AdminRoles.getAdminApp(realm), AdminRoles.ALL_REALM_ROLES);
RealmAdminResource adminResource = new RealmAdminResource(auth, realm, tokenManager);
resourceContext.initResource(adminResource);
return adminResource;
}
}

View file

@ -23,7 +23,6 @@ import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
@ -31,12 +30,10 @@ import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.ServerErrorException;
import javax.ws.rs.container.ResourceContext;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@ -431,7 +428,7 @@ public class UsersResource {
}
String redirect = Urls.accountBase(uriInfo.getBaseUri()).path("/").build(realm.getName()).toString();
String clientId = Constants.ACCOUNT_APPLICATION;
String clientId = Constants.ACCOUNT_MANAGEMENT_APP;
String state = null;
String scope = null;

View file

@ -228,7 +228,7 @@ public class KeycloakServer {
try {
RealmManager manager = new RealmManager(session);
RealmModel adminRealm = manager.getRealm(Constants.ADMIN_REALM);
RealmModel adminRealm = manager.getKeycloakAdminstrationRealm();
UserModel admin = adminRealm.getUser("admin");
admin.removeRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);

View file

@ -148,20 +148,6 @@ public class OAuthClient {
}
}
public UserRepresentation getProfile(String token) {
HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(baseUrl + "/realms/" + realm + "/account");
get.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token);
get.setHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.getMimeType());
try {
HttpResponse response = client.execute(get);
return JsonSerialization.readValue(response.getEntity().getContent(), UserRepresentation.class);
} catch (Exception e) {
throw new RuntimeException("Failed to retrieve profile", e);
}
}
public AccessToken verifyToken(String token) {
try {
return RSATokenVerifier.verifyToken(token, realmPublicKey, realm);

View file

@ -10,6 +10,7 @@ import org.json.JSONObject;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.models.AccountRoles;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
@ -31,8 +32,6 @@ import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import java.io.IOException;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@ -53,7 +52,7 @@ public class ProfileTest {
user.setAttribute("key1", "value1");
user.setAttribute("key2", "value2");
ApplicationModel accountApp = appRealm.getApplicationNameMap().get(org.keycloak.models.Constants.ACCOUNT_APPLICATION);
ApplicationModel accountApp = appRealm.getApplicationNameMap().get(org.keycloak.models.Constants.ACCOUNT_MANAGEMENT_APP);
UserModel user2 = appRealm.addUser("test-user-no-access@localhost");
user2.setEnabled(true);
@ -66,12 +65,12 @@ public class ProfileTest {
appRealm.updateCredential(user2, creds);
ApplicationModel app = appRealm.getApplicationNameMap().get("test-app");
appRealm.addScopeMapping(app.getApplicationUser(), accountApp.getRole(org.keycloak.models.Constants.ACCOUNT_PROFILE_ROLE));
appRealm.addScopeMapping(app.getApplicationUser(), accountApp.getRole(AccountRoles.VIEW_PROFILE));
app.getApplicationUser().addWebOrigin("http://localtest.me:8081");
UserModel thirdParty = appRealm.getUser("third-party");
appRealm.addScopeMapping(thirdParty, accountApp.getRole(org.keycloak.models.Constants.ACCOUNT_PROFILE_ROLE));
appRealm.addScopeMapping(thirdParty, accountApp.getRole(AccountRoles.VIEW_PROFILE));
}
});
@ -175,7 +174,7 @@ public class ProfileTest {
@Test
public void getProfileOAuthClient() throws Exception {
oauth.addScope(org.keycloak.models.Constants.ACCOUNT_APPLICATION, org.keycloak.models.Constants.ACCOUNT_PROFILE_ROLE);
oauth.addScope(org.keycloak.models.Constants.ACCOUNT_MANAGEMENT_APP, AccountRoles.VIEW_PROFILE);
oauth.clientId("third-party");
oauth.doLoginGrant("test-user@localhost", "password");
@ -192,7 +191,7 @@ public class ProfileTest {
@Test
public void getProfileOAuthClientNoScope() throws Exception {
oauth.addScope(org.keycloak.models.Constants.ACCOUNT_APPLICATION);
oauth.addScope(org.keycloak.models.Constants.ACCOUNT_MANAGEMENT_APP);
oauth.clientId("third-party");
oauth.doLoginGrant("test-user@localhost", "password");

View file

@ -90,7 +90,7 @@ public class CompositeImportRoleTest {
AccessToken token = oauth.verifyToken(response.getAccessToken());
Assert.assertEquals("APP_COMPOSITE_USER", oauth.getProfile(response.getAccessToken()).getUsername());
Assert.assertEquals(keycloakRule.getUser("Test", "APP_COMPOSITE_USER").getId(), token.getSubject());
Assert.assertEquals(1, token.getResourceAccess("APP_ROLE_APPLICATION").getRoles().size());
Assert.assertEquals(1, token.getRealmAccess().getRoles().size());
@ -115,7 +115,7 @@ public class CompositeImportRoleTest {
AccessToken token = oauth.verifyToken(response.getAccessToken());
Assert.assertEquals("REALM_APP_COMPOSITE_USER", oauth.getProfile(response.getAccessToken()).getUsername());
Assert.assertEquals(keycloakRule.getUser("Test", "REALM_APP_COMPOSITE_USER").getId(), token.getSubject());
Assert.assertEquals(1, token.getResourceAccess("APP_ROLE_APPLICATION").getRoles().size());
Assert.assertTrue(token.getResourceAccess("APP_ROLE_APPLICATION").isUserInRole("APP_ROLE_1"));
@ -139,7 +139,7 @@ public class CompositeImportRoleTest {
AccessToken token = oauth.verifyToken(response.getAccessToken());
Assert.assertEquals("REALM_COMPOSITE_1_USER", oauth.getProfile(response.getAccessToken()).getUsername());
Assert.assertEquals(keycloakRule.getUser("Test", "REALM_COMPOSITE_1_USER").getId(), token.getSubject());
Assert.assertEquals(2, token.getRealmAccess().getRoles().size());
Assert.assertTrue(token.getRealmAccess().isUserInRole("REALM_COMPOSITE_1"));
@ -162,7 +162,7 @@ public class CompositeImportRoleTest {
AccessToken token = oauth.verifyToken(response.getAccessToken());
Assert.assertEquals("REALM_COMPOSITE_1_USER", oauth.getProfile(response.getAccessToken()).getUsername());
Assert.assertEquals(keycloakRule.getUser("Test", "REALM_COMPOSITE_1_USER").getId(), token.getSubject());
Assert.assertEquals(1, token.getRealmAccess().getRoles().size());
Assert.assertTrue(token.getRealmAccess().isUserInRole("REALM_ROLE_1"));
@ -184,7 +184,7 @@ public class CompositeImportRoleTest {
AccessToken token = oauth.verifyToken(response.getAccessToken());
Assert.assertEquals("REALM_ROLE_1_USER", oauth.getProfile(response.getAccessToken()).getUsername());
Assert.assertEquals(keycloakRule.getUser("Test", "REALM_ROLE_1_USER").getId(), token.getSubject());
Assert.assertEquals(1, token.getRealmAccess().getRoles().size());
Assert.assertTrue(token.getRealmAccess().isUserInRole("REALM_ROLE_1"));

View file

@ -168,7 +168,7 @@ public class CompositeRoleTest {
AccessToken token = oauth.verifyToken(response.getAccessToken());
Assert.assertEquals("APP_COMPOSITE_USER", oauth.getProfile(response.getAccessToken()).getUsername());
Assert.assertEquals(keycloakRule.getUser("Test", "APP_COMPOSITE_USER").getId(), token.getSubject());
Assert.assertEquals(1, token.getResourceAccess("APP_ROLE_APPLICATION").getRoles().size());
Assert.assertEquals(1, token.getRealmAccess().getRoles().size());
@ -193,7 +193,7 @@ public class CompositeRoleTest {
AccessToken token = oauth.verifyToken(response.getAccessToken());
Assert.assertEquals("REALM_APP_COMPOSITE_USER", oauth.getProfile(response.getAccessToken()).getUsername());
Assert.assertEquals(keycloakRule.getUser("Test", "REALM_APP_COMPOSITE_USER").getId(), token.getSubject());
Assert.assertEquals(1, token.getResourceAccess("APP_ROLE_APPLICATION").getRoles().size());
Assert.assertTrue(token.getResourceAccess("APP_ROLE_APPLICATION").isUserInRole("APP_ROLE_1"));
@ -217,7 +217,7 @@ public class CompositeRoleTest {
AccessToken token = oauth.verifyToken(response.getAccessToken());
Assert.assertEquals("REALM_COMPOSITE_1_USER", oauth.getProfile(response.getAccessToken()).getUsername());
Assert.assertEquals(keycloakRule.getUser("Test", "REALM_COMPOSITE_1_USER").getId(), token.getSubject());
Assert.assertEquals(2, token.getRealmAccess().getRoles().size());
Assert.assertTrue(token.getRealmAccess().isUserInRole("REALM_COMPOSITE_1"));
@ -240,7 +240,7 @@ public class CompositeRoleTest {
AccessToken token = oauth.verifyToken(response.getAccessToken());
Assert.assertEquals("REALM_COMPOSITE_1_USER", oauth.getProfile(response.getAccessToken()).getUsername());
Assert.assertEquals(keycloakRule.getUser("Test", "REALM_COMPOSITE_1_USER").getId(), token.getSubject());
Assert.assertEquals(1, token.getRealmAccess().getRoles().size());
Assert.assertTrue(token.getRealmAccess().isUserInRole("REALM_ROLE_1"));
@ -262,7 +262,7 @@ public class CompositeRoleTest {
AccessToken token = oauth.verifyToken(response.getAccessToken());
Assert.assertEquals("REALM_ROLE_1_USER", oauth.getProfile(response.getAccessToken()).getUsername());
Assert.assertEquals(keycloakRule.getUser("Test", "REALM_ROLE_1_USER").getId(), token.getSubject());
Assert.assertEquals(1, token.getRealmAccess().getRoles().size());
Assert.assertTrue(token.getRealmAccess().isUserInRole("REALM_ROLE_1"));

View file

@ -21,7 +21,6 @@
*/
package org.keycloak.testsuite.forms;
import org.apache.http.HttpResponse;
import org.junit.*;
import org.keycloak.models.*;
import org.keycloak.models.utils.TimeBasedOTP;
@ -35,10 +34,6 @@ import org.keycloak.testsuite.rule.KeycloakRule.KeycloakSetup;
import org.keycloak.testsuite.rule.WebResource;
import org.keycloak.testsuite.rule.WebRule;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import java.util.List;
import static org.junit.Assert.assertEquals;
@ -53,7 +48,7 @@ public class AccountTest {
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
UserModel user = appRealm.getUser("test-user@localhost");
ApplicationModel accountApp = appRealm.getApplicationNameMap().get(org.keycloak.models.Constants.ACCOUNT_APPLICATION);
ApplicationModel accountApp = appRealm.getApplicationNameMap().get(org.keycloak.models.Constants.ACCOUNT_MANAGEMENT_APP);
UserModel user2 = appRealm.addUser("test-user-no-access@localhost");
user2.setEnabled(true);

View file

@ -25,8 +25,10 @@ import org.junit.Assert;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testsuite.OAuthClient;
import org.keycloak.testsuite.OAuthClient.AccessTokenResponse;
import org.keycloak.testsuite.pages.LoginPage;
@ -70,11 +72,8 @@ public class AccessTokenTest {
AccessToken token = oauth.verifyToken(response.getAccessToken());
UserRepresentation user = oauth.getProfile(response.getAccessToken());
Assert.assertEquals(user.getId(), token.getSubject());
Assert.assertEquals(keycloakRule.getUser("test", "test-user@localhost").getId(), token.getSubject());
Assert.assertNotEquals("test-user@localhost", token.getSubject());
Assert.assertEquals("test-user@localhost", user.getUsername());
Assert.assertEquals(1, token.getRealmAccess().getRoles().size());
Assert.assertTrue(token.getRealmAccess().isUserInRole("user"));

View file

@ -7,6 +7,8 @@ import org.keycloak.models.Constants;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.managers.ModelToRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.testutils.KeycloakServer;
import org.keycloak.util.JsonSerialization;
@ -31,6 +33,24 @@ public abstract class AbstractKeycloakRule extends ExternalResource {
setupKeycloak();
}
public UserRepresentation getUser(String realm, String name) {
KeycloakSession session = server.getKeycloakSessionFactory().createSession();
try {
return ModelToRepresentation.toRepresentation(session.getRealmByName(realm).getUser(name));
} finally {
session.close();
}
}
public UserRepresentation getUserById(String realm, String id) {
KeycloakSession session = server.getKeycloakSessionFactory().createSession();
try {
return ModelToRepresentation.toRepresentation(session.getRealmByName(realm).getUserById(id));
} finally {
session.close();
}
}
protected void setupKeycloak() {
KeycloakSession session = server.getKeycloakSessionFactory().createSession();
session.getTransaction().begin();

View file

@ -26,6 +26,9 @@ import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.keycloak.models.AccountRoles;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.Constants;
import org.keycloak.models.RealmModel;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.idm.UserRepresentation;
@ -107,7 +110,7 @@ public class SocialLoginTest {
AccessToken token = oauth.verifyToken(response.getAccessToken());
Assert.assertEquals(36, token.getSubject().length());
UserRepresentation profile = oauth.getProfile(response.getAccessToken());
UserRepresentation profile = keycloakRule.getUserById("test", token.getSubject());
Assert.assertEquals(36, profile.getUsername().length());
Assert.assertEquals("Bob", profile.getFirstName());
@ -146,7 +149,9 @@ public class SocialLoginTest {
Assert.assertEquals(RequestType.AUTH_RESPONSE, appPage.getRequestType());
AccessTokenResponse response = oauth.doAccessTokenRequest(oauth.getCurrentQuery().get("code"), "password");
UserRepresentation profile = oauth.getProfile(response.getAccessToken());
AccessToken token = oauth.verifyToken(response.getAccessToken());
UserRepresentation profile = keycloakRule.getUserById("test", token.getSubject());
Assert.assertEquals("Dummy", profile.getFirstName());
Assert.assertEquals("User", profile.getLastName());