Commit 65cea0a9 authored by Gradl, Tobias's avatar Gradl, Tobias
Browse files

No module project anymore

parent 15d5f74e
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>dariah-javasp-master</artifactId>
<groupId>de.dariah</groupId>
<version>0.0.3-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>dariah-javasp-core</artifactId>
<name>DARIAH AAI library - core</name>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>de.dariah</groupId>
<artifactId>spring-security-saml2-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j-version}</version>
</dependency>
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.1</version>
</dependency>
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
</project>
package de.dariah.aai.javasp.base;
import org.springframework.security.core.GrantedAuthority;
public interface Role extends GrantedAuthority {
public int getId();
public int getLevel();
public String getDescription();
}
package de.dariah.aai.javasp.base;
import java.util.Collection;
import org.springframework.security.core.userdetails.UserDetails;
public class SimpleUserDetails implements UserDetails, StagedUserDetails {
private static final long serialVersionUID = -1504226378983075730L;
private Collection<Role> authorities;
protected String id;
private String endpointId;
private String endpointName;
private String username;
private boolean expired;
private String language;
private boolean hasAllAttributes;
public SimpleUserDetails() {}
public SimpleUserDetails(User user, Collection<Role> authorities) {
this.id = user.getId();
this.username = user.getNameId();
this.expired = user.isExpired();
this.endpointId = user.getEndpointId();
this.language = user.getLanguage();
this.authorities = authorities;
this.endpointName = user.getEndpointName();
this.hasAllAttributes = false;
}
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public String getEndpointId() { return endpointId; }
public void setEndpointId(String endpointId) { this.endpointId = endpointId; }
public String getEndpointName() { return endpointName; }
public void setEndpointName(String endpointName) { this.endpointName = endpointName; }
public boolean isExpired() { return expired; }
public void setExpired(boolean expired) { this.expired = expired; }
public String getLanguage() { return language; }
public void setLanguage(String language) { this.language = language; }
public boolean isHasAllAttributes() { return hasAllAttributes; }
public void setHasAllAttributes(boolean hasAllAttributes) { this.hasAllAttributes = hasAllAttributes; }
public void setAuthorities(Collection<Role> authorities) { this.authorities = authorities; }
public void setUsername(String username) { this.username = username; }
@Override public String getUsername() { return username; }
@Override public String getPassword() { return ""; }
@Override public Collection<Role> getAuthorities() { return authorities; }
@Override public boolean isAccountNonExpired() { return !expired; }
@Override public boolean isAccountNonLocked() { return !expired; }
@Override public boolean isCredentialsNonExpired() { return !expired; }
@Override public boolean isEnabled() { return !expired; }
@Override
public int getMaxAuthorityLevel() {
int level = 0;
if (getAuthorities() != null) {
for (Role auth : getAuthorities()) {
if (auth instanceof Role && ((Role) auth).getLevel() > level) {
level = ((Role) auth).getLevel();
}
}
}
return level;
}
@Override
public boolean isAuthorized(Role compRole) {
return getMaxAuthorityLevel() >= compRole.getLevel();
}
}
\ No newline at end of file
package de.dariah.aai.javasp.base;
public interface StagedUserDetails {
public int getMaxAuthorityLevel();
public boolean isAuthorized(Role compRole);
}
package de.dariah.aai.javasp.base;
import java.io.IOException;
import java.net.InetAddress;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.commons.net.ntp.NTPUDPClient;
import org.apache.commons.net.ntp.NtpV3Packet;
import org.apache.commons.net.ntp.TimeInfo;
import org.apache.commons.net.ntp.TimeStamp;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
/**
* Service for determining the actual time from NTP Servers. The exact time is especially reqired for the exchange of SAML messages.
* TimeService does not modify the system time. Checks for the NTP time or deltas must manually be triggered.
*
* @author Tobias Gradl
*/
@Component
public class TimeService implements InitializingBean, DisposableBean {
private static final Logger logger = LoggerFactory.getLogger(TimeService.class);
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private Timer timer;
private NTPUDPClient ntpClient = new NTPUDPClient();
private String mainNtpServer;
private String backupNtpServer;
private int refreshInterval = 3600000; // default: every hour
private int msOffset;
private DateTime lastUpdate;
public String getMainNtpServer() { return mainNtpServer; }
public void setMainNtpServer(String mainNtpServer) { this.mainNtpServer = mainNtpServer; }
public String getBackupNtpServer() { return backupNtpServer; }
public void setBackupNtpServer(String backupNtpServer) { this.backupNtpServer = backupNtpServer; }
public int getRefreshInterval() { return refreshInterval; }
public void setRefreshInterval(int refreshInterval) { this.refreshInterval = refreshInterval; }
public int getMsOffset() { return msOffset; }
public DateTime getLastUpdate() { return lastUpdate; }
public DateTime getNow() { return DateTime.now().plusMillis(getMsOffset()); }
@Override
public void afterPropertiesSet() throws Exception {
ntpClient.setDefaultTimeout(10000);
// Run once at startup
new TimeUpdateTask().run();
// Then after certain interval (refreshInterval)
startRefreshCycle();
}
@Override
public void destroy() throws Exception {
if (timer != null) {
timer.cancel();
}
}
private void startRefreshCycle() {
// Create timer if needed
if (refreshInterval > 0) {
logger.debug("Creating metadata reload timer with interval {}", refreshInterval);
this.timer = new Timer("NTP Time Refresh", true);
this.timer.schedule(new TimeUpdateTask(), refreshInterval, refreshInterval);
} else {
logger.debug("Metadata reload timer is not created, refreshCheckInternal is {}", refreshInterval);
}
}
private class TimeUpdateTask extends TimerTask {
@Override
public void run() {
if (sendNtpRequest(getMainNtpServer()) || sendNtpRequest(getBackupNtpServer())) {
logger.info("NTP refresh was successfully competed");
} else {
logger.error("Could not refresh NTP time...try modifying hosts and check network availability");
}
}
}
public boolean sendNtpRequest(String ntpServer) {
this.lock.writeLock().lock();
try {
logger.info(String.format("Requesting timestamp from NTP Server [%s]", ntpServer));
if (!ntpClient.isOpen()) {
ntpClient.open();
}
InetAddress ntpServerAddr = InetAddress.getByName(mainNtpServer);
processNtpResponse(ntpClient.getTime(ntpServerAddr));
return true;
} catch (IOException e) {
logger.error(String.format("Error refreshing timestamp from NTP Server [%s]", ntpServer), e);
return false;
} finally {
ntpClient.close();
this.lock.writeLock().unlock();
}
}
public void processNtpResponse(TimeInfo info)
{
NtpV3Packet message = info.getMessage();
StringBuilder stb = new StringBuilder();
stb.append("Communication with NTP Server resulted in the following timestamps:\n");
stb.append(String.format("\t\t- Reference Timestamp:\t%s %s\n", message.getReferenceTimeStamp(), message.getReferenceTimeStamp().toDateString()));
stb.append(String.format("\t\t- Originate Timestamp:\t%s %s\n", message.getOriginateTimeStamp(), message.getOriginateTimeStamp().toDateString()));
stb.append(String.format("\t\t- Receive Timestamp:\t%s %s\n", message.getReceiveTimeStamp(), message.getReceiveTimeStamp().toDateString()));
stb.append(String.format("\t\t- Transmit Timestamp:\t%s %s\n", message.getTransmitTimeStamp(), message.getTransmitTimeStamp().toDateString()));
TimeStamp destNtpTime = TimeStamp.getNtpTime(info.getReturnTime());
stb.append(String.format("\t\t- Transmit Timestamp:\t%s %s\n", destNtpTime, destNtpTime.toDateString()));
info.computeDetails(); // compute offset/delay if not already done
stb.append(String.format("\t\t- Roundtrip delay(ms):\t%s\n", info.getDelay()));
stb.append(String.format("\t\t- New time offset:\t%sms (was %sms)", info.getOffset(), msOffset));
logger.info(stb.toString());
msOffset = info.getOffset().intValue();
lastUpdate = DateTime.now();
}
}
package de.dariah.aai.javasp.base;
public interface User {
public String getId();
public String getNameId();
public boolean isExpired();
public String getEndpointId();
public String getEndpointName();
public String getLanguage();
}
package de.dariah.aai.javasp.exception;
import org.springframework.security.core.AuthenticationException;
import de.dariah.aai.javasp.base.SimpleUserDetails;
public class UserCredentialsException extends AuthenticationException {
private static final long serialVersionUID = 2783603614136569036L;
/**
* Not saving the Authentication but actually the SimpleUserDetails prevents potential leaking of sensitive information
*/
private SimpleUserDetails userDetails;
private UserCredentialsExceptionTypes type;
public enum UserCredentialsExceptionTypes {
ID_ATTRIBUTE_NOT_PROVIDED,
NAME_ID_NOT_PROVIDED,
CREDENTIALS_NOT_AVAILABLE
}
public SimpleUserDetails getUserDetails() { return userDetails; }
public void setUserDetails(SimpleUserDetails userDetails) { this.userDetails = userDetails; }
public UserCredentialsExceptionTypes getType() { return type; }
public void setType(UserCredentialsExceptionTypes type) { this.type = type; }
public UserCredentialsException(UserCredentialsExceptionTypes type, String msg) {
super(msg);
this.type = type;
}
public UserCredentialsException(UserCredentialsExceptionTypes type, String msg, Throwable t) {
super(msg, t);
this.type = type;
}
public UserCredentialsException(UserCredentialsExceptionTypes type, String msg, SimpleUserDetails userDetails) {
super(msg);
this.setUserDetails(userDetails);
this.type = type;
}
public UserCredentialsException(UserCredentialsExceptionTypes type, String msg, SimpleUserDetails userDetails, Throwable t) {
super(msg, t);
this.setUserDetails(userDetails);
this.type = type;
}
}
package de.dariah.aai.javasp.saml;
import java.util.ArrayList;
import java.util.List;
import org.joda.time.DateTime;
import org.opensaml.common.SAMLException;
import org.opensaml.saml2.core.AuthnRequest;
import org.opensaml.saml2.core.NameID;
import org.opensaml.saml2.core.RequestAbstractType;
import org.opensaml.saml2.core.Subject;
import org.opensaml.saml2.core.SubjectConfirmation;
import org.opensaml.saml2.core.SubjectConfirmationData;
import org.opensaml.xml.encryption.DecryptionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.saml.context.SAMLMessageContext;
import org.springframework.security.saml.websso.WebSSOProfileConsumerImpl;
import org.springframework.util.Assert;
import de.dariah.aai.javasp.base.TimeService;
public class SAMLAssertionConsumer extends WebSSOProfileConsumerImpl {
protected static final Logger logger = LoggerFactory.getLogger(SAMLAssertionConsumer.class);
@Autowired
private TimeService timeService;
@Override
protected boolean isDateTimeSkewValid(int skewInSec, DateTime time) {
long current = timeService.getNow().getMillis();
return time.isAfter(current - skewInSec * 1000) && time.isBefore(current + skewInSec * 1000);
}
@Override
protected boolean isDateTimeSkewValid(int skewInSec, int forwardInterval, DateTime time) {
long current = System.currentTimeMillis();
return time.isBefore(current + (skewInSec * 1000)) && time.isAfter(current - ((skewInSec + forwardInterval) * 1000));
}
@Override
protected void verifySubject(Subject subject, RequestAbstractType request, SAMLMessageContext context) throws SAMLException, DecryptionException {
List<SubjectConfirmation> confirmations = new ArrayList<SubjectConfirmation>();
for (SubjectConfirmation confirmation : subject.getSubjectConfirmations()) {
if (SubjectConfirmation.METHOD_BEARER.equals(confirmation.getMethod())) {
confirmations.add(confirmation);
} else if (SubjectConfirmation.METHOD_SENDER_VOUCHES.equals(confirmation.getMethod())) {
if (verifySubject(confirmation, request, context)) {
// Was the subject confirmed by this confirmation data? If so let's store the subject in the context.
NameID nameID;
if (subject.getEncryptedID() != null) {
Assert.notNull(context.getLocalDecrypter(), "Can't decrypt NameID, no decrypter is set in the context");
nameID = (NameID) context.getLocalDecrypter().decrypt(subject.getEncryptedID());
} else {
nameID = subject.getNameID();
}
context.setSubjectNameIdentifier(nameID);
}
}
}
if (confirmations.size()>0) {
super.verifySubject(subject, request, context);
}
}
// TODO: Need to check the SAML specification since other requirements are set for sender vouches confirmation
private boolean verifySubject(SubjectConfirmation confirmation, RequestAbstractType request, SAMLMessageContext context) {
logger.debug("Processing Sender vouches subject confirmation");
SubjectConfirmationData data = confirmation.getSubjectConfirmationData();
// Bearer must have confirmation saml-profiles-2.0-os 554
if (data == null) {
logger.debug("Sender vouches SubjectConfirmation invalidated by missing confirmation data");
return false;
}
// Not before forbidden by saml-profiles-2.0-os 558
if (data.getNotBefore() != null) {
logger.debug("Sender vouches SubjectConfirmation invalidated by not before which is forbidden");
return false;
}
// Required by saml-profiles-2.0-os 556
if (data.getNotOnOrAfter() == null) {
logger.debug("Sender vouches SubjectConfirmation invalidated by missing notOnOrAfter");
return false;
}
// Validate not on or after
if (data.getNotOnOrAfter().plusSeconds(getResponseSkew()).isBeforeNow()) {
logger.debug("Sender vouches SubjectConfirmation invalidated by notOnOrAfter");
return false;
}
// Validate in response to
if (request != null) {
if (data.getInResponseTo() == null) {
logger.debug("Sender vouches SubjectConfirmation invalidated by missing inResponseTo field");
return false;
} else {
if (!data.getInResponseTo().equals(request.getID())) {
logger.debug("Sender vouches SubjectConfirmation invalidated by invalid in response to");
return false;
}
}
}
return true;
}
}
package de.dariah.aai.javasp.saml;
import java.util.Collection;
import java.util.Date;
import org.opensaml.common.SAMLException;
import org.opensaml.common.SAMLRuntimeException;
import org.opensaml.xml.encryption.DecryptionException;
import org.opensaml.xml.validation.ValidationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.providers.ExpiringUsernameAuthenticationToken;
import org.springframework.security.saml.SAMLAuthenticationToken;
import org.springframework.security.saml.SAMLConstants;
import org.springframework.security.saml.SAMLCredential;
import org.springframework.security.saml.context.SAMLMessageContext;
import de.dariah.aai.saml.attributequery.SAMLAttributeAggregationService;
public class SAMLAuthenticationProvider extends org.springframework.security.saml.SAMLAuthenticationProvider {
protected static final Logger logger = LoggerFactory.getLogger(SAMLAuthenticationProvider.class);
private SAMLAttributeAggregationService attributeAggregationService = null;
public SAMLAttributeAggregationService getAttributeAggregationService() { return attributeAggregationService; }
public void setAttributeAggregationService(SAMLAttributeAggregationService attributeAggregationService) { this.attributeAggregationService = attributeAggregationService; }
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if (!supports(authentication.getClass())) {
throw new IllegalArgumentException("Only SAMLAuthenticationToken is supported, " + authentication.getClass() + " was attempted");
}
SAMLAuthenticationToken token = (SAMLAuthenticationToken) authentication;
SAMLMessageContext context = token.getCredentials();
SAMLCredential credential;
try {
if (SAMLConstants.SAML2_WEBSSO_PROFILE_URI.equals(context.getCommunicationProfileId())) {
credential = consumer.processAuthenticationResponse(context);
} else if (SAMLConstants.SAML2_HOK_WEBSSO_PROFILE_URI.equals(context.getCommunicationProfileId())) {
credential = hokConsumer.processAuthenticationResponse(context);
} else {
throw new SAMLException("Unsupported profile encountered in the context " + context.getCommunicationProfileId());
}
} catch (SAMLRuntimeException e) {
samlLogger.log(SAMLConstants.AUTH_N_RESPONSE, SAMLConstants.FAILURE, context, e);
throw new AuthenticationServiceException("Error validating SAML message", e);
} catch (SAMLException e) {
samlLogger.log(SAMLConstants.AUTH_N_RESPONSE, SAMLConstants.FAILURE, context, e);
throw new AuthenticationServiceException("Error validating SAML message", e);
} catch (ValidationException e) {
logger.debug("Error validating signature", e);
samlLogger.log(SAMLConstants.AUTH_N_RESPONSE, SAMLConstants.FAILURE, context);
throw new AuthenticationServiceException("Error validating SAML message signature", e);
} catch (org.opensaml.xml.security.SecurityException e) {
logger.debug("Error validating signature", e);
samlLogger.log(SAMLConstants.AUTH_N_RESPONSE, SAMLConstants.FAILURE, context);
throw new AuthenticationServiceException("Error validating SAML message signature", e);
} catch (DecryptionException e) {
logger.debug("Error decrypting SAML message", e);
samlLogger.log(SAMLConstants.AUTH_N_RESPONSE, SAMLConstants.FAILURE, context);
throw new AuthenticationServiceException("Error decrypting SAML message", e);
}
if (attributeAggregationService!=null) {
attributeAggregationService.aggregateIfRequired(credential);
}
Object userDetails = getUserDetails(credential);
Object principal = getPrincipal(credential, userDetails);
Collection<? extends GrantedAuthority> entitlements = getEntitlements(credential, userDetails);
Date expiration = getExpirationDate(credential);
ExpiringUsernameAuthenticationToken result = new ExpiringUsernameAuthenticationToken(expiration, principal, credential, entitlements);
result.setDetails(userDetails);
samlLogger.log(SAMLConstants.AUTH_N_RESPONSE, SAMLConstants.SUCCESS, context, result, null);
return result;
}
}
package de.dariah.aai.javasp.saml;
import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.saml.websso.SingleLogoutProfileImpl;