admin ui login

This commit is contained in:
Bill Burke 2013-08-02 22:26:57 -04:00
parent 1d0b510637
commit 540385fec5
22 changed files with 449 additions and 134 deletions

View file

@ -7,7 +7,8 @@ import org.keycloak.services.models.KeycloakSession;
import org.keycloak.services.models.RealmModel;
import org.keycloak.services.models.RequiredCredentialModel;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.services.resources.RegistrationService;
import org.keycloak.services.resources.SaasService;
import org.keycloak.services.resources.SaasService;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@ -35,12 +36,12 @@ public class DemoApplication extends KeycloakApplication {
defaultRealm.setEnabled(true);
defaultRealm.setTokenLifespan(300);
defaultRealm.setAccessCodeLifespan(60);
defaultRealm.setSslNotRequired(false);
defaultRealm.setSslNotRequired(true);
defaultRealm.setCookieLoginAllowed(true);
defaultRealm.setRegistrationAllowed(true);
manager.generateRealmKeys(defaultRealm);
defaultRealm.addRequiredCredential(RequiredCredentialModel.PASSWORD);
defaultRealm.addRole(RegistrationService.REALM_CREATOR_ROLE);
defaultRealm.addRole(SaasService.REALM_CREATOR_ROLE);
RealmRepresentation rep = loadJson("META-INF/testrealm.json");
RealmModel realm = manager.createRealm("demo", rep.getRealm());

View file

@ -61,11 +61,11 @@
<hr/>
<% String errorMessage = (String)request.getAttribute("KEYCLOAK_LOGIN_ERROR_MESSAGE");
if (errorMessage != null) { %>
<div id="error-message" class="alert alert-block alert-error" style="block"><%=errorMessage%></div>
<div id="error-message" class="alert alert-block alert-error" ><%=errorMessage%></div>
<% } %>
<form class="form-horizontal" name="loginForm" action="<%=request.getAttribute("KEYCLOAK_LOGIN_ACTION")%>" method="POST">
<div class="control-group">
<label class="control-label" for="username">User Name</label>
<label class="control-label">User Name</label>
<div class="controls">
<% if (username != null) { %>
@ -123,5 +123,6 @@
</div>
<footer>
<p>Powered By Keycloak</p>
</footer>
</body>
</html>

View file

@ -163,7 +163,7 @@ public class OAuthManagedResourceValve extends FormAuthenticator implements Life
protected void remoteLogout(JWSInput token, HttpServletResponse response) throws IOException {
try {
log.debug("->> remoteLogout: ");
log.info("->> remoteLogout: ");
LogoutAction action = JsonSerialization.fromBytes(LogoutAction.class, token.getContent());
if (action.isExpired()) {
log.warn("admin request failed, expired token");

View file

@ -175,8 +175,9 @@ public class ServletOAuthLogin {
return false;
}
// reset the cookie
Cookie reset = new Cookie(stateCookie.getName(), stateCookie.getValue());
reset.setPath(stateCookie.getPath());
log.info("** reseting application state cookie");
Cookie reset = new Cookie(realmInfo.getStateCookieName(), "");
reset.setPath(getDefaultCookiePath());
reset.setMaxAge(0);
response.addCookie(reset);

View file

@ -1,5 +1,7 @@
package org.keycloak.services.managers;
import org.jboss.resteasy.jose.jws.JWSBuilder;
import org.jboss.resteasy.jwt.JsonSerialization;
import org.jboss.resteasy.logging.Logger;
import org.jboss.resteasy.spi.HttpResponse;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
@ -11,6 +13,7 @@ import org.keycloak.services.models.RealmModel;
import org.keycloak.services.models.RequiredCredentialModel;
import org.keycloak.services.models.UserModel;
import org.keycloak.services.resources.RealmsResource;
import org.keycloak.services.resources.SaasService;
import javax.ws.rs.NotAuthorizedException;
import javax.ws.rs.core.Cookie;
@ -31,6 +34,7 @@ import java.util.Set;
public class AuthenticationManager {
protected Logger logger = Logger.getLogger(AuthenticationManager.class);
public static final String FORM_USERNAME = "username";
public static final String KEYCLOAK_IDENTITY_COOKIE = "KEYCLOAK_IDENTITY";
/**
* Grabs token from headers, authenticates, authorizes
@ -44,21 +48,99 @@ public class AuthenticationManager {
return realm.isRealmAdmin(user);
}
public SkeletonKeyToken createIdentityToken(RealmModel realm, String username) {
SkeletonKeyToken token = new SkeletonKeyToken();
token.id(RealmManager.generateId());
token.issuedNow();
token.principal(username);
token.audience(realm.getId());
if (realm.getTokenLifespan() > 0) {
token.expiration((System.currentTimeMillis() / 1000) + realm.getTokenLifespan());
}
return token;
}
public NewCookie createLoginCookie(RealmModel realm, UserModel user, UriInfo uriInfo) {
String cookieName = KEYCLOAK_IDENTITY_COOKIE;
URI uri = RealmsResource.realmBaseUrl(uriInfo).build(realm.getId());
String cookiePath = uri.getPath();
return createLoginCookie(realm, user, cookieName, cookiePath);
}
public NewCookie createSaasIdentityCookie(RealmModel realm, UserModel user, UriInfo uriInfo) {
String cookieName = SaasService.SAAS_IDENTITY_COOKIE;
URI uri = SaasService.saasCookiePath(uriInfo).build();
String cookiePath = uri.getPath();
return createLoginCookie(realm, user, cookieName, cookiePath);
}
protected NewCookie createLoginCookie(RealmModel realm, UserModel user, String cookieName, String cookiePath) {
SkeletonKeyToken identityToken = createIdentityToken(realm, user.getLoginName());
String encoded = encodeToken(realm, identityToken);
boolean secureOnly = !realm.isSslNotRequired();
logger.info("creatingLoginCookie - name: " + cookieName + " path: " + cookiePath);
NewCookie cookie = new NewCookie(cookieName, encoded, cookiePath, null, null, NewCookie.DEFAULT_MAX_AGE, secureOnly, false);
return cookie;
}
protected String encodeToken(RealmModel realm, Object token) {
byte[] tokenBytes = null;
try {
tokenBytes = JsonSerialization.toByteArray(token, false);
} catch (Exception e) {
throw new RuntimeException(e);
}
String encodedToken = new JWSBuilder()
.content(tokenBytes)
.rsa256(realm.getPrivateKey());
return encodedToken;
}
public void expireIdentityCookie(RealmModel realm, UriInfo uriInfo) {
URI uri = RealmsResource.realmBaseUrl(uriInfo).build(realm.getId());
logger.info("Expiring identity cookie");
String path = uri.getPath();
String cookieName = KEYCLOAK_IDENTITY_COOKIE;
expireCookie(cookieName, path);
}
public void expireSaasIdentityCookie(UriInfo uriInfo) {
URI uri = SaasService.saasCookiePath(uriInfo).build();
String cookiePath = uri.getPath();
expireCookie(SaasService.SAAS_IDENTITY_COOKIE, cookiePath);
}
protected void expireCookie(String cookieName, String path) {
HttpResponse response = ResteasyProviderFactory.getContextData(HttpResponse.class);
if (response == null) {
logger.info("can't expire identity cookie, no HttpResponse");
return;
}
logger.info("Expiring identity cookie");
NewCookie expireIt = new NewCookie(TokenManager.KEYCLOAK_IDENTITY_COOKIE, "", uri.getPath(), null, "Expiring cookie", 0, false);
logger.info("Expiring cookie: " + cookieName + " path: " + path);
NewCookie expireIt = new NewCookie(cookieName, "", path, null, "Expiring cookie", 0, false);
response.addNewCookie(expireIt);
}
public UserModel authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
Cookie cookie = headers.getCookies().get(TokenManager.KEYCLOAK_IDENTITY_COOKIE);
if (cookie == null) return null;
String cookieName = KEYCLOAK_IDENTITY_COOKIE;
return authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
}
public UserModel authenticateSaasIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers) {
String cookieName = SaasService.SAAS_IDENTITY_COOKIE;
return authenticateIdentityCookie(realm, uriInfo, headers, cookieName);
}
protected UserModel authenticateIdentityCookie(RealmModel realm, UriInfo uriInfo, HttpHeaders headers, String cookieName) {
Cookie cookie = headers.getCookies().get(cookieName);
if (cookie == null) {
logger.info("authenticateCookie could not find cookie: " + cookieName);
return null;
}
String tokenString = cookie.getValue();
try {
@ -112,7 +194,6 @@ public class AuthenticationManager {
}
public boolean authenticateForm(RealmModel realm, UserModel user, MultivaluedMap<String, String> formData) {
String username = user.getLoginName();
Set<String> types = new HashSet<String>();
for (RequiredCredentialModel credential : realm.getRequiredCredentials()) {

View file

@ -2,6 +2,7 @@ package org.keycloak.services.managers;
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder;
import org.jboss.resteasy.logging.Logger;
import org.keycloak.TokenIdGenerator;
import org.keycloak.representations.idm.admin.LogoutAction;
import org.keycloak.services.models.RealmModel;
@ -17,6 +18,7 @@ import java.util.List;
* @version $Revision: 1 $
*/
public class ResourceAdminManager {
protected Logger logger = Logger.getLogger(ResourceAdminManager.class);
public void logoutAll(RealmModel realm) {
singleLogOut(realm, null);
@ -28,6 +30,7 @@ public class ResourceAdminManager {
.build();
List<ResourceModel> resources = realm.getResources();
logger.info("logging out " + resources.size() + " resoures.");
for (ResourceModel resource : resources) {
logoutResource(realm, resource, user, client);
}
@ -38,7 +41,9 @@ public class ResourceAdminManager {
String token = new TokenManager().encodeToken(realm, adminAction);
Form form = new Form();
form.param("token", token);
Response response = client.target(resource.getManagementUrl()).queryParam("action", "logout").request().post(Entity.form(form));
String managementUrl = resource.getManagementUrl();
logger.info("logout user: " + user + " resource: " + resource.getName() + " url" + managementUrl);
Response response = client.target(managementUrl).queryParam("action", "logout").request().post(Entity.form(form));
boolean success = response.getStatus() == 204;
response.close();
return success;

View file

@ -30,7 +30,6 @@ import java.util.concurrent.ConcurrentHashMap;
*/
public class TokenManager {
public static final String KEYCLOAK_IDENTITY_COOKIE = "KEYCLOAK_IDENTITY";
protected Map<String, AccessCodeEntry> accessCodeMap = new ConcurrentHashMap<String, AccessCodeEntry>();
public void clearAccessCodes() {
@ -45,14 +44,6 @@ public class TokenManager {
return accessCodeMap.remove(key);
}
public NewCookie createLoginCookie(RealmModel realm, UserModel user, UriInfo uriInfo) {
SkeletonKeyToken identityToken = createIdentityToken(realm, user.getLoginName());
String encoded = encodeToken(realm, identityToken);
URI uri = RealmsResource.realmBaseUrl(uriInfo).build(realm.getId());
boolean secureOnly = !realm.isSslNotRequired();
NewCookie cookie = new NewCookie(KEYCLOAK_IDENTITY_COOKIE, encoded, uri.getPath(), null, null, NewCookie.DEFAULT_MAX_AGE, secureOnly, true);
return cookie;
}
public AccessCodeEntry createAccessCode(String scopeParam, String state, String redirect, RealmModel realm, UserModel client, UserModel user) {
AccessCodeEntry code = new AccessCodeEntry();
@ -212,17 +203,6 @@ public class TokenManager {
return token;
}
public SkeletonKeyToken createIdentityToken(RealmModel realm, String username) {
SkeletonKeyToken token = new SkeletonKeyToken();
token.id(RealmManager.generateId());
token.issuedNow();
token.principal(username);
token.audience(realm.getId());
if (realm.getTokenLifespan() > 0) {
token.expiration((System.currentTimeMillis() / 1000) + realm.getTokenLifespan());
}
return token;
}
public String encodeToken(RealmModel realm, Object token) {
byte[] tokenBytes = null;

View file

@ -1,6 +1,8 @@
package org.keycloak.services.models.picketlink;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.NotImplementedYetException;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.keycloak.services.models.KeycloakSession;
import org.keycloak.services.models.KeycloakTransaction;
import org.keycloak.services.models.RealmModel;
@ -17,6 +19,7 @@ import java.util.concurrent.atomic.AtomicLong;
public class PicketlinkKeycloakSession implements KeycloakSession {
public static ThreadLocal<EntityManager> currentEntityManager = new ThreadLocal<EntityManager>();
public static ThreadLocal<Exception> setWhere = new ThreadLocal<Exception>();
public static ThreadLocal<String> setFromPath = new ThreadLocal<String>();
protected PartitionManager partitionManager;
protected EntityManager entityManager;
@ -31,7 +34,14 @@ public class PicketlinkKeycloakSession implements KeycloakSession {
if (currentEntityManager.get() != null)
{
setWhere.get().printStackTrace();
throw new IllegalStateException("Thread local was leaked!");
String path = setFromPath.get();
if (path == null) path = "???";
throw new IllegalStateException("Thread local was leaked! from path: " + path);
}
HttpRequest request = ResteasyProviderFactory.getContextData(HttpRequest.class);
if (request != null) {
setFromPath.set(request.getUri().getPath());
}
currentEntityManager.set(entityManager);
setWhere.set(new Exception());
@ -75,9 +85,10 @@ public class PicketlinkKeycloakSession implements KeycloakSession {
@Override
public void close() {
if (entityManager.getTransaction().isActive()) entityManager.getTransaction().rollback();
setFromPath.set(null);
setWhere.set(null);
currentEntityManager.set(null);
if (entityManager.getTransaction().isActive()) entityManager.getTransaction().rollback();
if (entityManager.isOpen()) entityManager.close();
}
}

View file

@ -390,11 +390,17 @@ public class RealmAdapter implements RealmModel {
@Override
public ResourceModel addResource(String name) {
ResourceData resourceData = new ResourceData(name);
ResourceData resourceData = new ResourceData(RealmManager.generateId());
User resourceUser = new User(name);
idm.add(resourceUser);
resourceData.setResourceUser(resourceUser);
resourceData.setResourceName(name);
resourceData.setResourceUser(resourceUser);
partitionManager.add(resourceData);
ResourceRelationship resourceRelationship = new ResourceRelationship();
resourceRelationship.setRealm(realm.getName());
resourceRelationship.setResource(resourceData.getName());
getRelationshipManager().add(resourceRelationship);
ResourceModel resource = new ResourceAdapter(resourceData, this, partitionManager);
resource.addRole("*");
resource.addScope(new UserAdapter(resourceUser, idm), "*");

View file

@ -23,6 +23,7 @@ public class ResourceData extends AbstractPartition {
super(name);
}
@AttributeProperty
public String getResourceName() {
return resourceName;
}

View file

@ -24,7 +24,7 @@ public class ResourceEntity implements Serializable {
private PartitionTypeEntity partitionTypeEntity;
@AttributeValue
private String realmName;
private String resourceName;
@AttributeValue
private boolean enabled;
@AttributeValue
@ -45,12 +45,12 @@ public class ResourceEntity implements Serializable {
this.partitionTypeEntity = partitionTypeEntity;
}
public String getRealmName() {
return realmName;
public String getResourceName() {
return resourceName;
}
public void setRealmName(String realmName) {
this.realmName = realmName;
public void setResourceName(String realmName) {
this.resourceName = realmName;
}
public boolean isEnabled() {

View file

@ -1,12 +1,10 @@
package org.keycloak.services.models.picketlink.relationships;
import org.keycloak.services.models.picketlink.mappings.RealmData;
import org.picketlink.idm.model.AbstractAttributedType;
import org.picketlink.idm.model.Attribute;
import org.picketlink.idm.model.Relationship;
import org.picketlink.idm.model.annotation.AttributeProperty;
import org.picketlink.idm.query.AttributeParameter;
import org.picketlink.idm.query.RelationshipQueryParameter;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>

View file

@ -1,15 +1,10 @@
package org.keycloak.services.models.picketlink.relationships;
import org.keycloak.services.models.picketlink.mappings.RealmData;
import org.keycloak.services.models.picketlink.mappings.ResourceData;
import org.picketlink.idm.model.AbstractAttributedType;
import org.picketlink.idm.model.Attribute;
import org.picketlink.idm.model.Relationship;
import org.picketlink.idm.model.annotation.AttributeProperty;
import org.picketlink.idm.model.sample.Agent;
import org.picketlink.idm.model.sample.User;
import org.picketlink.idm.query.AttributeParameter;
import org.picketlink.idm.query.RelationshipQueryParameter;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -20,9 +15,10 @@ public class ResourceRelationship extends AbstractAttributedType implements Rela
public static final AttributeParameter REALM = new AttributeParameter("realm");
protected String realm;
protected String resource;
public ResourceRelationship() {
}
@AttributeProperty
public String getRealm() {
return (String)getAttribute("realm").getValue();
}
@ -32,6 +28,7 @@ public class ResourceRelationship extends AbstractAttributedType implements Rela
}
@AttributeProperty
public String getResource() {
return (String)getAttribute("resource").getValue();
}

View file

@ -11,6 +11,7 @@ import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.HttpResponse;
import org.keycloak.services.JspRequestParameters;
import org.keycloak.services.managers.AccessCodeEntry;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.models.RealmModel;
@ -34,6 +35,7 @@ public abstract class AbstractLoginService {
protected RealmModel realm;
protected TokenManager tokenManager;
protected AuthenticationManager authManager = new AuthenticationManager();
public AbstractLoginService(RealmModel realm, TokenManager tokenManager) {
this.realm = realm;
@ -69,7 +71,7 @@ public abstract class AbstractLoginService {
redirectUri.queryParam("state", state);
Response.ResponseBuilder location = Response.status(302).location(redirectUri.build());
if (realm.isCookieLoginAllowed()) {
location.cookie(tokenManager.createLoginCookie(realm, accessCode.getUser(), uriInfo));
location.cookie(authManager.createLoginCookie(realm, accessCode.getUser(), uriInfo));
}
return location.build();
}

View file

@ -54,7 +54,7 @@ public class KeycloakApplication extends Application {
singletons.add(filter);
classes.add(KeycloakSessionResponseFilter.class);
classes.add(SkeletonKeyContextResolver.class);
classes.add(RegistrationService.class);
classes.add(SaasService.class);
}
protected KeycloakSessionFactory createSessionFactory() {

View file

@ -5,8 +5,6 @@ import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.managers.TokenManager;
import org.keycloak.services.models.KeycloakSession;
import org.keycloak.services.models.KeycloakSessionFactory;
import org.keycloak.services.models.RealmModel;
import org.keycloak.services.models.RoleModel;
import org.keycloak.services.models.UserModel;
@ -88,7 +86,7 @@ public class RealmsResource {
}
SocialService socialService = new SocialService(realm, tokenManager, socialRequestManager);
resourceContext.initResource(socialService);
return socialService;
return socialService;
}
}.call();
}
@ -121,7 +119,7 @@ public class RealmsResource {
RealmManager realmManager = new RealmManager(session);
RealmModel defaultRealm = realmManager.getRealm(RealmModel.DEFAULT_REALM);
UserModel realmCreator = new AuthenticationManager().authenticateBearerToken(defaultRealm, headers);
RoleModel creatorRole = defaultRealm.getRole(RegistrationService.REALM_CREATOR_ROLE);
RoleModel creatorRole = defaultRealm.getRole(SaasService.REALM_CREATOR_ROLE);
if (!defaultRealm.hasRole(realmCreator, creatorRole)) {
logger.warn("not a realm creator");
throw new NotAuthorizedException("Bearer");

View file

@ -1,70 +0,0 @@
package org.keycloak.services.resources;
import org.jboss.resteasy.logging.Logger;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.models.KeycloakSessionFactory;
import org.keycloak.services.models.RealmModel;
import org.keycloak.services.models.RoleModel;
import org.keycloak.services.models.UserModel;
import org.keycloak.services.models.UserCredentialModel;
import javax.ws.rs.Consumes;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.net.URI;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@Path("/registrations")
public class RegistrationService {
protected static final Logger logger = Logger.getLogger(RegistrationService.class);
public static final String REALM_CREATOR_ROLE = "realm-creator";
@Context
protected UriInfo uriInfo;
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response register(final UserRepresentation newUser) {
return new Transaction() {
@Override
protected Response callImpl() {
RealmManager realmManager = new RealmManager(session);
RealmModel defaultRealm = realmManager.defaultRealm();
if (!defaultRealm.isEnabled()) {
throw new ForbiddenException();
}
if (!defaultRealm.isRegistrationAllowed()) {
throw new ForbiddenException();
}
UserModel user = defaultRealm.getUser(newUser.getUsername());
if (user != null) {
return Response.status(400).type("text/plain").entity("user exists").build();
}
user = defaultRealm.addUser(newUser.getUsername());
for (CredentialRepresentation cred : newUser.getCredentials()) {
UserCredentialModel credModel = new UserCredentialModel();
credModel.setType(cred.getType());
credModel.setValue(cred.getValue());
defaultRealm.updateCredential(user, credModel);
}
RoleModel realmCreator = defaultRealm.getRole(REALM_CREATOR_ROLE);
defaultRealm.grantRole(user, realmCreator);
URI uri = uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(user.getLoginName()).build();
return Response.created(uri).build();
}
}.call();
}
}

View file

@ -0,0 +1,296 @@
package org.keycloak.services.resources;
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.representations.idm.CredentialRepresentation;
import org.keycloak.representations.idm.RequiredCredentialRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.models.KeycloakSession;
import org.keycloak.services.models.RealmModel;
import org.keycloak.services.models.RoleModel;
import org.keycloak.services.models.UserModel;
import org.keycloak.services.models.UserCredentialModel;
import javax.ws.rs.Consumes;
import javax.ws.rs.ForbiddenException;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
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 java.net.URI;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
@Path("/saas")
public class SaasService {
protected static final Logger logger = Logger.getLogger(SaasService.class);
public static final String REALM_CREATOR_ROLE = "realm-creator";
public static final String SAAS_IDENTITY_COOKIE = "KEYCLOAK_SAAS_IDENTITY";
@Context
protected UriInfo uriInfo;
@Context
protected HttpRequest request;
@Context
HttpResponse response;
protected String saasLoginPath = "/saas/saas-login.jsp";
protected String saasRegisterPath = "/saas/saas-register.jsp";
protected String adminPath = "/saas/admin/index.html";
protected AuthenticationManager authManager = new AuthenticationManager();
public static class WhoAmI {
protected String userId;
protected String displayName;
public WhoAmI() {
}
public WhoAmI(String userId, String displayName) {
this.userId = userId;
this.displayName = displayName;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
}
@Path("whoami")
@GET
@Produces("application/json")
public Response whoAmI(final @Context HttpHeaders headers) {
return new Transaction() {
@Override
public Response callImpl()
{
logger.info("WHOAMI start.");
RealmManager realmManager = new RealmManager(session);
RealmModel realm = realmManager.defaultRealm();
if (realm == null) throw new NotFoundException();
UserModel user = authManager.authenticateSaasIdentityCookie(realm, uriInfo, headers);
if (user == null) {
return Response.status(404).build();
}
logger.info("WHOAMI: " + user.getLoginName());
return Response.ok(new WhoAmI(user.getLoginName(), user.getLoginName())).build();
}
}.call();
}
@Path("isLoggedIn.js")
@GET
@Produces("application/javascript")
public String isLoggedIn(final @Context HttpHeaders headers) {
return new Transaction() {
@Override
public String callImpl()
{
logger.info("WHOAMI Javascript start.");
RealmManager realmManager = new RealmManager(session);
RealmModel realm = realmManager.defaultRealm();
if (realm == null) {
return "var keycloakCookieLoggedIn = false;";
}
UserModel user = authManager.authenticateSaasIdentityCookie(realm, uriInfo, headers);
if (user == null) {
return "var keycloakCookieLoggedIn = false;";
}
logger.info("WHOAMI: " + user.getLoginName());
return "var keycloakCookieLoggedIn = true;";
}
}.call();
}
public static UriBuilder contextRoot(UriInfo uriInfo) {
return UriBuilder.fromUri(uriInfo.getBaseUri()).replacePath("/auth-server");
}
public static UriBuilder saasCookiePath(UriInfo uriInfo) {
return contextRoot(uriInfo).path("rest").path(SaasService.class);
}
@Path("logout")
@GET
public void logout() {
new Transaction() {
@Override
protected void runImpl() {
authManager.expireSaasIdentityCookie(uriInfo);
request.forward(saasLoginPath);
}
}.run();
}
@Path("logout-cookie")
@GET
public void logoutCookie() {
logger.info("*** logoutCookie");
new Transaction() {
@Override
protected void runImpl() {
authManager.expireSaasIdentityCookie(uriInfo);
}
}.run();
}
@Path("login")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void processLogin(final MultivaluedMap<String, String> formData) {
logger.info("processLogin start");
new Transaction() {
@Override
protected void runImpl() {
RealmManager realmManager = new RealmManager(session);
RealmModel realm = realmManager.defaultRealm();
if (realm == null) throw new NotFoundException();
if (!realm.isEnabled()) {
throw new NotImplementedYetException();
}
String username = formData.getFirst("username");
UserModel user = realm.getUser(username);
if (user == null) {
logger.info("Not Authenticated! Incorrect user name");
request.setAttribute("KEYCLOAK_LOGIN_ERROR_MESSAGE", "Incorrect user name.");
request.forward(saasLoginPath);
return;
}
if (!user.isEnabled()) {
logger.info("NAccount is disabled, contact admin.");
request.setAttribute("KEYCLOAK_LOGIN_ERROR_MESSAGE", "Account is disabled, contact admin.");
request.forward(saasLoginPath);
return;
}
boolean authenticated = authManager.authenticateForm(realm, user, formData);
if (!authenticated) {
logger.info("Not Authenticated! Invalid credentials");
request.setAttribute("KEYCLOAK_LOGIN_ERROR_MESSAGE", "Invalid credentials.");
request.forward(saasLoginPath);
return;
}
NewCookie cookie = authManager.createSaasIdentityCookie(realm, user, uriInfo);
response.addNewCookie(cookie);
request.forward(adminPath);
}
}.run();
}
@Path("registrations")
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response register(final UserRepresentation newUser) {
return new Transaction() {
@Override
protected Response callImpl() {
RealmManager realmManager = new RealmManager(session);
RealmModel defaultRealm = realmManager.defaultRealm();
UserModel user = registerMe(defaultRealm, newUser);
if (user == null) {
return Response.status(400).type("text/plain").entity("Already exists").build();
}
URI uri = uriInfo.getBaseUriBuilder().path(RealmsResource.class).path(user.getLoginName()).build();
return Response.created(uri).build();
}
}.call();
}
@Path("registrations")
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response processRegister(final @FormParam("name") String name,
final @FormParam("email") String email,
final @FormParam("username") String username,
final @FormParam("password") String password,
final @FormParam("password-confirm") String confirm) {
if (!password.equals(confirm)) {
request.setAttribute("KEYCLOAK_LOGIN_ERROR_MESSAGE", "Password confirmation doesn't match.");
request.forward(saasRegisterPath);
return null;
}
return new Transaction() {
@Override
protected Response callImpl() {
RealmManager realmManager = new RealmManager(session);
RealmModel defaultRealm = realmManager.defaultRealm();
UserRepresentation newUser = new UserRepresentation();
newUser.setUsername(username);
newUser.credential(RequiredCredentialRepresentation.PASSWORD, password, false);
UserModel user = registerMe(defaultRealm, newUser);
if (user == null) {
request.setAttribute("KEYCLOAK_LOGIN_ERROR_MESSAGE", "Username already exists.");
request.forward(saasRegisterPath);
return null;
}
NewCookie cookie = authManager.createSaasIdentityCookie(defaultRealm, user, uriInfo);
return Response.status(302).location(contextRoot(uriInfo).path(adminPath).build()).cookie(cookie).build();
}
}.call();
}
protected UserModel registerMe(RealmModel defaultRealm, UserRepresentation newUser) {
if (!defaultRealm.isEnabled()) {
throw new ForbiddenException();
}
if (!defaultRealm.isRegistrationAllowed()) {
throw new ForbiddenException();
}
UserModel user = defaultRealm.getUser(newUser.getUsername());
if (user != null) {
return null;
}
user = defaultRealm.addUser(newUser.getUsername());
for (CredentialRepresentation cred : newUser.getCredentials()) {
UserCredentialModel credModel = new UserCredentialModel();
credModel.setType(cred.getType());
credModel.setValue(cred.getValue());
defaultRealm.updateCredential(user, credModel);
}
RoleModel realmCreator = defaultRealm.getRole(REALM_CREATOR_ROLE);
defaultRealm.grantRole(user, realmCreator);
return user;
}
}

View file

@ -49,7 +49,6 @@ public class TokenService extends AbstractLoginService {
@Context
protected SecurityContext securityContext;
protected AuthenticationManager authManager = new AuthenticationManager();
private ResourceAdminManager resourceAdminManager = new ResourceAdminManager();
public TokenService(RealmModel realm, TokenManager tokenManager) {
@ -115,7 +114,7 @@ public class TokenService extends AbstractLoginService {
throw new NotAuthorizedException("FORM");
}
tokenManager = new TokenManager();
SkeletonKeyToken token = tokenManager.createIdentityToken(realm, username);
SkeletonKeyToken token = authManager.createIdentityToken(realm, username);
String encoded = tokenManager.encodeToken(realm, token);
AccessTokenResponse res = accessTokenResponse(token, encoded);
return Response.ok(res, MediaType.APPLICATION_JSON_TYPE).build();

View file

@ -12,10 +12,13 @@ import org.keycloak.services.models.KeycloakSession;
import org.keycloak.services.models.KeycloakSessionFactory;
import org.keycloak.services.models.RealmModel;
import org.keycloak.services.models.RequiredCredentialModel;
import org.keycloak.services.models.ResourceModel;
import org.keycloak.services.models.UserModel;
import org.keycloak.services.resources.KeycloakApplication;
import org.keycloak.services.resources.RegistrationService;
import org.keycloak.services.resources.SaasService;
import org.keycloak.services.resources.SaasService;
import java.util.List;
import java.util.Set;
/**
@ -56,17 +59,21 @@ public class ImportTest {
defaultRealm.setRegistrationAllowed(true);
manager.generateRealmKeys(defaultRealm);
defaultRealm.addRequiredCredential(RequiredCredentialModel.PASSWORD);
defaultRealm.addRole(RegistrationService.REALM_CREATOR_ROLE);
defaultRealm.addRole(SaasService.REALM_CREATOR_ROLE);
RealmRepresentation rep = KeycloakTestBase.loadJson("testrealm.json");
RealmModel realm = manager.createRealm("demo", rep.getRealm());
manager.importRealm(rep, realm);
List<RequiredCredentialModel> creds = realm.getRequiredCredentials();
Assert.assertEquals(1, creds.size());
UserModel user = realm.getUser("loginclient");
Assert.assertNotNull(user);
Set<String> scopes = realm.getScope(user);
System.out.println("Scopes size: " + scopes.size());
Assert.assertTrue(scopes.contains("*"));
List<ResourceModel> resources = realm.getResources();
Assert.assertEquals(2, resources.size());
}

View file

@ -3,7 +3,8 @@ package org.keycloak.test;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.models.RealmModel;
import org.keycloak.services.models.RequiredCredentialModel;
import org.keycloak.services.resources.RegistrationService;
import org.keycloak.services.resources.SaasService;
import org.keycloak.services.resources.SaasService;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@ -21,7 +22,7 @@ public class InstallationManager {
defaultRealm.setRegistrationAllowed(true);
manager.generateRealmKeys(defaultRealm);
defaultRealm.addRequiredCredential(RequiredCredentialModel.PASSWORD);
defaultRealm.addRole(RegistrationService.REALM_CREATOR_ROLE);
defaultRealm.addRole(SaasService.REALM_CREATOR_ROLE);
}
public boolean isInstalled(RealmManager manager) {

View file

@ -64,7 +64,7 @@ public class RealmCreationTest {
user.credential(RequiredCredentialRepresentation.PASSWORD, "geheim", false);
WebTarget target = client.target(generateURL("/"));
Response response = target.path("registrations").request().post(Entity.json(user));
Response response = target.path("saas/registrations").request().post(Entity.json(user));
Assert.assertEquals(201, response.getStatus());
response.close();