Merge pull request #2364 from stianst/KEYCLOAK-2637

KEYCLOAK-2637
This commit is contained in:
Stian Thorgersen 2016-03-11 13:00:54 +01:00
commit b3bb68aed7
13 changed files with 50 additions and 324 deletions

View file

@ -1,29 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.messages;
import org.keycloak.provider.Provider;
/**
* @author <a href="mailto:leonardo.zanivan@gmail.com">Leonardo Zanivan</a>
*/
public interface MessagesProvider extends Provider {
String getMessage(String messageKey, Object... parameters);
}

View file

@ -1,27 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.messages;
import org.keycloak.provider.ProviderFactory;
/**
* @author <a href="mailto:leonardo.zanivan@gmail.com">Leonardo Zanivan</a>
*/
public interface MessagesProviderFactory extends ProviderFactory<MessagesProvider> {
}

View file

@ -1,49 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.messages;
import org.keycloak.provider.Provider;
import org.keycloak.provider.ProviderFactory;
import org.keycloak.provider.Spi;
/**
* @author <a href="mailto:leonardo.zanivan@gmail.com">Leonardo Zanivan</a>
*/
public class MessagesSpi implements Spi {
@Override
public boolean isInternal() {
return true;
}
@Override
public String getName() {
return "messages";
}
@Override
public Class<? extends Provider> getProviderClass() {
return MessagesProvider.class;
}
@Override
public Class<? extends ProviderFactory> getProviderFactoryClass() {
return MessagesProviderFactory.class;
}
}

View file

@ -1,81 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.services.messages;
import java.io.IOException;
import java.net.URL;
import java.text.MessageFormat;
import java.util.Locale;
import java.util.Properties;
import org.keycloak.models.KeycloakSession;
import org.keycloak.messages.MessagesProvider;
import org.keycloak.services.ServicesLogger;
/**
* @author <a href="mailto:leonardo.zanivan@gmail.com">Leonardo Zanivan</a>
*/
public class AdminMessagesProvider implements MessagesProvider {
private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
private KeycloakSession session;
private Locale locale;
private Properties messagesBundle;
public AdminMessagesProvider(KeycloakSession session, Locale locale) {
this.session = session;
this.locale = locale;
this.messagesBundle = getMessagesBundle(locale);
}
@Override
public String getMessage(String messageKey, Object... parameters) {
String message = messagesBundle.getProperty(messageKey, messageKey);
try {
return new MessageFormat(message, locale).format(parameters);
} catch (Exception e) {
logger.failedToFormatMessage(e.getMessage());
return message;
}
}
@Override
public void close() {
}
private Properties getMessagesBundle(Locale locale) {
Properties properties = new Properties();
if (locale == null) {
return properties;
}
URL url = getClass().getClassLoader().getResource(
"theme/base/admin/messages/messages_" + locale.toString() + ".properties");
if (url != null) {
try {
properties.load(url.openStream());
} catch (IOException ex) {
logger.failedToloadMessages(ex);
}
}
return properties;
}
}

View file

@ -1,54 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.services.messages;
import java.util.Locale;
import org.keycloak.Config;
import org.keycloak.messages.MessagesProvider;
import org.keycloak.messages.MessagesProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
/**
* @author <a href="mailto:leonardo.zanivan@gmail.com">Leonardo Zanivan</a>
*/
public class AdminMessagesProviderFactory implements MessagesProviderFactory {
@Override
public MessagesProvider create(KeycloakSession session) {
return new AdminMessagesProvider(session, Locale.ENGLISH);
}
@Override
public void init(Config.Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) {
}
@Override
public void close() {
}
@Override
public String getId() {
return "admin";
}
}

View file

@ -82,7 +82,6 @@ public class KeycloakApplication extends Application {
singletons.add(new ServerVersionResource());
singletons.add(new RealmsResource());
singletons.add(new AdminRoot());
singletons.add(new ModelExceptionMapper());
classes.add(QRCodeResource.class);
classes.add(ThemeResource.class);
classes.add(JsResource.class);

View file

@ -1,49 +0,0 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.services.resources;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import org.keycloak.messages.MessagesProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelException;
import org.keycloak.services.ErrorResponse;
import org.keycloak.services.ServicesLogger;
/**
* @author <a href="mailto:leonardo.zanivan@gmail.com">Leonardo Zanivan</a>
*/
@Provider
public class ModelExceptionMapper implements ExceptionMapper<ModelException> {
private static final ServicesLogger logger = ServicesLogger.ROOT_LOGGER;
@Context
private KeycloakSession session;
@Override
public Response toResponse(ModelException ex) {
String message = session.getProvider(MessagesProvider.class, "admin")
.getMessage(ex.getMessage(), ex.getParameters());
logger.error(message, ex);
return ErrorResponse.error(message, Response.Status.BAD_REQUEST);
}
}

View file

@ -27,7 +27,6 @@ import org.keycloak.theme.BrowserSecurityHeaderSetup;
import org.keycloak.theme.FreeMarkerException;
import org.keycloak.theme.FreeMarkerUtil;
import org.keycloak.theme.Theme;
import org.keycloak.theme.ThemeProvider;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
@ -281,7 +280,7 @@ public class AdminConsole {
if (!uriInfo.getRequestUri().getPath().endsWith("/")) {
return Response.status(302).location(uriInfo.getRequestUriBuilder().path("/").build()).build();
} else {
Theme theme = getTheme();
Theme theme = AdminRoot.getTheme(session, realm);
Map<String, Object> map = new HashMap<>();
@ -303,11 +302,6 @@ public class AdminConsole {
}
}
private Theme getTheme() throws IOException {
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
return themeProvider.getTheme(realm.getAdminTheme(), Theme.Type.ADMIN);
}
@GET
@Path("{indexhtml: index.html}") // this expression is a hack to get around jaxdoclet generation bug. Doesn't like index.html
public Response getIndexHtmlRedirect() {
@ -318,11 +312,7 @@ public class AdminConsole {
@Path("messages.json")
@Produces(MediaType.APPLICATION_JSON)
public Properties getMessages(@QueryParam("lang") String lang) {
try {
Locale locale = lang != null ? Locale.forLanguageTag(lang) : Locale.ENGLISH;
return getTheme().getMessages("admin-messages", locale);
} catch (IOException e) {
throw new WebApplicationException("Failed to load message bundle", e);
}
return AdminRoot.getMessages(session, realm, "admin-messages", lang);
}
}

View file

@ -38,6 +38,8 @@ import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.Cors;
import org.keycloak.services.resources.admin.info.ServerInfoAdminResource;
import org.keycloak.theme.Theme;
import org.keycloak.theme.ThemeProvider;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
@ -47,6 +49,9 @@ import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.util.Locale;
import java.util.Properties;
/**
* Root resource for admin console and admin REST API
@ -265,4 +270,31 @@ public class AdminRoot {
}
}
public static Theme getTheme(KeycloakSession session, RealmModel realm) throws IOException {
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
return themeProvider.getTheme(realm.getAdminTheme(), Theme.Type.ADMIN);
}
public static Properties getMessages(KeycloakSession session, RealmModel realm, String lang) {
try {
Theme theme = getTheme(session, realm);
Locale locale = lang != null ? Locale.forLanguageTag(lang) : Locale.ENGLISH;
return theme.getMessages(locale);
} catch (IOException e) {
logger.error("Failed to load messages from theme", e);
return new Properties();
}
}
public static Properties getMessages(KeycloakSession session, RealmModel realm, String bundle, String lang) {
try {
Theme theme = getTheme(session, realm);
Locale locale = lang != null ? Locale.forLanguageTag(lang) : Locale.ENGLISH;
return theme.getMessages(bundle, locale);
} catch (IOException e) {
logger.error("Failed to load messages from theme", e);
return new Properties();
}
}
}

View file

@ -36,6 +36,7 @@ import org.keycloak.models.GroupModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelException;
import org.keycloak.models.ModelReadOnlyException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserConsentModel;
@ -54,6 +55,7 @@ import org.keycloak.representations.idm.GroupRepresentation;
import org.keycloak.representations.idm.UserConsentRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.representations.idm.UserSessionRepresentation;
import org.keycloak.services.ErrorResponseException;
import org.keycloak.services.managers.AuthenticationManager;
import org.keycloak.services.managers.ClientSessionCode;
import org.keycloak.services.managers.UserManager;
@ -74,11 +76,14 @@ import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.WebApplicationException;
import java.io.IOException;
import java.net.URI;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -86,6 +91,7 @@ import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import org.keycloak.models.UsernameLoginFailureModel;
@ -94,6 +100,9 @@ import org.keycloak.services.managers.UserSessionManager;
import org.keycloak.services.resources.AccountService;
import org.keycloak.common.util.Time;
import org.keycloak.services.validation.Validation;
import org.keycloak.theme.Theme;
import org.keycloak.theme.Theme.Type;
import org.keycloak.theme.ThemeProvider;
/**
* Base resource for managing users
@ -719,6 +728,10 @@ public class UsersResource {
throw new BadRequestException("Resetting to N old passwords is not allowed.");
} catch (ModelReadOnlyException mre) {
throw new BadRequestException("Can't reset password as account is read only");
} catch (ModelException e) {
Properties messages = AdminRoot.getMessages(session, realm, auth.getAuth().getToken().getLocale());
throw new ErrorResponseException(e.getMessage(), MessageFormat.format(messages.getProperty(e.getMessage(), e.getMessage()), e.getParameters()),
Status.BAD_REQUEST);
}
if (pass.isTemporary() != null && pass.isTemporary()) user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);

View file

@ -1,18 +0,0 @@
#
# Copyright 2016 Red Hat, Inc. and/or its affiliates
# and other contributors as indicated by the @author tags.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
org.keycloak.services.messages.AdminMessagesProviderFactory

View file

@ -17,5 +17,4 @@
org.keycloak.exportimport.ClientDescriptionConverterSpi
org.keycloak.wellknown.WellKnownSpi
org.keycloak.messages.MessagesSpi
org.keycloak.services.clientregistration.ClientRegistrationSpi

View file

@ -506,8 +506,8 @@ module.controller('UserCredentialsCtrl', function($scope, realm, user, RequiredA
$scope.password = null;
$scope.confirmPassword = null;
}, function(response) {
if (response.data && response.data.errorMessage) {
Notifications.error(response.data.errorMessage);
if (response.data && response.data['error_description']) {
Notifications.error(response.data['error_description']);
} else {
Notifications.error("Failed to reset user password");
}