Compare commits

...
8 changed files with 205 additions and 50 deletions

No files matched your search

+26 -14
View File
@@ -11,21 +11,33 @@
<beans:bean id="x509PrincipalExtractor" class="org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor">
<beans:property name="subjectDnRegex" value="CN=(.*?)(?:,|$)"/>
</beans:bean>
</beans:bean>
<beans:bean id="timelyUserDetailsService" class="timely.auth.UserDetailsService">
<beans:property name="users">
<util:map key-type="java.lang.String">
<beans:entry key="CN=example.com">
<util:list>
<beans:value>D</beans:value>
<beans:value>E</beans:value>
<beans:value>F</beans:value>
</util:list>
</beans:entry>
</util:map>
</beans:property>
</beans:bean>
<beans:bean id="timelyUserDetailsService" class="timely.auth.UserDetailsService">
<beans:property name="users">
<util:list>
<beans:bean class="timely.auth.TimelyUser">
<beans:constructor-arg name="subjectDn">
<beans:value>CN=example.com</beans:value>
</beans:constructor-arg>
<beans:constructor-arg name="auths">
<util:list>
<beans:value>D</beans:value>
<beans:value>E</beans:value>
<beans:value>F</beans:value>
</util:list>
</beans:constructor-arg>
<beans:constructor-arg name="roles">
<util:list>
<beans:value>G</beans:value>
<beans:value>H</beans:value>
<beans:value>I</beans:value>
</util:list>
</beans:constructor-arg>
</beans:bean>
</util:list>
</beans:property>
</beans:bean>
<beans:bean id="x509AuthorizationProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<beans:property name="preAuthenticatedUserDetailsService" ref="timelyUserDetailsService" />
+26 -10
View File
@@ -15,15 +15,27 @@
<beans:bean id="timelyUserDetailsService" class="timely.auth.UserDetailsService">
<beans:property name="users">
<util:map key-type="java.lang.String">
<beans:entry key="CN=example.com">
<util:list>
<beans:value>D</beans:value>
<beans:value>E</beans:value>
<beans:value>F</beans:value>
</util:list>
</beans:entry>
</util:map>
<util:list>
<beans:bean class="timely.auth.TimelyUser">
<beans:constructor-arg name="subjectDn">
<beans:value>CN=example.com</beans:value>
</beans:constructor-arg>
<beans:constructor-arg name="auths">
<util:list>
<beans:value>D</beans:value>
<beans:value>E</beans:value>
<beans:value>F</beans:value>
</util:list>
</beans:constructor-arg>
<beans:constructor-arg name="roles">
<util:list>
<beans:value>G</beans:value>
<beans:value>H</beans:value>
<beans:value>I</beans:value>
</util:list>
</beans:constructor-arg>
</beans:bean>
</util:list>
</beans:property>
</beans:bean>
@@ -45,5 +57,9 @@
<!-- Basic authorization provider, validates provided username and password -->
<authentication-provider ref="UserNamePasswordAuthenticationProvider" />
</authentication-manager>
<beans:bean id="requiredRoles" class="Array">
</beans:bean>
</beans:beans>
@@ -6,6 +6,8 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.ServiceConfigurationError;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@@ -26,6 +28,7 @@ import timely.api.request.AuthenticatedRequest;
import timely.api.response.TimelyException;
import timely.auth.util.DnUtils;
import timely.netty.http.auth.TimelyAuthenticationToken;
import timely.netty.http.auth.TimelyUserDetails;
public class AuthenticationService {
@@ -35,12 +38,16 @@ public class AuthenticationService {
private static SubjectDnX509PrincipalExtractor x509 = null;
private static ConcurrentHashMap<String, Object> fetchingEntity = new ConcurrentHashMap<>();
private static ConcurrentHashMap<String, Object> fetchingPrincipal = new ConcurrentHashMap<>();
private static Collection<String> requiredRoles;
private static Collection<String> requiredAuths;
static {
try {
springContext = new ClassPathXmlApplicationContext("security.xml");
authManager = (AuthenticationManager) springContext.getBean("authenticationManager");
x509 = (SubjectDnX509PrincipalExtractor) springContext.getBean("x509PrincipalExtractor");
authManager = springContext.getBean("authenticationManager", AuthenticationManager.class);
x509 = springContext.getBean("x509PrincipalExtractor", SubjectDnX509PrincipalExtractor.class);
requiredRoles = springContext.getBean("requiredRoles", List.class);
requiredAuths = springContext.getBean("requiredAuths", List.class);
} catch (BeansException e) {
throw new ServiceConfigurationError("Error setting up Authentication objects: " + e.getMessage(),
e.getRootCause());
@@ -92,11 +99,13 @@ public class AuthenticationService {
public static void enforceAccess(AuthenticatedRequest request) throws Exception {
String sessionId = request.getSessionId();
X509Certificate clientCert = request.getToken().getClientCert();
TimelyPrincipal verifiedPrincipal = null;
if (StringUtils.isBlank(sessionId) && clientCert == null) {
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "User must log in",
"Anonymous access is disabled. User must either send a client certificate or log in");
} else if (StringUtils.isNotBlank(sessionId)) {
if (!AuthCache.containsKey(sessionId)) {
verifiedPrincipal = AuthCache.get(sessionId);
if (verifiedPrincipal == null) {
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "User must log in",
"Unknown session id was submitted, log in again");
}
@@ -139,6 +148,27 @@ public class AuthenticationService {
} else {
LOG.trace("Verified principal {} in AuthCache on 1st attempt", entity);
}
TimelyUser primaryUser = verifiedPrincipal.getPrimaryUser();
if (requiredRoles != null) {
if (!primaryUser.getRoles().containsAll(requiredRoles)) {
Set<String> missingRoles = new TreeSet<>();
missingRoles.addAll(requiredRoles);
missingRoles.removeAll(primaryUser.getRoles());
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "Access denied",
"User:" + primaryUser.getCommonName() + " is missing auth(s):" + missingRoles);
}
}
if (requiredAuths != null) {
if (!primaryUser.getAuths().containsAll(requiredAuths)) {
Set<String> missingAuths = new TreeSet<>();
missingAuths.addAll(requiredRoles);
missingAuths.removeAll(primaryUser.getAuths());
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "Access denied",
"User:" + primaryUser.getCommonName() + " is missing role(s):" + missingAuths);
}
}
} catch (Exception e) {
LOG.error(e.getMessage(), e);
throw new TimelyException(HttpResponseStatus.UNAUTHORIZED.code(), "Access denied", e.getMessage());
@@ -193,7 +223,12 @@ public class AuthenticationService {
: TimelyUser.UserType.USER;
Collection<String> auths = authentication.getAuthorities().stream().map(a -> a.getAuthority())
.collect(Collectors.toList());
TimelyUser timelyUser = new TimelyUser(subjectIssuerDNPair, userType, auths, null, null,
Object o = authentication.getPrincipal();
Collection<String> roles = null;
if (o instanceof TimelyUserDetails) {
roles = ((TimelyUserDetails) o).getRoles();
}
TimelyUser timelyUser = new TimelyUser(subjectIssuerDNPair, userType, auths, roles, null,
System.currentTimeMillis());
return new TimelyPrincipal(Arrays.asList(timelyUser));
}
@@ -38,6 +38,23 @@ public class TimelyUser implements Serializable {
private final long creationTime;
private final long expirationTime;
/*
* For Spring configuration
*/
public TimelyUser(String subjectDn, Collection<String> auths, Collection<String> roles) {
this.name = subjectDn;
this.commonName = DnUtils.getCommonName(subjectDn);
this.dn = SubjectIssuerDNPair.of(subjectDn);
this.userType = DnUtils.isServerDN(subjectDn) ? UserType.SERVER : UserType.USER;
this.auths = auths == null ? Collections.emptyList() : new LinkedHashSet<>(auths);
this.unmodifiableAuths = Collections.unmodifiableCollection(this.auths);
this.roles = roles == null ? Collections.emptyList() : new LinkedHashSet<>(roles);
this.unmodifiableRoles = Collections.unmodifiableCollection(this.roles);
this.roleToAuthMapping = null;
this.creationTime = System.currentTimeMillis();
this.expirationTime = -1L;
}
/*
* For testing
*/
@@ -3,7 +3,6 @@ package timely.auth;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -11,18 +10,20 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import timely.netty.http.auth.TimelyUserDetails;
public class UserDetailsService implements
org.springframework.security.core.userdetails.AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> {
private HashMap<String, List<String>> users;
private HashMap<String, TimelyUser> users;
public Map<String, List<String>> getUsers() {
public Map<String, TimelyUser> getUsers() {
return users;
}
public void setUsers(LinkedHashMap<String, List<String>> users) {
this.users = new HashMap<>(users);
public void setUsers(List<TimelyUser> users) {
this.users = new HashMap<>();
users.forEach(u -> this.users.put(u.getName(), u));
}
@Override
@@ -30,25 +31,32 @@ public class UserDetailsService implements
throws UsernameNotFoundException {
// Determine if the user is allowed to access the system, if not throw
// UsernameNotFoundException
String username = token.getName().toString();
String username = token.getName();
if (!users.containsKey(username)) {
throw new UsernameNotFoundException(username + " not configured.");
}
// If allowed, populate the user details object with the authorities for
// the user.
return new UserDetails() {
return new TimelyUserDetails() {
private static final long serialVersionUID = 1L;
@Override
public Collection<SimpleGrantedAuthority> getAuthorities() {
final Collection<SimpleGrantedAuthority> auths = new ArrayList<>();
users.get(username).forEach(a -> {
users.get(username).getAuths().forEach(a -> {
auths.add(new SimpleGrantedAuthority(a));
});
return auths;
}
@Override
public Collection<String> getRoles() {
final Collection<String> roles = new ArrayList<>();
roles.addAll(users.get(username).getRoles());
return roles;
}
@Override
public String getPassword() {
return "";
@@ -0,0 +1,10 @@
package timely.netty.http.auth;
import java.util.Collection;
import org.springframework.security.core.userdetails.UserDetails;
public interface TimelyUserDetails extends UserDetails {
Collection<String> getRoles();
}
@@ -0,0 +1,45 @@
package timely.auth;
import java.util.Arrays;
import java.util.ServiceConfigurationError;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class UserDetailsServiceTest {
private static ApplicationContext springContext = null;
private static UserDetailsService userDetailsService = null;
@BeforeClass
public static void setupClass() {
try {
springContext = new ClassPathXmlApplicationContext("security.xml");
userDetailsService = (UserDetailsService) springContext.getBean("timelyUserDetailsService");
} catch (BeansException e) {
throw new ServiceConfigurationError("Error initializing from spring: " + e.getMessage(), e.getRootCause());
}
}
@Test
public void testAuths() {
TimelyUser u = userDetailsService.getUsers().get("CN=example.com");
Assert.assertNotNull("TimelyUser not found", u);
Assert.assertEquals("3 auths expected", 3, u.getAuths().size());
Assert.assertTrue("Unexpected auths found " + u.getAuths(),
u.getAuths().containsAll(Arrays.asList("D", "E", "F")));
}
@Test
public void testRoles() {
TimelyUser u = userDetailsService.getUsers().get("CN=example.com");
Assert.assertNotNull("TimelyUser not found", u);
Assert.assertEquals("3 roles expected", 3, u.getRoles().size());
Assert.assertTrue("Unexpected roles found " + u.getRoles(),
u.getRoles().containsAll(Arrays.asList("G", "H", "I")));
}
}
+26 -14
View File
@@ -11,21 +11,33 @@
<beans:bean id="x509PrincipalExtractor" class="org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor">
<beans:property name="subjectDnRegex" value="CN=(.*?)(?:,|$)"/>
</beans:bean>
</beans:bean>
<beans:bean id="timelyUserDetailsService" class="timely.auth.UserDetailsService">
<beans:property name="users">
<util:map key-type="java.lang.String">
<beans:entry key="CN=example.com">
<util:list>
<beans:value>D</beans:value>
<beans:value>E</beans:value>
<beans:value>F</beans:value>
</util:list>
</beans:entry>
</util:map>
</beans:property>
</beans:bean>
<beans:bean id="timelyUserDetailsService" class="timely.auth.UserDetailsService">
<beans:property name="users">
<util:list>
<beans:bean class="timely.auth.TimelyUser">
<beans:constructor-arg name="subjectDn">
<beans:value>CN=example.com</beans:value>
</beans:constructor-arg>
<beans:constructor-arg name="auths">
<util:list>
<beans:value>D</beans:value>
<beans:value>E</beans:value>
<beans:value>F</beans:value>
</util:list>
</beans:constructor-arg>
<beans:constructor-arg name="roles">
<util:list>
<beans:value>G</beans:value>
<beans:value>H</beans:value>
<beans:value>I</beans:value>
</util:list>
</beans:constructor-arg>
</beans:bean>
</util:list>
</beans:property>
</beans:bean>
<beans:bean id="x509AuthorizationProvider" class="org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationProvider">
<beans:property name="preAuthenticatedUserDetailsService" ref="timelyUserDetailsService" />