Merge pull request #4476 from stianst/ACCOUNT32
KEYCLOAK-1250 Profile and console loader for new account management c…
This commit is contained in:
commit
c597123d1a
9 changed files with 276 additions and 14 deletions
|
@ -35,13 +35,13 @@ import java.util.Set;
|
|||
public class Profile {
|
||||
|
||||
public enum Feature {
|
||||
AUTHORIZATION, IMPERSONATION, SCRIPTS, DOCKER
|
||||
AUTHORIZATION, IMPERSONATION, SCRIPTS, DOCKER, ACCOUNT2
|
||||
}
|
||||
|
||||
private enum ProfileValue {
|
||||
PRODUCT(Feature.AUTHORIZATION, Feature.SCRIPTS, Feature.DOCKER),
|
||||
PREVIEW,
|
||||
COMMUNITY(Feature.DOCKER);
|
||||
PRODUCT(Feature.AUTHORIZATION, Feature.SCRIPTS, Feature.DOCKER, Feature.ACCOUNT2),
|
||||
PREVIEW(Feature.ACCOUNT2),
|
||||
COMMUNITY(Feature.DOCKER, Feature.ACCOUNT2);
|
||||
|
||||
private List<Feature> disabled;
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@
|
|||
<module name="org.bouncycastle" />
|
||||
<module name="javax.api"/>
|
||||
<module name="javax.activation.api"/>
|
||||
<module name="javax.json.api"/>
|
||||
<module name="org.apache.httpcomponents"/>
|
||||
<module name="org.twitter4j"/>
|
||||
<module name="javax.transaction.api"/>
|
||||
|
|
6
pom.xml
6
pom.xml
|
@ -81,6 +81,7 @@
|
|||
<elytron.undertow-server.version>1.0.0.Final</elytron.undertow-server.version>
|
||||
<woodstox.version>5.0.3</woodstox.version>
|
||||
<xmlsec.version>2.0.5</xmlsec.version>
|
||||
<glassfish.json.version>1.0.4</glassfish.json.version>
|
||||
|
||||
<!-- Authorization Drools Policy Provider -->
|
||||
<version.org.drools>6.4.0.Final</version.org.drools>
|
||||
|
@ -414,6 +415,11 @@
|
|||
<version>${wildfly.version}</version>
|
||||
<type>zip</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish</groupId>
|
||||
<artifactId>javax.json</artifactId>
|
||||
<version>${glassfish.json.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Twitter -->
|
||||
<dependency>
|
||||
|
|
|
@ -58,6 +58,10 @@
|
|||
<groupId>javax.mail</groupId>
|
||||
<artifactId>javax.mail-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.glassfish</groupId>
|
||||
<artifactId>javax.json</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
<artifactId>keycloak-server-spi</artifactId>
|
||||
|
|
|
@ -209,7 +209,7 @@ public class RealmsResource {
|
|||
public Object getAccountService(final @PathParam("realm") String name) {
|
||||
RealmModel realm = init(name);
|
||||
EventBuilder event = new EventBuilder(realm, session, clientConnection);
|
||||
return AccountLoader.getAccountService(session, event);
|
||||
return new AccountLoader().getAccountService(session, event);
|
||||
}
|
||||
|
||||
@Path("{realm}")
|
||||
|
|
|
@ -0,0 +1,215 @@
|
|||
package org.keycloak.services.resources.account;
|
||||
|
||||
import org.jboss.resteasy.annotations.cache.NoCache;
|
||||
import org.keycloak.common.Version;
|
||||
import org.keycloak.models.*;
|
||||
import org.keycloak.models.Constants;
|
||||
import org.keycloak.services.Urls;
|
||||
import org.keycloak.services.managers.ClientManager;
|
||||
import org.keycloak.theme.BrowserSecurityHeaderSetup;
|
||||
import org.keycloak.theme.FreeMarkerException;
|
||||
import org.keycloak.theme.FreeMarkerUtil;
|
||||
import org.keycloak.theme.Theme;
|
||||
import org.keycloak.utils.MediaType;
|
||||
|
||||
import javax.ws.rs.GET;
|
||||
import javax.ws.rs.Path;
|
||||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.core.Context;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import javax.json.Json;
|
||||
import javax.json.JsonObjectBuilder;
|
||||
import javax.json.JsonWriter;
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.models.ClientModel;
|
||||
import org.keycloak.protocol.oidc.utils.RedirectUtils;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
import org.keycloak.services.managers.AppAuthManager;
|
||||
import org.keycloak.services.managers.Auth;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.services.util.ResolveRelative;
|
||||
import org.keycloak.services.validation.Validation;
|
||||
|
||||
/**
|
||||
* Created by st on 29/03/17.
|
||||
*/
|
||||
public class AccountConsole {
|
||||
private static final Logger logger = Logger.getLogger(AccountConsole.class);
|
||||
|
||||
private final Pattern bundleParamPattern = Pattern.compile("(\\{\\s*(\\d+)\\s*\\})");
|
||||
|
||||
@Context
|
||||
protected KeycloakSession session;
|
||||
@Context
|
||||
protected UriInfo uriInfo;
|
||||
|
||||
private final AppAuthManager authManager;
|
||||
private final RealmModel realm;
|
||||
private final ClientModel client;
|
||||
private final Theme theme;
|
||||
|
||||
private Auth auth;
|
||||
|
||||
public AccountConsole(RealmModel realm, ClientModel client, Theme theme) {
|
||||
this.realm = realm;
|
||||
this.client = client;
|
||||
this.theme = theme;
|
||||
this.authManager = new AppAuthManager();
|
||||
}
|
||||
|
||||
public void init() {
|
||||
AuthenticationManager.AuthResult authResult = authManager.authenticateIdentityCookie(session, realm);
|
||||
if (authResult != null) {
|
||||
auth = new Auth(realm, authResult.getToken(), authResult.getUser(), client, authResult.getSession(), true);
|
||||
}
|
||||
}
|
||||
|
||||
@GET
|
||||
@NoCache
|
||||
public Response getMainPage() throws URISyntaxException, IOException, FreeMarkerException {
|
||||
if (!uriInfo.getRequestUri().getPath().endsWith("/")) {
|
||||
return Response.status(302).location(uriInfo.getRequestUriBuilder().path("/").build()).build();
|
||||
} else {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
|
||||
URI baseUri = uriInfo.getBaseUri();
|
||||
|
||||
String authUrl = baseUri.toString();
|
||||
authUrl = authUrl.substring(0, authUrl.length() - 1);
|
||||
|
||||
map.put("authUrl", authUrl);
|
||||
map.put("baseUrl", authUrl + "/realms/" + realm.getName() + "/account");
|
||||
map.put("realm", realm.getName());
|
||||
map.put("resourceUrl", Urls.themeRoot(baseUri) + "/account/" + theme.getName());
|
||||
map.put("resourceVersion", Version.RESOURCES_VERSION);
|
||||
|
||||
String[] referrer = getReferrer();
|
||||
if (referrer != null) {
|
||||
map.put("referrer", referrer[0]);
|
||||
map.put("referrer_uri", referrer[1]);
|
||||
}
|
||||
|
||||
try {
|
||||
if (auth != null) {
|
||||
Locale locale = session.getContext().resolveLocale(auth.getUser());
|
||||
map.put("locale", locale.toLanguageTag());
|
||||
map.put("msg", messagesToJsonString(theme.getMessages(locale)));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to load messages", e);
|
||||
}
|
||||
|
||||
map.put("properties", theme.getProperties());
|
||||
|
||||
FreeMarkerUtil freeMarkerUtil = new FreeMarkerUtil();
|
||||
String result = freeMarkerUtil.processTemplate(map, "index.ftl", theme);
|
||||
Response.ResponseBuilder builder = Response.status(Response.Status.OK).type(MediaType.TEXT_HTML_UTF_8).language(Locale.ENGLISH).entity(result);
|
||||
BrowserSecurityHeaderSetup.headers(builder, realm);
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
||||
private String messagesToJsonString(Properties props) {
|
||||
if (props == null) return "";
|
||||
|
||||
JsonObjectBuilder json = Json.createObjectBuilder();
|
||||
for (String prop : props.stringPropertyNames()) {
|
||||
json.add(prop, convertPropValue(props.getProperty(prop)));
|
||||
}
|
||||
|
||||
return json.build().toString();
|
||||
}
|
||||
|
||||
private String convertPropValue(String propertyValue) {
|
||||
propertyValue = propertyValue.replace("''", "%27");
|
||||
propertyValue = propertyValue.replace("'", "%27");
|
||||
propertyValue = propertyValue.replace("\"", "%22");
|
||||
|
||||
propertyValue = putJavaParamsInNgTranslateFormat(propertyValue);
|
||||
|
||||
return propertyValue;
|
||||
}
|
||||
|
||||
// Put java resource bundle params in ngx-translate format
|
||||
// Do you like {0} and {1} ?
|
||||
// becomes
|
||||
// Do you like {{param_0}} and {{param_1}} ?
|
||||
private String putJavaParamsInNgTranslateFormat(String propertyValue) {
|
||||
Matcher matcher = bundleParamPattern.matcher(propertyValue);
|
||||
while (matcher.find()) {
|
||||
propertyValue = propertyValue.replace(matcher.group(1), "{{param_" + matcher.group(2) + "}}");
|
||||
}
|
||||
|
||||
return propertyValue;
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("index.html")
|
||||
public Response getIndexHtmlRedirect() {
|
||||
return Response.status(302).location(session.getContext().getUri().getRequestUriBuilder().path("../").build()).build();
|
||||
}
|
||||
|
||||
@GET
|
||||
@Path("keycloak.json")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NoCache
|
||||
public ClientManager.InstallationAdapterConfig getConfig() {
|
||||
ClientModel accountClient = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
|
||||
if (accountClient == null) {
|
||||
throw new javax.ws.rs.NotFoundException("Account console client not found");
|
||||
}
|
||||
RealmManager realmMgr = new RealmManager(session);
|
||||
URI baseUri = session.getContext().getUri().getBaseUri();
|
||||
return new ClientManager(realmMgr).toInstallationRepresentation(realm, accountClient, baseUri);
|
||||
}
|
||||
|
||||
// TODO: took this code from elsewhere - refactor
|
||||
private String[] getReferrer() {
|
||||
String referrer = uriInfo.getQueryParameters().getFirst("referrer");
|
||||
if (referrer == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String referrerUri = uriInfo.getQueryParameters().getFirst("referrer_uri");
|
||||
|
||||
ClientModel referrerClient = realm.getClientByClientId(referrer);
|
||||
if (referrerClient != null) {
|
||||
if (referrerUri != null) {
|
||||
referrerUri = RedirectUtils.verifyRedirectUri(uriInfo, referrerUri, realm, referrerClient);
|
||||
} else {
|
||||
referrerUri = ResolveRelative.resolveRelativeUri(uriInfo.getRequestUri(), client.getRootUrl(), referrerClient.getBaseUrl());
|
||||
}
|
||||
|
||||
if (referrerUri != null) {
|
||||
String referrerName = referrerClient.getName();
|
||||
if (Validation.isBlank(referrerName)) {
|
||||
referrerName = referrer;
|
||||
}
|
||||
return new String[]{referrerName, referrerUri};
|
||||
}
|
||||
} else if (referrerUri != null) {
|
||||
referrerClient = realm.getClientByClientId(referrer);
|
||||
if (client != null) {
|
||||
referrerUri = RedirectUtils.verifyRedirectUri(uriInfo, referrerUri, realm, referrerClient);
|
||||
|
||||
if (referrerUri != null) {
|
||||
return new String[]{referrer, referrerUri};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -27,12 +27,16 @@ import org.keycloak.models.RealmModel;
|
|||
import org.keycloak.services.managers.AppAuthManager;
|
||||
import org.keycloak.services.managers.Auth;
|
||||
import org.keycloak.services.managers.AuthenticationManager;
|
||||
import org.keycloak.theme.Theme;
|
||||
import org.keycloak.theme.ThemeProvider;
|
||||
|
||||
import javax.ws.rs.HttpMethod;
|
||||
import javax.ws.rs.InternalServerErrorException;
|
||||
import javax.ws.rs.NotAuthorizedException;
|
||||
import javax.ws.rs.NotFoundException;
|
||||
import javax.ws.rs.core.HttpHeaders;
|
||||
import javax.ws.rs.core.MediaType;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
|
@ -42,10 +46,7 @@ public class AccountLoader {
|
|||
|
||||
private static final Logger logger = Logger.getLogger(AccountLoader.class);
|
||||
|
||||
private AccountLoader() {
|
||||
}
|
||||
|
||||
public static Object getAccountService(KeycloakSession session, EventBuilder event) {
|
||||
public Object getAccountService(KeycloakSession session, EventBuilder event) {
|
||||
RealmModel realm = session.getContext().getRealm();
|
||||
|
||||
ClientModel client = realm.getClientByClientId(Constants.ACCOUNT_MANAGEMENT_CLIENT_ID);
|
||||
|
@ -59,6 +60,9 @@ public class AccountLoader {
|
|||
MediaType content = headers.getMediaType();
|
||||
List<MediaType> accepts = headers.getAcceptableMediaTypes();
|
||||
|
||||
Theme theme = getTheme(session);
|
||||
boolean deprecatedAccount = isDeprecatedFormsAccountConsole(theme);
|
||||
|
||||
if (request.getHttpMethod().equals(HttpMethod.OPTIONS)) {
|
||||
return new CorsPreflightService(request);
|
||||
} else if ((accepts.contains(MediaType.APPLICATION_JSON_TYPE) || MediaType.APPLICATION_JSON_TYPE.equals(content)) && !request.getUri().getPath().endsWith("keycloak.json")) {
|
||||
|
@ -73,10 +77,34 @@ public class AccountLoader {
|
|||
accountRestService.init();
|
||||
return accountRestService;
|
||||
} else {
|
||||
AccountFormService accountFormService = new AccountFormService(realm, client, event);
|
||||
ResteasyProviderFactory.getInstance().injectProperties(accountFormService);
|
||||
accountFormService.init();
|
||||
return accountFormService;
|
||||
if (deprecatedAccount) {
|
||||
AccountFormService accountFormService = new AccountFormService(realm, client, event);
|
||||
ResteasyProviderFactory.getInstance().injectProperties(accountFormService);
|
||||
accountFormService.init();
|
||||
return accountFormService;
|
||||
} else {
|
||||
AccountConsole console = new AccountConsole(realm, client, theme);
|
||||
ResteasyProviderFactory.getInstance().injectProperties(console);
|
||||
console.init();
|
||||
return console;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Theme getTheme(KeycloakSession session) {
|
||||
try {
|
||||
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
|
||||
return themeProvider.getTheme(session.getContext().getRealm().getAccountTheme(), Theme.Type.ACCOUNT);
|
||||
} catch (IOException e) {
|
||||
throw new InternalServerErrorException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isDeprecatedFormsAccountConsole(Theme theme) {
|
||||
try {
|
||||
return Boolean.parseBoolean(theme.getProperties().getProperty("deprecatedMode", "true"));
|
||||
} catch (IOException e) {
|
||||
throw new InternalServerErrorException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ package org.keycloak.services.resources.admin.info;
|
|||
import org.keycloak.broker.provider.IdentityProvider;
|
||||
import org.keycloak.broker.provider.IdentityProviderFactory;
|
||||
import org.keycloak.broker.social.SocialIdentityProvider;
|
||||
import org.keycloak.common.Profile;
|
||||
import org.keycloak.component.ComponentFactory;
|
||||
import org.keycloak.events.EventType;
|
||||
import org.keycloak.events.admin.OperationType;
|
||||
|
@ -169,6 +170,10 @@ public class ServerInfoAdminResource {
|
|||
List<String> themeNames = new LinkedList<>(themeProvider.nameSet(type));
|
||||
Collections.sort(themeNames);
|
||||
|
||||
if (!Profile.isFeatureEnabled(Profile.Feature.ACCOUNT2)) {
|
||||
themeNames.remove("keycloak-preview");
|
||||
}
|
||||
|
||||
List<ThemeInfoRepresentation> themes = new LinkedList<>();
|
||||
info.getThemes().put(type.toString().toLowerCase(), themes);
|
||||
|
||||
|
|
|
@ -5,5 +5,8 @@
|
|||
}, {
|
||||
"name" : "keycloak",
|
||||
"types": [ "admin", "account", "login", "common", "email", "welcome" ]
|
||||
}, {
|
||||
"name" : "keycloak-preview",
|
||||
"types": [ "account" ]
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue