mirror of
https://github.com/NationalSecurityAgency/timely.git
synced 2026-11-10 03:41:25 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6a420d9f89 | ||
|
|
2fbd306a0f | ||
|
|
ac841d3f75 |
No files matched your search
+26
-14
@@ -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
@@ -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")));
|
||||
}
|
||||
}
|
||||
@@ -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" />
|
||||
|
||||
Reference in new issue
Block a user