/* * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU General Public License, version 2 as published by the Free Software * Foundation. * * You should have received a copy of the GNU General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/gpl-2.0.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * * * Copyright 2006 - 2016 Pentaho Corporation. All rights reserved. */ package org.pentaho.platform.repository2.unified.jcr.jackrabbit.security; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.jackrabbit.core.security.authentication.AbstractLoginModule; import org.apache.jackrabbit.core.security.authentication.Authentication; import org.pentaho.platform.engine.core.system.PentahoSystem; import org.pentaho.platform.repository2.unified.jcr.jackrabbit.security.messages.Messages; import org.springframework.security.authentication.ProviderNotFoundException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import javax.jcr.Credentials; import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.SimpleCredentials; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.login.LoginException; import java.security.Principal; import java.security.acl.Group; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * A Jackrabbit {@code LoginModule} that delegates to a Spring Security {@link AuthenticationManager}. Also, adds * more checks to the pre-authentication scenario. * * @author mlowery */ public class SpringSecurityLoginModule extends AbstractLoginModule { // ~ Static fields/initializers // ====================================================================================== private static final Log logger = LogFactory.getLog( SpringSecurityLoginModule.class ); /** * Comma separated list of known tokens. If a Credentials instance has a preauthentication token, it must match * one of the values in this list. Ideally, there is a token per application. In this way, other applications are * unaffected should a token have to be blacklisted. */ private static final String KEY_PRE_AUTHENTICATION_TOKENS = "preAuthenticationTokens"; //$NON-NLS-1$ private static final String PRE_AUTHENTICATION_TOKEN_SEPARATOR = ","; //$NON-NLS-1$ /** * When there's no AuthenticationManager available in PentahoSystem, this one will be returned. * It's sole purpose is to throw an Exception whenever an Authentication attempt is made so a * NPE doesn't occur. */ protected static final AuthenticationManager NULL_AUTHENTICATION_MANAGER = new AuthenticationManager() { @Override public org.springframework.security.core.Authentication authenticate( org.springframework.security.core.Authentication authentication ) throws AuthenticationException { throw new ProviderNotFoundException( "Authentication Manager not present in PentahoSystem." ); } }; // ~ Instance fields // ================================================================================================= private AuthenticationManager authenticationManager; private static Set<String> preAuthenticationTokens = new HashSet<String>(); // ~ Constructors // ==================================================================================================== public SpringSecurityLoginModule() { super(); } // ~ Methods // ========================================================================================================= /** * {@inheritDoc} */ @Override protected void doInit( final CallbackHandler callbackHandler, final Session session, final Map options ) throws LoginException { if ( options.containsKey( KEY_PRE_AUTHENTICATION_TOKENS ) ) { String preAuthenticationTokensString = (String) options.get( KEY_PRE_AUTHENTICATION_TOKENS ); String[] tokens = preAuthenticationTokensString.split( PRE_AUTHENTICATION_TOKEN_SEPARATOR ); if ( tokens.length == 0 ) { throw new LoginException( Messages.getInstance().getString( "AbstractPentahoLoginModule.ERROR_0001_PRE_AUTH_TOKENS_MALFORMED", KEY_PRE_AUTHENTICATION_TOKENS ) ); //$NON-NLS-1$ } for ( String token : tokens ) { preAuthenticationTokens.add( token.trim() ); } logger.debug( "preAuthenticationTokens=" + preAuthenticationTokens ); //$NON-NLS-1$ } } // Static caching as this class is instanced over and over protected static AuthenticationManager authManager = null; protected AuthenticationManager getAuthenticationManager( ) { if ( authManager == null && PentahoSystem.getInitializedOK() ) { authManager = PentahoSystem.get( AuthenticationManager.class ); } if ( authManager == null ) { return NULL_AUTHENTICATION_MANAGER; } return authManager; } /** * {@inheritDoc} * * Creates a {@code UsernamePasswordAuthenticationToken} from the given {@code principal} and {@code credentials} * and passes to Spring Security {@code AuthenticationManager}. */ @Override protected Authentication getAuthentication( final Principal principal, final Credentials credentials ) throws RepositoryException { // only handles SimpleCredential instances; DefaultLoginModule behaves the same way (albeit indirectly) if ( !( credentials instanceof SimpleCredentials ) ) { logger.debug( "credentials not instance of SimpleCredentials; returning null" ); //$NON-NLS-1$ return null; } SimpleCredentials simpleCredentials = (SimpleCredentials) credentials; UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken( simpleCredentials.getUserID(), String.valueOf( simpleCredentials .getPassword() ) ); boolean authenticated = false; try { org.springframework.security.core.Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if ( authentication != null && authentication.getName().equals( simpleCredentials.getUserID() ) ) { // see if there's already an active Authentication for this user. authenticated = true; } else { // delegate to Spring Security getAuthenticationManager().authenticate( token ); authenticated = true; } } catch ( AuthenticationException e ) { logger.debug( "authentication exception", e ); //$NON-NLS-1$ } final boolean authenticateResult = authenticated; return new Authentication() { public boolean canHandle( Credentials credentials ) { // this is decided earlier in getAuthentication return true; } public boolean authenticate( Credentials credentials ) throws RepositoryException { return authenticateResult; } }; } /** * {@inheritDoc} * * <p> * Implementation copied from {@link org.apache.jackrabbit.core.security.simple.SimpleLoginModule}. Delegates to * a {@code PrincipalProvider}. * </p> */ @Override protected Principal getPrincipal( final Credentials credentials ) { String userId = getUserID( credentials ); Principal principal = principalProvider.getPrincipal( userId ); if ( principal == null || principal instanceof Group ) { // no matching user principal return null; } else { return principal; } } /** * {@inheritDoc} * * <p> * Not implemented. * </p> */ @Override protected boolean impersonate( final Principal principal, final Credentials credentials ) throws RepositoryException, LoginException { throw new UnsupportedOperationException(); } @Override protected boolean isPreAuthenticated( final Credentials creds ) { if ( super.isPreAuthenticated( creds ) ) { SimpleCredentials simpleCreds = (SimpleCredentials) creds; String preAuth = (String) simpleCreds.getAttribute( getPreAuthAttributeName() ); boolean preAuthenticated = preAuthenticationTokens.contains( preAuth ); if ( preAuthenticated ) { if ( logger.isDebugEnabled() ) { logger.debug( simpleCreds.getUserID() + " is pre-authenticated" ); //$NON-NLS-1$ } } else { if ( logger.isDebugEnabled() ) { logger.debug( "pre-authentication token rejected" ); //$NON-NLS-1$ } } return preAuthenticated; } return false; } }