commit
97f3a3feef
18 changed files with 453 additions and 218 deletions
|
@ -34,6 +34,24 @@
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<h2><span>Realm Roles</span></h2>
|
||||||
|
<button type="submit" data-ng-click="loadRoles()">load Roles</button>
|
||||||
|
<button type="submit" data-ng-click="addRole()">Add Role</button>
|
||||||
|
<button type="submit" data-ng-click="deleteRole()">Delete Role</button>
|
||||||
|
<table class="table" data-ng-show="roles.length > 0">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Role Listing</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr data-ng-repeat="r in roles">
|
||||||
|
<td>{{r.name}}</a></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -29,12 +29,32 @@ angular.element(document).ready(function ($http) {
|
||||||
|
|
||||||
module.controller('GlobalCtrl', function($scope, $http) {
|
module.controller('GlobalCtrl', function($scope, $http) {
|
||||||
$scope.products = [];
|
$scope.products = [];
|
||||||
|
$scope.roles = [];
|
||||||
$scope.reloadData = function() {
|
$scope.reloadData = function() {
|
||||||
$http.get("http://localhost-db:8080/database/products").success(function(data) {
|
$http.get("http://localhost-db:8080/database/products").success(function(data) {
|
||||||
$scope.products = angular.fromJson(data);
|
$scope.products = angular.fromJson(data);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
$scope.loadRoles = function() {
|
||||||
|
$http.query("http://localhost-auth:8080/auth/admin/realms/" + keycloakAuth.realm + "/roles").success(function(data) {
|
||||||
|
$scope.roles = angular.fromJson(data);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
$scope.addRole = function() {
|
||||||
|
$http.post("http://localhost-auth:8080/auth/admin/realms/" + keycloakAuth.realm + "/roles", {name: 'stuff'}).success(function() {
|
||||||
|
$scope.loadRoles();
|
||||||
|
});
|
||||||
|
|
||||||
|
};
|
||||||
|
$scope.deleteRole = function() {
|
||||||
|
$http.delete("http://localhost-auth:8080/auth/admin/realms/" + keycloakAuth.realm + "/roles/stuff").success(function() {
|
||||||
|
$scope.loadRoles();
|
||||||
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
$scope.logout = logout;
|
$scope.logout = logout;
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"realm" : "cors-realm",
|
"realm" : "cors",
|
||||||
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
"realm-public-key" : "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
|
||||||
"auth-server-url" : "http://localhost-auth:8080/auth",
|
"auth-server-url" : "http://localhost-auth:8080/auth",
|
||||||
"ssl-not-required" : true,
|
"ssl-not-required" : true,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"realm": "cors-realm",
|
"realm": "cors",
|
||||||
"enabled": true,
|
"enabled": true,
|
||||||
"accessTokenLifespan": 3000,
|
"accessTokenLifespan": 3000,
|
||||||
"accessCodeLifespan": 10,
|
"accessCodeLifespan": 10,
|
||||||
|
@ -57,5 +57,22 @@
|
||||||
"http://localhost:8080"
|
"http://localhost:8080"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
"applicationRoleMappings": {
|
||||||
|
"realm-management": [
|
||||||
|
{
|
||||||
|
"username": "bburke@redhat.com",
|
||||||
|
"roles": ["realm-admin"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"applicationScopeMappings": {
|
||||||
|
"realm-management": [
|
||||||
|
{
|
||||||
|
"client": "angular-product",
|
||||||
|
"roles": ["realm-admin"]
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -7,17 +7,16 @@ module.controller('GlobalCtrl', function($scope, $http, Auth, WhoAmI, Current, $
|
||||||
$scope.logout = logout;
|
$scope.logout = logout;
|
||||||
|
|
||||||
$scope.auth = Auth;
|
$scope.auth = Auth;
|
||||||
|
WhoAmI.get(function(data) {
|
||||||
WhoAmI.get(function(user) {
|
Auth.user = data;
|
||||||
Auth.user = user;
|
Auth.loggedIn = true;
|
||||||
});
|
|
||||||
|
|
||||||
function getAccess(role) {
|
function getAccess(role) {
|
||||||
if (!Current.realm) {
|
if (!Current.realm) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
var realmAccess = Auth.user && Auth.user['realm_access'];
|
var realmAccess = Auth.user['realm_access'];
|
||||||
if (realmAccess) {
|
if (realmAccess) {
|
||||||
realmAccess = realmAccess[Current.realm.realm];
|
realmAccess = realmAccess[Current.realm.realm];
|
||||||
if (realmAccess) {
|
if (realmAccess) {
|
||||||
|
@ -28,7 +27,7 @@ module.controller('GlobalCtrl', function($scope, $http, Auth, WhoAmI, Current, $
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.access = {
|
$scope.access = {
|
||||||
createRealm: Auth.user && Auth.user.createRealm,
|
createRealm: data.createRealm,
|
||||||
|
|
||||||
get viewRealm() {
|
get viewRealm() {
|
||||||
return getAccess('view-realm') || this.manageRealm;
|
return getAccess('view-realm') || this.manageRealm;
|
||||||
|
@ -70,6 +69,7 @@ module.controller('GlobalCtrl', function($scope, $http, Auth, WhoAmI, Current, $
|
||||||
return getAccess('manage-audit');
|
return getAccess('manage-audit');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$scope.$watch(function() {
|
$scope.$watch(function() {
|
||||||
return $location.path();
|
return $location.path();
|
||||||
|
@ -114,7 +114,7 @@ module.controller('RealmDropdownCtrl', function($scope, Realm, Current, Auth, $l
|
||||||
|
|
||||||
$scope.showNav = function() {
|
$scope.showNav = function() {
|
||||||
var show = Current.realms.length > 0;
|
var show = Current.realms.length > 0;
|
||||||
return Auth.user && show;
|
return Auth.loggedIn && show;
|
||||||
}
|
}
|
||||||
$scope.refresh = function() {
|
$scope.refresh = function() {
|
||||||
Current.refresh();
|
Current.refresh();
|
||||||
|
|
|
@ -76,21 +76,21 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" data-ng-show="!application.bearerOnly">
|
<div class="form-group" data-ng-show="!application.bearerOnly && !create">
|
||||||
<label class="col-sm-2 control-label" for="baseUrl">Base URL</label>
|
<label class="col-sm-2 control-label" for="baseUrl">Base URL</label>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<input class="form-control" type="text" name="baseUrl" id="baseUrl"
|
<input class="form-control" type="text" name="baseUrl" id="baseUrl"
|
||||||
data-ng-model="application.baseUrl">
|
data-ng-model="application.baseUrl">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group" data-ng-hide="create">
|
||||||
<label class="col-sm-2 control-label" for="adminUrl">Admin URL</label>
|
<label class="col-sm-2 control-label" for="adminUrl">Admin URL</label>
|
||||||
<div class="col-sm-4">
|
<div class="col-sm-4">
|
||||||
<input class="form-control" type="text" name="adminUrl" id="adminUrl"
|
<input class="form-control" type="text" name="adminUrl" id="adminUrl"
|
||||||
data-ng-model="application.adminUrl">
|
data-ng-model="application.adminUrl">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group" data-ng-show="!application.bearerOnly">
|
<div class="form-group" data-ng-show="!application.bearerOnly && !create">
|
||||||
<label class="col-sm-2 control-label" for="newWebOrigin">Web Origin</label>
|
<label class="col-sm-2 control-label" for="newWebOrigin">Web Origin</label>
|
||||||
<div class="col-sm-4 multiple" ng-repeat="webOrigin in application.webOrigins">
|
<div class="col-sm-4 multiple" ng-repeat="webOrigin in application.webOrigins">
|
||||||
<div class="input-group kc-item-deletable">
|
<div class="input-group kc-item-deletable">
|
||||||
|
|
|
@ -3,7 +3,6 @@ package org.keycloak.services.managers;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.UnauthorizedException;
|
import org.jboss.resteasy.spi.UnauthorizedException;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.UserModel;
|
|
||||||
import org.keycloak.provider.ProviderSession;
|
import org.keycloak.provider.ProviderSession;
|
||||||
|
|
||||||
import javax.ws.rs.core.Cookie;
|
import javax.ws.rs.core.Cookie;
|
||||||
|
@ -21,18 +20,16 @@ public class AppAuthManager extends AuthenticationManager {
|
||||||
super(providerSession);
|
super(providerSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthResult authenticateRequest(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
|
@Override
|
||||||
AuthResult authResult = authenticateIdentityCookie(realm, uriInfo, headers);
|
public AuthResult authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
|
||||||
if (authResult != null) {
|
AuthResult authResult = super.authenticateIdentityCookie(realm, uriInfo, headers);
|
||||||
|
if (authResult == null) return null;
|
||||||
Cookie remember = headers.getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
|
Cookie remember = headers.getCookies().get(AuthenticationManager.KEYCLOAK_REMEMBER_ME);
|
||||||
boolean rememberMe = remember != null;
|
boolean rememberMe = remember != null;
|
||||||
// refresh the cookies!
|
// refresh the cookies!
|
||||||
createLoginCookie(realm, authResult.getUser(), authResult.getSession(), uriInfo, rememberMe);
|
createLoginCookie(realm, authResult.getUser(), authResult.getSession(), uriInfo, rememberMe);
|
||||||
if (rememberMe) createRememberMeCookie(realm, uriInfo);
|
if (rememberMe) createRememberMeCookie(realm, uriInfo);
|
||||||
return authResult;
|
return authResult;
|
||||||
} else {
|
|
||||||
return authenticateBearerToken(realm, uriInfo, headers);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String extractAuthorizationHeaderToken(HttpHeaders headers) {
|
public String extractAuthorizationHeaderToken(HttpHeaders headers) {
|
||||||
|
|
|
@ -80,6 +80,26 @@ public class ApplicationManager {
|
||||||
logger.debugv("Application: {0} webOrigin: {1}", resourceRep.getName(), webOrigin);
|
logger.debugv("Application: {0} webOrigin: {1}", resourceRep.getName(), webOrigin);
|
||||||
applicationModel.addWebOrigin(webOrigin);
|
applicationModel.addWebOrigin(webOrigin);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// add origins from redirect uris
|
||||||
|
if (resourceRep.getRedirectUris() != null) {
|
||||||
|
Set<String> origins = new HashSet<String>();
|
||||||
|
for (String redirectUri : resourceRep.getRedirectUris()) {
|
||||||
|
logger.info("add redirectUri to origin: " + redirectUri);
|
||||||
|
if (redirectUri.startsWith("http:")) {
|
||||||
|
URI uri = URI.create(redirectUri);
|
||||||
|
String origin = uri.getScheme() + "://" + uri.getHost();
|
||||||
|
if (uri.getPort() != -1) {
|
||||||
|
origin += ":" + uri.getPort();
|
||||||
|
}
|
||||||
|
logger.debugv("adding default application origin: {0}" , origin);
|
||||||
|
origins.add(origin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (origins.size() > 0) {
|
||||||
|
applicationModel.setWebOrigins(origins);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resourceRep.getDefaultRoles() != null) {
|
if (resourceRep.getDefaultRoles() != null) {
|
||||||
|
|
|
@ -17,19 +17,10 @@ public class Auth {
|
||||||
private final UserModel user;
|
private final UserModel user;
|
||||||
private final ClientModel client;
|
private final ClientModel client;
|
||||||
|
|
||||||
public Auth(RealmModel realm, UserModel user, ClientModel client) {
|
public Auth(RealmModel realm, AccessToken token, UserModel user, ClientModel client, boolean cookie) {
|
||||||
this.cookie = true;
|
this.cookie = cookie;
|
||||||
this.realm = realm;
|
|
||||||
this.token = null;
|
|
||||||
|
|
||||||
this.user = user;
|
|
||||||
this.client = client;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Auth(AccessToken token, UserModel user, ClientModel client) {
|
|
||||||
this.cookie = false;
|
|
||||||
this.token = token;
|
this.token = token;
|
||||||
this.realm = null;
|
this.realm = realm;
|
||||||
|
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.client = client;
|
this.client = client;
|
||||||
|
|
|
@ -221,7 +221,7 @@ public class AuthenticationManager {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new AuthResult(user, session);
|
return new AuthResult(user, session, token);
|
||||||
} catch (VerificationException e) {
|
} catch (VerificationException e) {
|
||||||
logger.info("Failed to verify identity token", e);
|
logger.info("Failed to verify identity token", e);
|
||||||
}
|
}
|
||||||
|
@ -361,10 +361,12 @@ public class AuthenticationManager {
|
||||||
public class AuthResult {
|
public class AuthResult {
|
||||||
private final UserModel user;
|
private final UserModel user;
|
||||||
private final UserSessionModel session;
|
private final UserSessionModel session;
|
||||||
|
private final AccessToken token;
|
||||||
|
|
||||||
public AuthResult(UserModel user, UserSessionModel session) {
|
public AuthResult(UserModel user, UserSessionModel session, AccessToken token) {
|
||||||
this.user = user;
|
this.user = user;
|
||||||
this.session = session;
|
this.session = session;
|
||||||
|
this.token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public UserSessionModel getSession() {
|
public UserSessionModel getSession() {
|
||||||
|
@ -374,6 +376,10 @@ public class AuthenticationManager {
|
||||||
public UserModel getUser() {
|
public UserModel getUser() {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AccessToken getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,7 +103,8 @@ public class RealmManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setupAdminConsole(RealmModel realm) {
|
protected void setupAdminConsole(RealmModel realm) {
|
||||||
ApplicationModel adminConsole = new ApplicationManager(this).createApplication(realm, Constants.ADMIN_CONSOLE_APPLICATION);
|
ApplicationModel adminConsole = realm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
|
||||||
|
if (adminConsole == null) adminConsole = new ApplicationManager(this).createApplication(realm, Constants.ADMIN_CONSOLE_APPLICATION);
|
||||||
String baseUrl = contextPath + "/admin/" + realm.getName() + "/console";
|
String baseUrl = contextPath + "/admin/" + realm.getName() + "/console";
|
||||||
adminConsole.setBaseUrl(baseUrl + "/index.html");
|
adminConsole.setBaseUrl(baseUrl + "/index.html");
|
||||||
adminConsole.setEnabled(true);
|
adminConsole.setEnabled(true);
|
||||||
|
@ -113,12 +114,10 @@ public class RealmManager {
|
||||||
RoleModel adminRole;
|
RoleModel adminRole;
|
||||||
if (realm.getName().equals(Config.getAdminRealm())) {
|
if (realm.getName().equals(Config.getAdminRealm())) {
|
||||||
adminRole = realm.getRole(AdminRoles.ADMIN);
|
adminRole = realm.getRole(AdminRoles.ADMIN);
|
||||||
} else {
|
|
||||||
ApplicationModel realmApp = realm.getApplicationByName(getRealmAdminApplicationName(realm));
|
|
||||||
adminRole = realmApp.getRole(AdminRoles.REALM_ADMIN);
|
|
||||||
|
|
||||||
}
|
|
||||||
realm.addScopeMapping(adminConsole, adminRole);
|
realm.addScopeMapping(adminConsole, adminRole);
|
||||||
|
} else {
|
||||||
|
// security roles are defined in application for the realm.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMasterRealmAdminApplicationName(RealmModel realm) {
|
public String getMasterRealmAdminApplicationName(RealmModel realm) {
|
||||||
|
@ -265,7 +264,11 @@ public class RealmManager {
|
||||||
|
|
||||||
ApplicationManager applicationManager = new ApplicationManager(new RealmManager(identitySession));
|
ApplicationManager applicationManager = new ApplicationManager(new RealmManager(identitySession));
|
||||||
|
|
||||||
ApplicationModel realmAdminApp = applicationManager.createApplication(realm, getRealmAdminApplicationName(realm));
|
String realmAdminApplicationName = getRealmAdminApplicationName(realm);
|
||||||
|
ApplicationModel realmAdminApp = realm.getApplicationByName(realmAdminApplicationName);
|
||||||
|
if (realmAdminApp == null) {
|
||||||
|
realmAdminApp = applicationManager.createApplication(realm, realmAdminApplicationName);
|
||||||
|
}
|
||||||
RoleModel adminRole = realmAdminApp.addRole(AdminRoles.REALM_ADMIN);
|
RoleModel adminRole = realmAdminApp.addRole(AdminRoles.REALM_ADMIN);
|
||||||
realmAdminApp.setBearerOnly(true);
|
realmAdminApp.setBearerOnly(true);
|
||||||
|
|
||||||
|
|
|
@ -145,9 +145,16 @@ public class AccountService {
|
||||||
account = providers.getProvider(AccountProvider.class).setRealm(realm).setUriInfo(uriInfo);
|
account = providers.getProvider(AccountProvider.class).setRealm(realm).setUriInfo(uriInfo);
|
||||||
|
|
||||||
boolean passwordUpdateSupported = false;
|
boolean passwordUpdateSupported = false;
|
||||||
AuthenticationManager.AuthResult authResult = authManager.authenticateRequest(realm, uriInfo, headers);
|
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(realm, uriInfo, headers);
|
||||||
|
if (authResult != null) {
|
||||||
|
auth = new Auth(realm, authResult.getToken(), authResult.getUser(), application, true);
|
||||||
|
} else {
|
||||||
|
authResult = authManager.authenticateBearerToken(realm, uriInfo, headers);
|
||||||
|
if (authResult != null) {
|
||||||
|
auth = new Auth(realm, authResult.getToken(), authResult.getUser(), application, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (authResult != null) {
|
if (authResult != null) {
|
||||||
auth = new Auth(realm, authResult.getUser(), application);
|
|
||||||
if (authResult.getSession() != null) {
|
if (authResult.getSession() != null) {
|
||||||
authResult.getSession().associateClient(application);
|
authResult.getSession().associateClient(application);
|
||||||
}
|
}
|
||||||
|
@ -208,7 +215,7 @@ public class AccountService {
|
||||||
} else if (types.contains(MediaType.APPLICATION_JSON_TYPE)) {
|
} else if (types.contains(MediaType.APPLICATION_JSON_TYPE)) {
|
||||||
requireOneOf(AccountRoles.MANAGE_ACCOUNT, AccountRoles.VIEW_PROFILE);
|
requireOneOf(AccountRoles.MANAGE_ACCOUNT, AccountRoles.VIEW_PROFILE);
|
||||||
|
|
||||||
return Cors.add(request, Response.ok(ModelToRepresentation.toRepresentation(auth.getUser()))).auth().allowedOrigins(auth.getClient()).build();
|
return Cors.add(request, Response.ok(ModelToRepresentation.toRepresentation(auth.getUser()))).auth().allowedOrigins(auth.getToken()).build();
|
||||||
} else {
|
} else {
|
||||||
return Response.notAcceptable(Variant.VariantListBuilder.newInstance().mediaTypes(MediaType.TEXT_HTML_TYPE, MediaType.APPLICATION_JSON_TYPE).build()).build();
|
return Response.notAcceptable(Variant.VariantListBuilder.newInstance().mediaTypes(MediaType.TEXT_HTML_TYPE, MediaType.APPLICATION_JSON_TYPE).build()).build();
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,14 +8,18 @@ import java.util.concurrent.TimeUnit;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import javax.ws.rs.core.Response.ResponseBuilder;
|
import javax.ws.rs.core.Response.ResponseBuilder;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
import org.jboss.resteasy.spi.HttpRequest;
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
|
import org.jboss.resteasy.spi.HttpResponse;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.util.CollectionUtil;
|
import org.keycloak.util.CollectionUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
*/
|
*/
|
||||||
public class Cors {
|
public class Cors {
|
||||||
|
protected static final Logger logger = Logger.getLogger(Cors.class);
|
||||||
|
|
||||||
public static final long DEFAULT_MAX_AGE = TimeUnit.HOURS.toSeconds(1);
|
public static final long DEFAULT_MAX_AGE = TimeUnit.HOURS.toSeconds(1);
|
||||||
public static final String DEFAULT_ALLOW_METHODS = "GET, HEAD, OPTIONS";
|
public static final String DEFAULT_ALLOW_METHODS = "GET, HEAD, OPTIONS";
|
||||||
|
@ -33,7 +37,7 @@ public class Cors {
|
||||||
|
|
||||||
|
|
||||||
private HttpRequest request;
|
private HttpRequest request;
|
||||||
private ResponseBuilder response;
|
private ResponseBuilder builder;
|
||||||
private Set<String> allowedOrigins;
|
private Set<String> allowedOrigins;
|
||||||
private Set<String> allowedMethods;
|
private Set<String> allowedMethods;
|
||||||
private Set<String> exposedHeaders;
|
private Set<String> exposedHeaders;
|
||||||
|
@ -43,13 +47,21 @@ public class Cors {
|
||||||
|
|
||||||
public Cors(HttpRequest request, ResponseBuilder response) {
|
public Cors(HttpRequest request, ResponseBuilder response) {
|
||||||
this.request = request;
|
this.request = request;
|
||||||
this.response = response;
|
this.builder = response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Cors(HttpRequest request) {
|
||||||
|
this.request = request;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Cors add(HttpRequest request, ResponseBuilder response) {
|
public static Cors add(HttpRequest request, ResponseBuilder response) {
|
||||||
return new Cors(request, response);
|
return new Cors(request, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Cors add(HttpRequest request) {
|
||||||
|
return new Cors(request);
|
||||||
|
}
|
||||||
|
|
||||||
public Cors preflight() {
|
public Cors preflight() {
|
||||||
preflight = true;
|
preflight = true;
|
||||||
return this;
|
return this;
|
||||||
|
@ -67,6 +79,13 @@ public class Cors {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Cors allowedOrigins(AccessToken token) {
|
||||||
|
if (token != null) {
|
||||||
|
allowedOrigins = token.getAllowedOrigins();
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public Cors allowedMethods(String... allowedMethods) {
|
public Cors allowedMethods(String... allowedMethods) {
|
||||||
this.allowedMethods = new HashSet<String>(Arrays.asList(allowedMethods));
|
this.allowedMethods = new HashSet<String>(Arrays.asList(allowedMethods));
|
||||||
return this;
|
return this;
|
||||||
|
@ -80,35 +99,69 @@ public class Cors {
|
||||||
public Response build() {
|
public Response build() {
|
||||||
String origin = request.getHttpHeaders().getRequestHeaders().getFirst(ORIGIN_HEADER);
|
String origin = request.getHttpHeaders().getRequestHeaders().getFirst(ORIGIN_HEADER);
|
||||||
if (origin == null) {
|
if (origin == null) {
|
||||||
return response.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!preflight && (allowedOrigins == null || !allowedOrigins.contains(origin))) {
|
if (!preflight && (allowedOrigins == null || !allowedOrigins.contains(origin))) {
|
||||||
return response.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
response.header(ACCESS_CONTROL_ALLOW_ORIGIN, origin);
|
builder.header(ACCESS_CONTROL_ALLOW_ORIGIN, origin);
|
||||||
|
|
||||||
if (allowedMethods != null) {
|
if (allowedMethods != null) {
|
||||||
response.header(ACCESS_CONTROL_ALLOW_METHODS, CollectionUtil.join(allowedMethods));
|
builder.header(ACCESS_CONTROL_ALLOW_METHODS, CollectionUtil.join(allowedMethods));
|
||||||
} else {
|
} else {
|
||||||
response.header(ACCESS_CONTROL_ALLOW_METHODS, DEFAULT_ALLOW_METHODS);
|
builder.header(ACCESS_CONTROL_ALLOW_METHODS, DEFAULT_ALLOW_METHODS);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exposedHeaders != null) {
|
if (exposedHeaders != null) {
|
||||||
response.header(ACCESS_CONTROL_EXPOSE_HEADERS, CollectionUtil.join(exposedHeaders));
|
builder.header(ACCESS_CONTROL_EXPOSE_HEADERS, CollectionUtil.join(exposedHeaders));
|
||||||
}
|
}
|
||||||
|
|
||||||
response.header(ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.toString(auth));
|
builder.header(ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.toString(auth));
|
||||||
if (auth) {
|
if (auth) {
|
||||||
response.header(ACCESS_CONTROL_ALLOW_HEADERS, String.format("%s, %s", DEFAULT_ALLOW_HEADERS, AUTHORIZATION_HEADER));
|
builder.header(ACCESS_CONTROL_ALLOW_HEADERS, String.format("%s, %s", DEFAULT_ALLOW_HEADERS, AUTHORIZATION_HEADER));
|
||||||
} else {
|
} else {
|
||||||
response.header(ACCESS_CONTROL_ALLOW_HEADERS, DEFAULT_ALLOW_HEADERS);
|
builder.header(ACCESS_CONTROL_ALLOW_HEADERS, DEFAULT_ALLOW_HEADERS);
|
||||||
}
|
}
|
||||||
|
|
||||||
response.header(ACCESS_CONTROL_MAX_AGE, DEFAULT_MAX_AGE);
|
builder.header(ACCESS_CONTROL_MAX_AGE, DEFAULT_MAX_AGE);
|
||||||
|
|
||||||
return response.build();
|
return builder.build();
|
||||||
|
}
|
||||||
|
public void build(HttpResponse response) {
|
||||||
|
logger.info("build CORS");
|
||||||
|
String origin = request.getHttpHeaders().getRequestHeaders().getFirst(ORIGIN_HEADER);
|
||||||
|
if (origin == null) {
|
||||||
|
logger.info("No origin returning");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!preflight && (allowedOrigins == null || !allowedOrigins.contains(origin))) {
|
||||||
|
logger.info("!preflight and no origin");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
logger.info("build CORS headers and return");
|
||||||
|
response.getOutputHeaders().add(ACCESS_CONTROL_ALLOW_ORIGIN, origin);
|
||||||
|
|
||||||
|
if (allowedMethods != null) {
|
||||||
|
response.getOutputHeaders().add(ACCESS_CONTROL_ALLOW_METHODS, CollectionUtil.join(allowedMethods));
|
||||||
|
} else {
|
||||||
|
response.getOutputHeaders().add(ACCESS_CONTROL_ALLOW_METHODS, DEFAULT_ALLOW_METHODS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exposedHeaders != null) {
|
||||||
|
response.getOutputHeaders().add(ACCESS_CONTROL_EXPOSE_HEADERS, CollectionUtil.join(exposedHeaders));
|
||||||
|
}
|
||||||
|
|
||||||
|
response.getOutputHeaders().add(ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.toString(auth));
|
||||||
|
if (auth) {
|
||||||
|
response.getOutputHeaders().add(ACCESS_CONTROL_ALLOW_HEADERS, String.format("%s, %s", DEFAULT_ALLOW_HEADERS, AUTHORIZATION_HEADER));
|
||||||
|
} else {
|
||||||
|
response.getOutputHeaders().add(ACCESS_CONTROL_ALLOW_HEADERS, DEFAULT_ALLOW_HEADERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
response.getOutputHeaders().add(ACCESS_CONTROL_MAX_AGE, DEFAULT_MAX_AGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -534,7 +534,7 @@ public class TokenService {
|
||||||
@OPTIONS
|
@OPTIONS
|
||||||
@Produces("application/json")
|
@Produces("application/json")
|
||||||
public Response accessCodeToTokenPreflight() {
|
public Response accessCodeToTokenPreflight() {
|
||||||
logger.info("cors request from: " + request.getHttpHeaders().getRequestHeaders().getFirst("Origin"));
|
logger.debugv("cors request from: {0}" , request.getHttpHeaders().getRequestHeaders().getFirst("Origin"));
|
||||||
return Cors.add(request, Response.ok()).auth().preflight().build();
|
return Cors.add(request, Response.ok()).auth().preflight().build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
83
services/src/main/java/org/keycloak/services/resources/admin/AdminAuth.java
Executable file
83
services/src/main/java/org/keycloak/services/resources/admin/AdminAuth.java
Executable file
|
@ -0,0 +1,83 @@
|
||||||
|
package org.keycloak.services.resources.admin;
|
||||||
|
|
||||||
|
import org.keycloak.models.ApplicationModel;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.representations.AccessToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||||
|
*/
|
||||||
|
public class AdminAuth {
|
||||||
|
|
||||||
|
private final RealmModel realm;
|
||||||
|
private final AccessToken token;
|
||||||
|
private final UserModel user;
|
||||||
|
private final ClientModel client;
|
||||||
|
|
||||||
|
public AdminAuth(RealmModel realm, AccessToken token, UserModel user, ClientModel client) {
|
||||||
|
this.token = token;
|
||||||
|
this.realm = realm;
|
||||||
|
|
||||||
|
this.user = user;
|
||||||
|
this.client = client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RealmModel getRealm() {
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserModel getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ClientModel getClient() {
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccessToken getToken() {
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public boolean hasRealmRole(String role) {
|
||||||
|
if (client instanceof ApplicationModel) {
|
||||||
|
RoleModel roleModel = realm.getRole(role);
|
||||||
|
return realm.hasRole(user, roleModel) && realm.hasScope(client, roleModel);
|
||||||
|
} else {
|
||||||
|
AccessToken.Access access = token.getRealmAccess();
|
||||||
|
return access != null && access.isUserInRole(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasOneOfRealmRole(String... roles) {
|
||||||
|
for (String r : roles) {
|
||||||
|
if (hasRealmRole(r)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasAppRole(ApplicationModel app, String role) {
|
||||||
|
if (client instanceof ApplicationModel) {
|
||||||
|
RoleModel roleModel = app.getRole(role);
|
||||||
|
return realm.hasRole(user, roleModel) && realm.hasScope(client, roleModel);
|
||||||
|
} else {
|
||||||
|
AccessToken.Access access = token.getResourceAccess(app.getName());
|
||||||
|
return access != null && access.isUserInRole(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasOneOfAppRole(ApplicationModel app, String... roles) {
|
||||||
|
for (String r : roles) {
|
||||||
|
if (hasAppRole(app, r)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,11 +1,15 @@
|
||||||
package org.keycloak.services.resources.admin;
|
package org.keycloak.services.resources.admin;
|
||||||
|
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
|
import org.jboss.resteasy.spi.DefaultOptionsMethodException;
|
||||||
|
import org.jboss.resteasy.spi.HttpRequest;
|
||||||
|
import org.jboss.resteasy.spi.HttpResponse;
|
||||||
import org.jboss.resteasy.spi.NotFoundException;
|
import org.jboss.resteasy.spi.NotFoundException;
|
||||||
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
import org.jboss.resteasy.spi.UnauthorizedException;
|
import org.jboss.resteasy.spi.UnauthorizedException;
|
||||||
import org.keycloak.jose.jws.JWSInput;
|
import org.keycloak.jose.jws.JWSInput;
|
||||||
import org.keycloak.models.ApplicationModel;
|
import org.keycloak.models.ApplicationModel;
|
||||||
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
|
@ -17,10 +21,12 @@ import org.keycloak.services.managers.Auth;
|
||||||
import org.keycloak.services.managers.AuthenticationManager;
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
import org.keycloak.services.managers.TokenManager;
|
import org.keycloak.services.managers.TokenManager;
|
||||||
|
import org.keycloak.services.resources.Cors;
|
||||||
|
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
|
import javax.ws.rs.WebApplicationException;
|
||||||
import javax.ws.rs.core.Context;
|
import javax.ws.rs.core.Context;
|
||||||
import javax.ws.rs.core.HttpHeaders;
|
import javax.ws.rs.core.HttpHeaders;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
@ -39,6 +45,12 @@ public class AdminRoot {
|
||||||
@Context
|
@Context
|
||||||
protected UriInfo uriInfo;
|
protected UriInfo uriInfo;
|
||||||
|
|
||||||
|
@Context
|
||||||
|
protected HttpRequest request;
|
||||||
|
|
||||||
|
@Context
|
||||||
|
protected HttpResponse response;
|
||||||
|
|
||||||
protected AppAuthManager authManager;
|
protected AppAuthManager authManager;
|
||||||
protected TokenManager tokenManager;
|
protected TokenManager tokenManager;
|
||||||
|
|
||||||
|
@ -101,7 +113,7 @@ public class AdminRoot {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected Auth authenticateRealmAdminRequest(HttpHeaders headers) {
|
protected AdminAuth authenticateRealmAdminRequest(HttpHeaders headers) {
|
||||||
String tokenString = authManager.extractAuthorizationHeaderToken(headers);
|
String tokenString = authManager.extractAuthorizationHeaderToken(headers);
|
||||||
if (tokenString == null) throw new UnauthorizedException("Bearer");
|
if (tokenString == null) throw new UnauthorizedException("Bearer");
|
||||||
JWSInput input = new JWSInput(tokenString);
|
JWSInput input = new JWSInput(tokenString);
|
||||||
|
@ -123,14 +135,13 @@ public class AdminRoot {
|
||||||
throw new UnauthorizedException("Bearer");
|
throw new UnauthorizedException("Bearer");
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationModel consoleApp = realm.getApplicationByName(Constants.ADMIN_CONSOLE_APPLICATION);
|
ClientModel client = realm.findClient(token.getIssuedFor());
|
||||||
if (consoleApp == null) {
|
if (client == null) {
|
||||||
throw new NotFoundException("Could not find admin console application");
|
throw new NotFoundException("Could not find client for authorization");
|
||||||
|
|
||||||
}
|
}
|
||||||
Auth auth = new Auth(realm, authResult.getUser(), consoleApp);
|
|
||||||
return auth;
|
|
||||||
|
|
||||||
|
|
||||||
|
return new AdminAuth(realm, authResult.getToken(), authResult.getUser(), client);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UriBuilder realmsUrl(UriInfo uriInfo) {
|
public static UriBuilder realmsUrl(UriInfo uriInfo) {
|
||||||
|
@ -143,10 +154,19 @@ public class AdminRoot {
|
||||||
|
|
||||||
@Path("realms")
|
@Path("realms")
|
||||||
public RealmsAdminResource getRealmsAdmin(@Context final HttpHeaders headers) {
|
public RealmsAdminResource getRealmsAdmin(@Context final HttpHeaders headers) {
|
||||||
Auth auth = authenticateRealmAdminRequest(headers);
|
if (request.getHttpMethod().equalsIgnoreCase("OPTIONS")) {
|
||||||
|
logger.info("*** CORS ADMIN PREFLIGHT!!!!");
|
||||||
|
Response response = Cors.add(request, Response.ok()).preflight().allowedMethods("GET", "PUT", "POST", "DELETE").auth().build();
|
||||||
|
throw new WebApplicationException(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
AdminAuth auth = authenticateRealmAdminRequest(headers);
|
||||||
if (auth != null) {
|
if (auth != null) {
|
||||||
logger.info("authenticated admin access for: " + auth.getUser().getLoginName());
|
logger.info("authenticated admin access for: " + auth.getUser().getLoginName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Cors.add(request).allowedOrigins(auth.getToken()).allowedMethods("GET", "PUT", "POST", "DELETE").auth().build(response);
|
||||||
|
|
||||||
RealmsAdminResource adminResource = new RealmsAdminResource(auth, tokenManager);
|
RealmsAdminResource adminResource = new RealmsAdminResource(auth, tokenManager);
|
||||||
ResteasyProviderFactory.getInstance().injectProperties(adminResource);
|
ResteasyProviderFactory.getInstance().injectProperties(adminResource);
|
||||||
//resourceContext.initResource(adminResource);
|
//resourceContext.initResource(adminResource);
|
||||||
|
|
|
@ -19,10 +19,10 @@ public class RealmAuth {
|
||||||
APPLICATION, CLIENT, USER, REALM, AUDIT
|
APPLICATION, CLIENT, USER, REALM, AUDIT
|
||||||
}
|
}
|
||||||
|
|
||||||
private Auth auth;
|
private AdminAuth auth;
|
||||||
private ApplicationModel realmAdminApp;
|
private ApplicationModel realmAdminApp;
|
||||||
|
|
||||||
public RealmAuth(Auth auth, ApplicationModel realmAdminApp) {
|
public RealmAuth(AdminAuth auth, ApplicationModel realmAdminApp) {
|
||||||
this.auth = auth;
|
this.auth = auth;
|
||||||
this.realmAdminApp = realmAdminApp;
|
this.realmAdminApp = realmAdminApp;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,10 +46,10 @@ import java.util.Map;
|
||||||
*/
|
*/
|
||||||
public class RealmsAdminResource {
|
public class RealmsAdminResource {
|
||||||
protected static final Logger logger = Logger.getLogger(RealmsAdminResource.class);
|
protected static final Logger logger = Logger.getLogger(RealmsAdminResource.class);
|
||||||
protected Auth auth;
|
protected AdminAuth auth;
|
||||||
protected TokenManager tokenManager;
|
protected TokenManager tokenManager;
|
||||||
|
|
||||||
public RealmsAdminResource(Auth auth, TokenManager tokenManager) {
|
public RealmsAdminResource(AdminAuth auth, TokenManager tokenManager) {
|
||||||
this.auth = auth;
|
this.auth = auth;
|
||||||
this.tokenManager = tokenManager;
|
this.tokenManager = tokenManager;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue