/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.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 Lesser General Public License for more details.
*
* Copyright 2016 Pentaho Corporation. All rights reserved.
*/
package org.pentaho.platform.osgi;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
import org.pentaho.platform.api.engine.IAuthorizationPolicy;
import org.pentaho.platform.api.engine.IUserRoleListService;
import org.pentaho.platform.engine.core.system.PentahoSystem;
import org.pentaho.platform.security.policy.rolebased.actions.AdministerSecurityAction;
import org.springframework.security.core.Authentication;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginException;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
public class SpringSecurityLoginModuleTest {
private static class AuthenticationManagerMatcher extends ArgumentMatcher<Authentication> {
private String user;
public AuthenticationManagerMatcher( String user ) {
this.user = user;
}
public boolean matches( Object auth ) {
return auth instanceof Authentication && ( (Authentication) auth ).getName().equals( user );
}
}
@Test
public void testLogin() throws Exception {
// instances and mocks
Subject subject = new Subject();
TestCallbackHandler testCallbackHandler = new TestCallbackHandler( "joe" );
SpringSecurityLoginModule loginModule = new SpringSecurityLoginModule();
AuthenticationManager authenticationManager = mock( AuthenticationManager.class );
IUserRoleListService userRoleListService = mock( IUserRoleListService.class );
IAuthorizationPolicy authorizationPolicy = mock( IAuthorizationPolicy.class );
Authentication authentication = mock( Authentication.class );
Collection authorities = Arrays.asList( new GrantedAuthority[] { new SimpleGrantedAuthority( "Authenticated" ),
new SimpleGrantedAuthority( "Administrator" ) } );
Authentication authentication2 = mock( Authentication.class );
Collection authorities2 = Arrays.asList(
new GrantedAuthority[] { new SimpleGrantedAuthority( "Authenticated" ), new SimpleGrantedAuthority( "ceo" ) } );
//
PentahoSystem.registerObject( userRoleListService, IUserRoleListService.class );
when( authorizationPolicy.isAllowed( AdministerSecurityAction.NAME ) ).thenReturn( true ).thenReturn( true )
.thenReturn( false );
when( authentication.getAuthorities() ).thenReturn( authorities );
when( authentication.getName() ).thenReturn( "joe" );
when( authentication.isAuthenticated() ).thenReturn( true );
when( authentication2.getAuthorities() ).thenReturn( authorities2 );
when( authentication2.getName() ).thenReturn( "pat" );
when( authentication2.isAuthenticated() ).thenReturn( true );
when( authenticationManager.authenticate( argThat( new AuthenticationManagerMatcher( "joe" ) ) ) ).thenReturn(
authentication );
when( authenticationManager.authenticate( argThat( new AuthenticationManagerMatcher( "pat" ) ) ) ).thenReturn(
authentication );
when( authenticationManager.authenticate( argThat( new AuthenticationManagerMatcher( "suzy" ) ) ) )
.thenThrow( new UsernameNotFoundException( "Error" ) );
when( userRoleListService.getRolesForUser( null, "joe" ) ).thenReturn( Arrays
.<String>asList( "Authenticated", "Administrator" ) );
when( userRoleListService.getRolesForUser( null, "pat" ) ).thenReturn( Arrays
.<String>asList( "Authenticated", "ceo" ) );
loginModule.setAuthenticationManager( authenticationManager );
loginModule.setAuthorizationPolicy( authorizationPolicy );
// start tests
loginModule.initialize( subject, testCallbackHandler, Collections.emptyMap(), Collections.emptyMap() );
loginModule.login();
loginModule.commit();
// joe should get the extra karaf_admin role
verify( authenticationManager ).authenticate( argThat( new AuthenticationManagerMatcher( "joe" ) ) );
assertEquals( 4, subject.getPrincipals().size() );
subject.getPrincipals().toArray()[ 3 ].equals( "karaf_admin" );
loginModule.logout();
assertEquals( 0, subject.getPrincipals().size() );
loginModule.login();
loginModule.commit();
assertEquals( 4, subject.getPrincipals().size() );
// Suzy is not found
testCallbackHandler = new TestCallbackHandler( "suzy" );
loginModule.initialize( subject, testCallbackHandler, Collections.emptyMap(), Collections.emptyMap() );
try {
loginModule.login();
fail( "Should have thrown a UsernameNotFoundException exception" );
} catch ( LoginException ex ) {
/* No-op */
}
// pat is found, but not an admin
testCallbackHandler = new TestCallbackHandler( "pat" );
loginModule.initialize( subject, testCallbackHandler, Collections.emptyMap(), Collections.emptyMap() );
loginModule.logout();
loginModule.login();
loginModule.commit();
assertEquals( 3, subject.getPrincipals().size() );
assertTrue( loginModule.abort() );
}
@Test
public void testExceptions() throws Exception {
// clear any authentication
SecurityContextHolder.getContext().setAuthentication( null );
Subject subject = new Subject();
TestCallbackHandler testCallbackHandler = new TestCallbackHandler( "joe" );
SpringSecurityLoginModule loginModule = new SpringSecurityLoginModule();
AuthenticationManager authenticationManager = mock( AuthenticationManager.class );
IUserRoleListService userRoleListService = mock( IUserRoleListService.class );
IAuthorizationPolicy authorizationPolicy = mock( IAuthorizationPolicy.class );
Authentication authentication = mock( Authentication.class );
Collection authorities = Arrays.asList( new GrantedAuthority[] { new SimpleGrantedAuthority( "Authenticated" ),
new SimpleGrantedAuthority( "Administrator" ) } );
Authentication authentication2 = mock( Authentication.class );
Collection authorities2 = Arrays.asList(
new GrantedAuthority[] { new SimpleGrantedAuthority( "Authenticated" ), new SimpleGrantedAuthority( "ceo" ) } );
PentahoSystem.registerObject( userRoleListService, IUserRoleListService.class );
when( authorizationPolicy.isAllowed( AdministerSecurityAction.NAME ) ).thenReturn( true ).thenReturn( true )
.thenReturn( false );
when( authentication.getAuthorities() ).thenReturn( authorities );
when( authentication.getName() ).thenReturn( "joe" );
when( authentication.isAuthenticated() ).thenReturn( true );
when( authentication2.getAuthorities() ).thenReturn( authorities2 );
when( authentication2.getName() ).thenReturn( "pat" );
when( authentication2.isAuthenticated() ).thenReturn( true );
when( authenticationManager.authenticate( argThat( new AuthenticationManagerMatcher( "joe" ) ) ) ).thenReturn(
authentication );
when( authenticationManager.authenticate( argThat( new AuthenticationManagerMatcher( "pat" ) ) ) ).thenReturn(
authentication );
when( authenticationManager.authenticate( argThat( new AuthenticationManagerMatcher( "suzy" ) ) ) )
.thenThrow( new UsernameNotFoundException( "Error" ) );
when( userRoleListService.getRolesForUser( null, "joe" ) ).thenReturn( Arrays
.<String>asList( "Authenticated", "Administrator" ) );
when( userRoleListService.getRolesForUser( null, "pat" ) ).thenReturn( Arrays
.<String>asList( "Authenticated", "ceo" ) );
loginModule.setAuthenticationManager( authenticationManager );
loginModule.setAuthorizationPolicy( authorizationPolicy );
// test a successful run
loginModule.initialize( subject, testCallbackHandler, Collections.emptyMap(), Collections.emptyMap() );
loginModule.login();
loginModule.commit();
verify( authenticationManager ).authenticate( argThat( new AuthenticationManagerMatcher( "joe" ) ) );
assertEquals( 4, subject.getPrincipals().size() );
subject.getPrincipals().toArray()[ 3 ].equals( "karaf_admin" );
// now test exceptions
// Test with Authentication bound to thread
testCallbackHandler = new TestCallbackHandler( "ioe" );
loginModule.initialize( subject, testCallbackHandler, Collections.emptyMap(), Collections.emptyMap() );
try {
loginModule.login();
fail( "Should have thrown IOException" );
} catch ( LoginException ioe ) {
/* No-op */
}
// UnsupportedCallbackException thrown by underlying system
testCallbackHandler = new TestCallbackHandler( "unsupported" );
loginModule.initialize( subject, testCallbackHandler, Collections.emptyMap(), Collections.emptyMap() );
try {
loginModule.login();
fail( "Should have thrown UnsupportedCallbackException" );
} catch ( LoginException ioe ) {
/* No-op */
}
SecurityContextHolder.getContext().setAuthentication( null );
// IOException thrown by underlying system
testCallbackHandler = new TestCallbackHandler( "ioe" );
loginModule.initialize( subject, testCallbackHandler, Collections.emptyMap(), Collections.emptyMap() );
try {
loginModule.login();
fail( "Should have thrown IOException" );
} catch ( LoginException ioe ) {
/* No-op */
}
testCallbackHandler = new TestCallbackHandler( "unsupported" );
loginModule.initialize( subject, testCallbackHandler, Collections.emptyMap(), Collections.emptyMap() );
try {
loginModule.login();
fail( "Should have thrown UnsupportedCallbackException" );
} catch ( LoginException ioe ) {
/* No-op */
}
}
private static class TestCallbackHandler implements CallbackHandler {
private String name;
private TestCallbackHandler( String name ) {
this.name = name;
}
@Override public void handle( Callback[] callbacks ) throws IOException, UnsupportedCallbackException {
// Eceptionals
if ( name.equals( "ioe" ) ) {
throw new IOException();
} else if ( name.equals( "unsupported" ) ) {
throw new UnsupportedCallbackException( callbacks[ 0 ] );
}
NameCallback nameCallback = (NameCallback) callbacks[ 0 ];
PasswordCallback passwordCallback = ( callbacks.length > 1 ) ? (PasswordCallback) callbacks[ 1 ] : null;
nameCallback.setName( name );
if ( passwordCallback != null ) {
passwordCallback.setPassword( "password".toCharArray() );
}
}
}
}