package fr.openwide.core.wicket.more;
import java.util.Collection;
import java.util.Locale;
import org.apache.wicket.Component;
import org.apache.wicket.RestartResponseAtInterceptPageException;
import org.apache.wicket.Session;
import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
import org.apache.wicket.authroles.authorization.strategies.role.Roles;
import org.apache.wicket.injection.Injector;
import org.apache.wicket.model.IModel;
import org.apache.wicket.request.Request;
import org.apache.wicket.spring.injection.annot.SpringBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.intercept.RunAsUserToken;
import org.springframework.security.acls.model.Permission;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import com.google.common.collect.Lists;
import fr.openwide.core.jpa.exception.SecurityServiceException;
import fr.openwide.core.jpa.exception.ServiceException;
import fr.openwide.core.jpa.security.business.authority.util.CoreAuthorityConstants;
import fr.openwide.core.jpa.security.business.person.model.GenericUser;
import fr.openwide.core.jpa.security.business.person.service.IGenericUserService;
import fr.openwide.core.jpa.security.config.spring.DefaultJpaSecurityConfig;
import fr.openwide.core.jpa.security.model.NamedPermission;
import fr.openwide.core.jpa.security.service.IAuthenticationService;
import fr.openwide.core.spring.property.service.IPropertyService;
import fr.openwide.core.wicket.more.link.descriptor.IPageLinkDescriptor;
import fr.openwide.core.wicket.more.model.threadsafe.SessionThreadSafeGenericEntityModel;
public abstract class AbstractCoreSession<U extends GenericUser<U, ?>> extends AuthenticatedWebSession {
private static final long serialVersionUID = 2591467597835056981L;
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractCoreSession.class);
private static final String REDIRECT_URL_ATTRIBUTE_NAME = "redirectUrl";
private static final String REDIRECT_PAGE_LINK_DESCRIPTOR_ATTRIBUTE_NAME = "redirectPageLinkDescriptor";
@SpringBean(name="personService")
protected IGenericUserService<U> userService;
@SpringBean(name="authenticationService")
protected IAuthenticationService authenticationService;
@SpringBean(name="authenticationManager")
protected AuthenticationManager authenticationManager;
@SpringBean(name="propertyService")
protected IPropertyService propertyService;
private final IModel<U> userModel = new SessionThreadSafeGenericEntityModel<Long, U>();
private final IModel<Locale> localeModel = new IModel<Locale>() {
private static final long serialVersionUID = -4356509005738585888L;
@Override
public Locale getObject() {
return AbstractCoreSession.this.getLocale();
}
@Override
public void setObject(Locale object) {
AbstractCoreSession.this.setLocale(object);
}
@Override
public void detach() {
// Nothing to do
}
};
private Roles roles = new Roles();
private boolean rolesInitialized = false;
private Collection<? extends Permission> permissions = Lists.newArrayList();
private boolean permissionsInitialized = false;
private boolean isSuperUser = false;
private boolean isSuperUserInitialized = false;
public AbstractCoreSession(Request request) {
super(request);
Injector.get().inject(this);
// Override browser locale with mapped locale
// setLocale process locale to map to one available locale
setLocale(getLocale());
}
public static AbstractCoreSession<?> get() {
return (AbstractCoreSession<?>) Session.get();
}
/**
* Attempts to authenticate a user that has provided the given username and
* password.
*
* @param username
* current username
* @param password
* current password
* @return <code>true</code> if authentication succeeds, throws an exception if not
*
* @throws BadCredentialsException if password doesn't match with username
* @throws UsernameNotFoundException if user name was not found
* @throws DisabledException if user was found but disabled
*/
@Override
public boolean authenticate(String username, String password)
throws BadCredentialsException, UsernameNotFoundException, DisabledException {
doAuthenticate(username, password);
doInitializeSession();
return true;
}
protected Authentication doAuthenticate(String username, String password) {
Authentication authentication = authenticationManager
.authenticate(new UsernamePasswordAuthenticationToken(username, password));
SecurityContextHolder.getContext().setAuthentication(authentication);
return authentication;
}
protected void doInitializeSession() {
U user = userService.getByUserName(authenticationService.getUserName());
if (user == null) {
throw new IllegalStateException("Unable to find the signed in user.");
}
userModel.setObject(user);
try {
if (user.getLastLoginDate() == null) {
onFirstLogin(user);
}
userService.updateLastLoginDate(user);
Locale locale = user.getLocale();
if (locale != null) {
setLocale(user.getLocale());
} else {
// si la personne ne possède pas de locale
// alors on enregistre celle mise en place
// automatiquement par le navigateur.
userService.updateLocale(user, getLocale());
}
} catch (RuntimeException | ServiceException | SecurityServiceException e) {
LOGGER.error(String.format("Unable to update the user information on sign in: %1$s", user), e);
}
Collection<? extends GrantedAuthority> authorities = authenticationService.getAuthorities();
roles = new Roles();
for (GrantedAuthority authority : authorities) {
roles.add(authority.getAuthority());
}
rolesInitialized = true;
permissions = authenticationService.getPermissions();
permissionsInitialized = true;
isSuperUser = authenticationService.isSuperUser();
isSuperUserInitialized = true;
}
protected void onFirstLogin(U user) {
}
public IModel<U> getUserModel() {
return userModel;
}
/**
* @return the currently logged in user, or null when no user is logged in
*/
public String getUserName() {
String userName = null;
if (isSignedIn()) {
userName = userModel.getObject().getUserName();
}
return userName;
}
public U getUser() {
U person = null;
if (isSignedIn()) {
person = userModel.getObject();
}
return person;
}
/**
* Returns the current user roles.
*
* @return current user roles
*/
@Override
public Roles getRoles() {
if (!rolesInitialized) {
Collection<? extends GrantedAuthority> authorities = authenticationService.getAuthorities();
for (GrantedAuthority authority : authorities) {
roles.add(authority.getAuthority());
}
rolesInitialized = true;
}
return roles;
}
public boolean hasRole(String authority) {
return getRoles().contains(authority);
}
public boolean hasRoleAdmin() {
return hasRole(CoreAuthorityConstants.ROLE_ADMIN);
}
public boolean hasRoleAuthenticated() {
return hasRole(CoreAuthorityConstants.ROLE_AUTHENTICATED);
}
public boolean hasRoleSystem() {
return hasRole(CoreAuthorityConstants.ROLE_SYSTEM);
}
public boolean hasRoleAnonymous() {
return hasRole(CoreAuthorityConstants.ROLE_ANONYMOUS);
}
protected Collection<? extends Permission> getPermissions() {
if (!permissionsInitialized) {
permissions = authenticationService.getPermissions();
permissionsInitialized = true;
}
return permissions;
}
public boolean hasPermission(Permission permission) {
if (isSuperUser()) {
return true;
}
return getPermissions().contains(permission);
}
protected boolean isSuperUser() {
if (!isSuperUserInitialized) {
isSuperUser = authenticationService.isSuperUser();
isSuperUserInitialized = true;
}
return isSuperUser;
}
/**
* Sign out the user. If you want to completely invalidate the session, call invalidate() instead.
* After a signout, you should redirect the browser to the home or sign in page.
*/
@Override
public void signOut() {
signOutWithoutCleaningUpRedirectUrl();
removeAttribute(REDIRECT_URL_ATTRIBUTE_NAME);
}
/**
* @deprecated Only useful when using OWSI-Core's redirection mechanism, which is deprecated.
*
* @see {@link #registerRedirectUrl(String)} for information about alternative mechanisms.
*/
@Deprecated
public void signOutWithoutCleaningUpRedirectUrl() {
userModel.setObject(null);
roles = new Roles();
rolesInitialized = false;
permissions = Lists.newArrayList();
permissionsInitialized = false;
authenticationService.signOut();
super.signOut();
}
/**
* @deprecated This was OWSI-Core's own redirection mechanism, which is now deprecated in favor of more standard
* mechanisms.
* You may use instead:
* <ul>
* <li>Wicket's
* {@link RestartResponseAtInterceptPageException} mechanism
* ({@link Component#redirectToInterceptPage(org.apache.wicket.Page)},
* {@link Component#continueToOriginalDestination()}), if redirecting within the same session (without login/logout
* during the redirection process).
* <li>Spring Security's saved requests mechanisms, triggered by an {@link AccessDeniedException}, if redirecting
* after an authentication/authorization error. Beware that most cases are already handled in OWSI-Core through the
* {@link CoreDefaultExceptionMapper}, so you normally shouldn't have to do this.
* <li>Or you own implementation with an URL as a page parameter, for the most specific needs.
* </ul>
*/
@Deprecated
public void registerRedirectUrl(String url) {
// le bind() est obligatoire pour demander à wicket de persister la session
// si on ne le fait pas, la session possède comme durée de vie le temps de
// la requête.
if (isTemporary()) {
bind();
}
setAttribute(REDIRECT_URL_ATTRIBUTE_NAME, url);
}
/**
* @deprecated This was OWSI-Core's own redirection mechanism, which is now deprecated in favor of more standard
* mechanisms.
* @see {@link #registerRedirectUrl(String)} for information about alternative mechanisms.
*/
public String getRedirectUrl() {
return (String) getAttribute(REDIRECT_URL_ATTRIBUTE_NAME);
}
/**
* @deprecated This was OWSI-Core's own redirection mechanism, which is now deprecated in favor of more standard
* mechanisms.
* @see {@link #registerRedirectUrl(String)} for information about alternative mechanisms.
*/
public String consumeRedirectUrl() {
String redirectUrl = getRedirectUrl();
removeAttribute(REDIRECT_URL_ATTRIBUTE_NAME);
return redirectUrl;
}
/**
* @deprecated This was OWSI-Core's own redirection mechanism, which is now deprecated in favor of more standard
* mechanisms.
* @see {@link #registerRedirectUrl(String)} for information about alternative mechanisms.
*/
public void registerRedirectPageLinkDescriptor(IPageLinkDescriptor pageLinkDescriptor) {
// le bind() est obligatoire pour demander à wicket de persister la session
// si on ne le fait pas, la session possède comme durée de vie le temps de
// la requête.
if (isTemporary()) {
bind();
}
setAttribute(REDIRECT_PAGE_LINK_DESCRIPTOR_ATTRIBUTE_NAME, pageLinkDescriptor);
}
/**
* @deprecated This was OWSI-Core's own redirection mechanism, which is now deprecated in favor of more standard
* mechanisms.
* @see {@link #registerRedirectUrl(String)} for information about alternative mechanisms.
*/
public IPageLinkDescriptor getRedirectPageLinkDescriptor() {
IPageLinkDescriptor pageLinkDescriptor = (IPageLinkDescriptor) getAttribute(REDIRECT_PAGE_LINK_DESCRIPTOR_ATTRIBUTE_NAME);
removeAttribute(REDIRECT_PAGE_LINK_DESCRIPTOR_ATTRIBUTE_NAME);
return pageLinkDescriptor;
}
/**
* <p>Override to provide locale mapping to available application locales.</p>
* @return
*/
@Override
public Session setLocale(Locale locale) {
return super.setLocale(propertyService.toAvailableLocale(locale));
}
public IModel<Locale> getLocaleModel() {
return localeModel;
}
@Override
public void detach() {
super.detach();
userModel.detach();
localeModel.detach();
}
@Override
public void internalDetach() {
super.internalDetach();
userModel.detach();
localeModel.detach();
}
@SpringBean
private DefaultJpaSecurityConfig defaultJpaSecurityConfig;
@SpringBean(name = "userDetailsService")
private UserDetailsService userDetailsService;
/**
* Utilisé pour garder l'authentification de l'utilisateur lorsqu'il se connecte en tant qu'un autre utilisateur.
*/
private Authentication originalAuthentication = null;
public Authentication getOriginalAuthentication() {
return originalAuthentication;
}
public boolean hasSignInAsPermissions(U utilisateurConnecte, U utilisateurCible) {
return authenticationService.hasPermission(NamedPermission.ADMIN_SIGN_IN_AS);
}
/**
* @see AbstractCoreSession#authenticate(String, String)
*/
public void signInAs(String username) throws UsernameNotFoundException {
// on charge l'utilisateur
// on le passe dans une méthode surchargeable -> implémentation par défaut à faire
// Sitra -> revoir l'implémentation par défaut
if (!hasSignInAsPermissions(getUser(), userService.getByUserName(username))) {
throw new SecurityException("L'utilisateur n'a pas les permissions nécessaires");
}
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
RunAsUserToken token = new RunAsUserToken(defaultJpaSecurityConfig.getRunAsKey(),
userDetails, "runAs", userDetails.getAuthorities(), null);
// On garde l'authentification de l'utilisateur pour pouvoir lui proposer de se reconnecter.
Authentication previousAuthentication = SecurityContextHolder.getContext().getAuthentication();
if (!(previousAuthentication instanceof AnonymousAuthenticationToken)) {
originalAuthentication = previousAuthentication;
}
signOut();
Authentication authentication = authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authentication);
doInitializeSession();
bind();
signIn(true);
}
public void signInAsMe() throws BadCredentialsException, SecurityException {
if (originalAuthentication == null) {
throw new BadCredentialsException("Pas d'authentification originelle");
}
SecurityContextHolder.getContext().setAuthentication(originalAuthentication);
doInitializeSession();
bind();
signIn(true);
originalAuthentication = null;
}
}