/*
* Copyright (C) 2014 Red Hat, Inc. and/or its affiliates.
*
* 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.jboss.errai.security.client.local.context;
import javax.annotation.PostConstruct;
import javax.enterprise.event.Event;
import javax.inject.Inject;
import org.jboss.errai.bus.client.ErraiBus;
import org.jboss.errai.bus.client.api.ClientMessageBus;
import org.jboss.errai.bus.client.framework.BusState;
import org.jboss.errai.bus.client.framework.ClientMessageBusImpl;
import org.jboss.errai.common.client.api.Caller;
import org.jboss.errai.common.client.api.ErrorCallback;
import org.jboss.errai.common.client.api.IsElement;
import org.jboss.errai.common.client.api.RemoteCallback;
import org.jboss.errai.common.client.api.extension.InitVotes;
import org.jboss.errai.common.client.dom.Div;
import org.jboss.errai.common.client.dom.HTMLElement;
import org.jboss.errai.ioc.client.api.EntryPoint;
import org.jboss.errai.security.client.local.api.SecurityContext;
import org.jboss.errai.security.client.local.spi.ActiveUserCache;
import org.jboss.errai.security.shared.api.identity.User;
import org.jboss.errai.security.shared.event.LoggedInEvent;
import org.jboss.errai.security.shared.event.LoggedOutEvent;
import org.jboss.errai.security.shared.service.NonCachingUserService;
import org.jboss.errai.ui.nav.client.local.DefaultPage;
import org.jboss.errai.ui.nav.client.local.Navigation;
import org.jboss.errai.ui.nav.client.local.Page;
import org.jboss.errai.ui.nav.client.local.TransitionToRole;
import org.jboss.errai.ui.nav.client.local.api.LoginPage;
import org.jboss.errai.ui.nav.client.local.api.SecurityError;
import org.jboss.errai.ui.nav.client.local.spi.PageNode;
import org.jboss.errai.ui.nav.rebind.NavigationGraphGenerator;
import org.jboss.errai.ui.shared.api.style.StyleBindingsRegistry;
import org.slf4j.Logger;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.Multimap;
/**
* @author Max Barkley <mbarkley@redhat.com>
*/
@EntryPoint
public class SecurityContextImpl implements SecurityContext {
/**
* This page exists so that the existence of pages with {@link LoginPage} and
* {@link SecurityError} roles can be enforced at compile-time. Currently the
* {@link NavigationGraphGenerator} only scans {@link Page} annotated classes for transitions. For
* performance reasons, this is preferable to scanning the whole classpath.
*/
@Page
public static class SecurityRolesConstraintPage implements IsElement {
@SuppressWarnings("unused")
@Inject private TransitionToRole<LoginPage> loginTransition;
@SuppressWarnings("unused")
@Inject private TransitionToRole<SecurityError> securityErrorTransition;
@Inject private Div element;
@Override
public HTMLElement getElement() {
return element;
}
}
private static class PageCache {
final Class<?> pageClass;
final Multimap<String, String> pageState;
PageCache(final Class<?> pageClass, final Multimap<String, String> pageState) {
this.pageClass = pageClass;
this.pageState = pageState;
}
}
@Inject
private Event<LoggedInEvent> loginEvent;
@Inject
private Event<LoggedOutEvent> logoutEvent;
@Inject
private Navigation navigation;
@Inject
private ActiveUserCache userCache;
@Inject
private Logger logger;
@Inject
private Caller<NonCachingUserService> userServiceCaller;
private PageCache lastPageCache;
@PostConstruct
private void setup() {
performLoginStatusChangeActions(userCache.getUser());
InitVotes.waitFor(SecurityContext.class);
InitVotes.registerOneTimeDependencyCallback(ClientMessageBus.class, new Runnable() {
@Override
public void run() {
if (((ClientMessageBusImpl) ErraiBus.get()).getState() == BusState.CONNECTED) {
initializeCacheFromServer();
}
else {
// Don't cause initialization to fail if remote communication is disabled
InitVotes.voteFor(SecurityContext.class);
}
}
});
}
private void initializeCacheFromServer() {
logger.debug("Attempting to initialize User cache from server.");
userServiceCaller.call(new RemoteCallback<User>() {
@Override
public void callback(final User user) {
logger.debug("Response received. Initializing user to " + String.valueOf(user));
setCachedUser(user);
InitVotes.voteFor(SecurityContext.class);
}
}, new ErrorCallback<Object>() {
@Override
public boolean error(final Object message, final Throwable throwable) {
logger.warn("Error received while attempting to populate cache: " + throwable.getMessage());
InitVotes.voteFor(SecurityContext.class);
return false;
}
}).getUser();
}
private Class<?> getCurrentPage() {
if (navigation.getCurrentPage() != null) {
return navigation.getCurrentPage().contentType();
}
else {
// Guaranteed to exist at compile-time.
return navigation.getPagesByRole(DefaultPage.class).iterator().next().contentType();
}
}
private Multimap<String, String> getCurrentPageState() {
return navigation.getCurrentState();
}
@Override
public Navigation getNavigation() {
return navigation;
}
@Override
public void redirectToLoginPage() {
redirectToLoginPage(getCurrentPage(), getCurrentPageState());
}
@Override
public void redirectToLoginPage(final Class<?> fromPage) {
redirectToLoginPage(fromPage, ImmutableMultimap.of());
}
@Override
public void redirectToLoginPage(final Class<?> fromPage, final Multimap<String, String> fromState) {
lastPageCache = new PageCache(fromPage, fromState);
navigation.goToWithRole(LoginPage.class);
}
@Override
public void redirectToSecurityErrorPage() {
redirectToSecurityErrorPage(getCurrentPage(), getCurrentPageState());
}
@Override
public void redirectToSecurityErrorPage(final Class<?> fromPage) {
redirectToSecurityErrorPage(fromPage, ImmutableMultimap.of());
}
@Override
public void redirectToSecurityErrorPage(final Class<?> fromPage, final Multimap<String, String> fromState) {
lastPageCache = new PageCache(fromPage, fromState);
navigation.goToWithRole(SecurityError.class);
}
@Override
public void invalidateCache() {
if (userCache.isValid()) {
// User must be updated before style bindings updated.
userCache.invalidateCache();
performLoginStatusChangeActions(userCache.getUser());
}
}
private void performLoginStatusChangeActions(final User user) {
StyleBindingsRegistry.get().updateStyles();
if (user == null) {
throw new RuntimeException("The current user should never be null.");
} else if (User.ANONYMOUS.equals(user)) {
logoutEvent.fire(new LoggedOutEvent());
}
else {
loginEvent.fire(new LoggedInEvent(user));
}
}
@Override
public void navigateBackOrToPage(final Class<?> pageType) {
navigateBackOrToPage(pageType, ImmutableMultimap.of());
}
@Override
public void navigateBackOrToPage(final Class<?> pageType, final Multimap<String, String> pageState) {
if (lastPageCache != null) {
navigation.goTo(lastPageCache.pageClass, lastPageCache.pageState);
lastPageCache = null;
}
else {
navigation.goTo(pageType, ImmutableListMultimap.<String, String>of());
}
}
@Override
public void navigateBackOrHome() {
// Guaranteed to exist at compile-time.
final PageNode<?> defaultPageNode = navigation.getPagesByRole(DefaultPage.class).iterator().next();
navigateBackOrToPage(defaultPageNode.contentType());
}
@Override
public boolean hasCachedUser() {
return userCache.hasUser();
}
@Override
public User getCachedUser() {
return userCache.getUser();
}
@Override
public void setCachedUser(final User user) {
// User must be updated before style bindings updated.
userCache.setUser(user);
performLoginStatusChangeActions(user);
}
@Override
public boolean isUserCacheValid() {
return userCache.isValid();
}
}