Skip to content
This repository has been archived by the owner on Feb 9, 2021. It is now read-only.

RHBPMS-473: Set TCCL to workaround login failures with JSM enabled #608

Merged
merged 1 commit into from Jan 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -16,6 +16,20 @@

package org.uberfire.backend.server.security;

import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Alternative;
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.LoginContext;
import javax.security.auth.login.LoginException;

import org.jboss.errai.security.shared.api.Role;
import org.jboss.errai.security.shared.api.identity.User;
import org.jboss.errai.security.shared.api.identity.UserImpl;
Expand All @@ -24,16 +38,6 @@
import org.uberfire.backend.server.security.adapter.GroupAdapterAuthorizationSource;
import org.uberfire.commons.validation.PortablePreconditions;

import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Alternative;
import javax.security.auth.callback.*;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;

/**
* Implements stateful, thread-local authentication of a user via the JAAS API (
* {@code javax.security.auth.login.LoginContext}).
Expand All @@ -42,7 +46,8 @@
* authenticated user. This association is only undone upon a call to {@link #logout()}. This is appropriate for use
* with the Git SSH daemon, but would cause serious security issues if used for authenticating HTTP requests.
*/
@ApplicationScoped @Alternative
@ApplicationScoped
@Alternative
public class JAASAuthenticationService extends GroupAdapterAuthorizationSource implements AuthenticationService {

public static final String DEFAULT_DOMAIN = "ApplicationRealm";
Expand All @@ -59,21 +64,49 @@ public JAASAuthenticationService(String domain) {
}

@Override
public User login( String username, String password ) {
try {
final LoginContext loginContext = createLoginContext( username, password );
loginContext.login();
List<String> principals = loadEntitiesFromSubjectAndAdapters( username, loginContext.getSubject(), new String[] { rolePrincipleName } );
Collection<Role> roles = getRoles( principals );
Collection<org.jboss.errai.security.shared.api.Group> groups = getGroups( principals );
UserImpl user = new UserImpl( username, roles, groups );
userOnThisThread.set( user );
return user;
} catch ( final LoginException ex ) {
throw new FailedAuthenticationException();
public User login( final String username,
final String password ) {
final SecurityManager jsm = System.getSecurityManager();

if ( jsm != null ) {
final ClassLoader tccl = Thread.currentThread().getContextClassLoader();
final ClassLoader cl = this.getClass().getClassLoader();
try {
// RHBPMS-473 - TCCL used in javax.security.auth.login.LoginContext
// is not the application CL if JSM is enabled.
// Setting TCCL to application CL as workaround
Thread.currentThread().setContextClassLoader( cl );

return executeLogin( username, password );
} catch ( final LoginException ex ) {
throw new FailedAuthenticationException();
} finally {
// RHBPMS-473 - Restore original TCCL
if ( tccl != null ) {
Thread.currentThread().setContextClassLoader( tccl );
}
}
} else {
try {
return executeLogin( username, password );
} catch ( final LoginException ex ) {
throw new FailedAuthenticationException();
}
}
}

private User executeLogin( final String username,
final String password ) throws LoginException {
final LoginContext loginContext = createLoginContext( username, password );
loginContext.login();
List<String> principals = loadEntitiesFromSubjectAndAdapters( username, loginContext.getSubject(), new String[]{ rolePrincipleName } );
Collection<Role> roles = getRoles( principals );
Collection<org.jboss.errai.security.shared.api.Group> groups = getGroups( principals );
UserImpl user = new UserImpl( username, roles, groups );
userOnThisThread.set( user );
return user;
}

@Override
public void logout() {
userOnThisThread.remove();
Expand Down Expand Up @@ -127,4 +160,4 @@ public void handle( final Callback[] callbacks ) throws IOException, Unsupported
}
}

}
}
@@ -1,27 +1,42 @@
package org.uberfire.backend.server.security;

import org.jboss.errai.security.shared.api.GroupImpl;
import org.jboss.errai.security.shared.api.RoleImpl;
import org.jboss.errai.security.shared.api.identity.User;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.runners.MockitoJUnitRunner;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.same;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.inOrder;
import static org.powermock.api.mockito.PowerMockito.mockStatic;

import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import java.security.Principal;
import java.security.acl.Group;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;

import static org.junit.Assert.*;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.*;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;

@RunWith(MockitoJUnitRunner.class)
import org.jboss.errai.security.shared.api.GroupImpl;
import org.jboss.errai.security.shared.api.RoleImpl;
import org.jboss.errai.security.shared.api.identity.User;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

@RunWith(PowerMockRunner.class)
@PrepareForTest({ JAASAuthenticationService.class})
@PowerMockIgnore("javax.security.*")
public class JAASAuthenticationServiceTest {

private JAASAuthenticationService tested;
Expand Down Expand Up @@ -64,6 +79,35 @@ public void testLogin() throws Exception {
assertEquals( 1, user.getGroups().size() );
assertTrue( user.getGroups().contains( new GroupImpl( "group1" ) ) );
}

@Test
public void testLoginSwitchesClassloaderForJsm() throws Exception {
mockStatic( JAASAuthenticationService.class );
mockStatic( Thread.class );
mockStatic( System.class );

final ClassLoader tccl = mock( ClassLoader.class );
final Thread thread = mock( Thread.class );

when( Thread.currentThread() ).thenReturn( thread );
when( System.getSecurityManager() ).thenReturn( mock( SecurityManager.class ) );
when( thread.getContextClassLoader() ).thenReturn( tccl );

String username = "user1";
String password = "password1";
Set<Principal> principals = mockPrincipals( "admin", "role1", "group1" );
Subject subject = new Subject();
subject.getPrincipals().addAll( principals );
LoginContext loginContext = mock( LoginContext.class );
when( loginContext.getSubject() ).thenReturn( subject );
doReturn( loginContext ).when( tested ).createLoginContext( anyString(), anyString() );

tested.login( username, password );

InOrder inOrder = inOrder(thread);
inOrder.verify( thread ).setContextClassLoader( tested.getClass().getClassLoader() );
inOrder.verify( thread ).setContextClassLoader( same( tccl ) );
}

@Test
public void testLoginSubjectGroups() throws Exception {
Expand Down