Skip to content
Snippets Groups Projects
Commit 2e1d78d7 authored by Alexander Lovett's avatar Alexander Lovett
Browse files

[LGR-75] - Configurable role to saml group

parent 26ebc4d5
Branches
No related tags found
No related merge requests found
Showing
with 491 additions and 346 deletions
# 2.0.14 # 2.0.14
* [LGR-75](https://jira.software.geant.org/browse/LGR-73) - Externalise saml groups to role
* [LGR-73](https://jira.software.geant.org/browse/LGR-73) - Remove router endpoint * [LGR-73](https://jira.software.geant.org/browse/LGR-73) - Remove router endpoint
* [LGR-72](https://jira.software.geant.org/browse/LGR-72) - Check user is authenticated prior to executing commands on internal routers * [LGR-72](https://jira.software.geant.org/browse/LGR-72) - Check user is authenticated prior to executing commands on internal routers
......
package org.geant.lgservice.config; package org.geant.lgservice.config;
import com.google.common.collect.ImmutableMap;
import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.geant.lgservice.exceptions.TechnicalException;
import org.geant.lgservice.pojos.Group; import org.geant.lgservice.pojos.Group;
import org.geant.lgservice.security.LGUser; import org.geant.lgservice.security.LGUser;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException; import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller; import javax.xml.bind.Unmarshaller;
import java.io.InputStream; import java.io.InputStream;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Optional;
import java.util.function.Predicate;
public class CommandsParser { public class CommandsParser {
private static final Logger logger = LogManager.getLogger(CommandsParser.class); private static final Logger logger = LogManager.getLogger(CommandsParser.class);
public List<Group> getCommandsFromXML(final String fileName, final LGUser user) { public List<Group> getCommandsFromXML(final String fileName, final LGUser user) {
Commands commands = new Commands(); try {
try { InputStream stream = getClass().getClassLoader().getResourceAsStream(resolveFileName(fileName, user));
String newFileName = resolveFileName(fileName, user);
if (newFileName != null) { JAXBContext jaxbContext = JAXBContext.newInstance(Commands.class);
ClassLoader classLoader = getClass().getClassLoader(); Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
InputStream stream = classLoader.getResourceAsStream(newFileName); return ((Commands) unmarshaller.unmarshal(stream)).getGroups();
} catch (JAXBException e) {
JAXBContext jaxbContext = JAXBContext.newInstance(Commands.class); logger.error("Unable to parse xml for " + fileName, e);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); throw new TechnicalException(e);
commands = (Commands) unmarshaller.unmarshal(stream); }
} else {
logger.error("Unable to read config file for commands list"); }
}
} catch (JAXBException e) { @RequiredArgsConstructor
logger.error("Unable to parse xml for " + fileName, e); private enum GroupRanking {
} INTERNAL(0),
EXTERNAL(1);
return commands.getGroups();
} @Getter
private final int rank;
@Builder }
@Getter
private static class FileName { public String resolveFileName(final String fileName, final LGUser user) {
private final String accessLevel; String accessLevel = Optional.ofNullable(user)
private final int rank; .map(UserDetails::getAuthorities)
} .flatMap(authorities -> authorities
.stream()
private static final Map<Predicate<Collection<GrantedAuthority>>, FileName> FILE_NAME_PREDICATES = ImmutableMap .map(GrantedAuthority::getAuthority)
.<Predicate<Collection<GrantedAuthority>>, FileName>builder() .map(GroupRanking::valueOf)
.put( .reduce((groupOne, groupTwo) -> groupOne.getRank() < groupTwo.getRank() ? groupOne : groupTwo))
grantedAuthorities -> grantedAuthorities.contains(LGUser.Group.INTERNAL), .map(GroupRanking::name)
FileName.builder().accessLevel("internal").rank(0).build()) .map(String::toLowerCase)
.put( .orElse("public");
grantedAuthorities -> grantedAuthorities.contains(LGUser.Group.EXTERNAL),
FileName.builder().accessLevel("external").rank(1).build()) return String.format("%s_%s.xml", fileName, accessLevel);
.build(); }
public String resolveFileName(final String fileName, final LGUser user) {
String accessLevel = "public";
if (user != null) {
accessLevel = FILE_NAME_PREDICATES.entrySet()
.stream()
.filter(entry -> entry.getKey().test(user.getAuthorities()))
.map(Map.Entry::getValue)
.reduce((fileName1, fileName2) -> fileName1.getRank() < fileName2.getRank() ? fileName1 : fileName2)
.map(FileName::getAccessLevel)
.orElse("public");
}
return String.format("%s_%s.xml", fileName, accessLevel);
}
} }
...@@ -9,7 +9,6 @@ import lombok.Getter; ...@@ -9,7 +9,6 @@ import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import okhttp3.Interceptor; import okhttp3.Interceptor;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.Request;
import org.geant.lgservice.domain.Router; import org.geant.lgservice.domain.Router;
import org.geant.lgservice.domain.RouterRepository; import org.geant.lgservice.domain.RouterRepository;
import org.geant.lgservice.exceptions.TechnicalException; import org.geant.lgservice.exceptions.TechnicalException;
......
...@@ -3,95 +3,67 @@ package org.geant.lgservice.security; ...@@ -3,95 +3,67 @@ package org.geant.lgservice.security;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Singular;
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection; import java.util.Collection;
import java.util.Optional;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toSet;
@Builder
@Getter @Getter
public class LGUser implements UserDetails { public class LGUser implements UserDetails {
private final String federatedUser; private final String federatedUser;
private final String givenName; private final String givenName;
private final String surname; private final String surname;
private final String mail; private final String mail;
private final Collection<GrantedAuthority> authorities; @Singular
private final Collection<Group> groups;
@RequiredArgsConstructor
public enum Group implements GrantedAuthority{ @RequiredArgsConstructor
INTERNAL(new SimpleGrantedAuthority("GEANT Staff:All")), public enum Group implements GrantedAuthority{
EXTERNAL(new SimpleGrantedAuthority("GEANT_CO:GEANT Services:NREN Svc Mgmt:GEANT NRENs:members_GEANT NRENs")); INTERNAL,
EXTERNAL;
private final GrantedAuthority authority;
@Override
public static Optional<Group> fromGroup(String group) { public String getAuthority() {
return Stream.of(Group.values()) return name();
.filter(value -> value.authority.getAuthority().equals(group)) }
.findFirst(); }
}
@Override
public static Optional<Group> fromAuthority(GrantedAuthority authority) { public Collection<? extends GrantedAuthority> getAuthorities() {
return Stream.of(Group.values()) return getGroups();
.filter(authority::equals) }
.findFirst();
} @Override
public String getPassword() {
@Override return null;
public String getAuthority() { }
return name();
} @Override
} public String getUsername() {
return federatedUser;
@Builder }
public LGUser(String federatedUser, String givenName, String surname, String mail,
String ...groups) { @Override
public boolean isAccountNonExpired() {
this.federatedUser = federatedUser; return true;
this.givenName = givenName; }
this.surname = surname;
this.mail = mail; @Override
this.authorities = Stream.of(groups) public boolean isAccountNonLocked() {
.map(Group::fromGroup) return true;
.filter(Optional::isPresent) }
.map(Optional::get)
.collect(toSet()); @Override
public boolean isCredentialsNonExpired() {
return true;
} }
@Override @Override
public String getPassword() { public boolean isEnabled() {
return null; return true;
} }
@Override
public String getUsername() {
return federatedUser;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
} }
package org.geant.lgservice.security; package org.geant.lgservice.security;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.UsernameNotFoundException;
...@@ -7,26 +9,47 @@ import org.springframework.security.saml.SAMLCredential; ...@@ -7,26 +9,47 @@ import org.springframework.security.saml.SAMLCredential;
import org.springframework.security.saml.userdetails.SAMLUserDetailsService; import org.springframework.security.saml.userdetails.SAMLUserDetailsService;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
import static org.geant.lgservice.security.SamlKeyMappings.Key.EMAIL;
import static org.geant.lgservice.security.SamlKeyMappings.Key.GIVEN_NAME;
import static org.geant.lgservice.security.SamlKeyMappings.Key.GROUPS;
import static org.geant.lgservice.security.SamlKeyMappings.Key.ID;
import static org.geant.lgservice.security.SamlKeyMappings.Key.SURNAME;
@Service @Service
@RequiredArgsConstructor
@Slf4j
public class LGUserService implements SAMLUserDetailsService { public class LGUserService implements SAMLUserDetailsService {
private static final Logger LOGGER = LoggerFactory.getLogger(WebSecurityConfig.class); private final Map<SamlKeyMappings.Key, String> keyMappings;
private final Map<String, LGUser.Group> groupMappings;
@Override
public Object loadUserBySAML(SAMLCredential credential) throws UsernameNotFoundException { @Override
String federatedUser = credential.getAttributeAsString("uid"); public Object loadUserBySAML(SAMLCredential credential) throws UsernameNotFoundException {
String givenName = credential.getAttributeAsString("givenName"); log.debug("Extracting attributes using keys: {}", keyMappings);
String surname = credential.getAttributeAsString("sn");
String mail = credential.getAttributeAsString("mail"); String federatedUser = credential.getAttributeAsString(keyMappings.get(ID));
String[] groupNames = credential.getAttributeAsStringArray("isMemberOf"); String givenName = credential.getAttributeAsString(keyMappings.get(GIVEN_NAME));
String surname = credential.getAttributeAsString(keyMappings.get(SURNAME));
LOGGER.info("Current user : " + givenName + ", " + surname); String mail = credential.getAttributeAsString(keyMappings.get(EMAIL));
return LGUser.builder()
.federatedUser(federatedUser) Collection<LGUser.Group> groups = Arrays.stream(credential.getAttributeAsStringArray(keyMappings.get(GROUPS)))
.givenName(givenName) .map(groupMappings::get)
.surname(surname) .filter(Objects::nonNull)
.mail(mail) .collect(Collectors.toSet());
.groups(groupNames)
.build(); log.info("Current user : " + givenName + ", " + surname);
} return LGUser.builder()
.federatedUser(federatedUser)
.givenName(givenName)
.surname(surname)
.mail(mail)
.groups(groups)
.build();
}
} }
package org.geant.lgservice.security;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
import java.util.Map;
import java.util.stream.Collectors;
import static java.util.Collections.unmodifiableMap;
@ConstructorBinding
@ConfigurationProperties("saml")
public class SamlGroupMapping {
private final Map<LGUser.Group, String> groupMappings;
public SamlGroupMapping(Map<LGUser.Group, String> groupMappings) {
this.groupMappings = groupMappings;
}
public Map<String, LGUser.Group> groupMappings() {
return unmodifiableMap(groupMappings.entrySet().stream()
.collect(Collectors.toMap(Map.Entry::getValue, Map.Entry::getKey)));
}
}
package org.geant.lgservice.security;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.ConstructorBinding;
import java.util.Arrays;
import java.util.Map;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.String.format;
import static java.util.Collections.unmodifiableMap;
@ConstructorBinding
@ConfigurationProperties("saml")
public class SamlKeyMappings {
public enum Key {
ID,
GIVEN_NAME,
SURNAME,
EMAIL,
GROUPS
}
private final Map<Key, String> keyMappings;
public SamlKeyMappings(Map<Key, String> keyMappings) {
checkNotNull(keyMappings, "Missing saml key mappings, under 'saml.key-mappings'");
checkArgument(keyMappings.size() == Key
.values().length, format("Missing saml key mappings, required: %s", Arrays.toString(Key.values())));
this.keyMappings = keyMappings;
}
public Map<Key, String> mappings() {
return unmodifiableMap(keyMappings);
}
}
...@@ -11,6 +11,7 @@ import org.opensaml.xml.parse.StaticBasicParserPool; ...@@ -11,6 +11,7 @@ import org.opensaml.xml.parse.StaticBasicParserPool;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.DefaultResourceLoader;
...@@ -71,65 +72,70 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher; ...@@ -71,65 +72,70 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy; import javax.annotation.PreDestroy;
import java.util.*; import java.util.*;
import java.util.Timer;
import static org.springframework.web.util.UriComponentsBuilder.fromHttpUrl; import static org.springframework.web.util.UriComponentsBuilder.fromHttpUrl;
@Configuration @Configuration
@EnableWebSecurity @EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true) @EnableGlobalMethodSecurity(securedEnabled = true)
@EnableConfigurationProperties({
SamlGroupMapping.class,
SamlKeyMappings.class
})
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired @Autowired
AppConfig config; AppConfig config;
private static final Logger LOGGER = LoggerFactory.getLogger(WebSecurityConfig.class); private static final Logger LOGGER = LoggerFactory.getLogger(WebSecurityConfig.class);
private String metadataURL; private String metadataURL;
private Timer backgroundTaskTimer; private Timer backgroundTaskTimer;
private MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager; private MultiThreadedHttpConnectionManager multiThreadedHttpConnectionManager;
@PostConstruct @PostConstruct
public void init() { public void init() {
this.backgroundTaskTimer = new Timer(true); this.backgroundTaskTimer = new Timer(true);
this.multiThreadedHttpConnectionManager = new MultiThreadedHttpConnectionManager(); this.multiThreadedHttpConnectionManager = new MultiThreadedHttpConnectionManager();
this.metadataURL = config.getMetadataURL(); this.metadataURL = config.getMetadataURL();
} }
@PreDestroy @PreDestroy
public void destroy() { public void destroy() {
this.backgroundTaskTimer.purge(); this.backgroundTaskTimer.purge();
this.backgroundTaskTimer.cancel(); this.backgroundTaskTimer.cancel();
this.multiThreadedHttpConnectionManager.shutdown(); this.multiThreadedHttpConnectionManager.shutdown();
} }
@Autowired @Autowired
private LGUserService userService; private LGUserService userService;
@Bean @Bean
public VelocityEngine velocityEngine() { public VelocityEngine velocityEngine() {
return VelocityFactory.getEngine(); return VelocityFactory.getEngine();
} }
@Bean(initMethod = "initialize") @Bean(initMethod = "initialize")
public StaticBasicParserPool parserPool() { public StaticBasicParserPool parserPool() {
return new StaticBasicParserPool(); return new StaticBasicParserPool();
} }
@Bean(name = "parserPoolHolder") @Bean(name = "parserPoolHolder")
public ParserPoolHolder parserPoolHolder() { public ParserPoolHolder parserPoolHolder() {
return new ParserPoolHolder(); return new ParserPoolHolder();
} }
// Bindings, encoders and decoders used for creating and parsing messages // Bindings, encoders and decoders used for creating and parsing messages
@Bean @Bean
public HttpClient httpClient() { public HttpClient httpClient() {
HttpClient httpClient = new HttpClient(this.multiThreadedHttpConnectionManager); HttpClient httpClient = new HttpClient(this.multiThreadedHttpConnectionManager);
httpClient.getHostConfiguration().setHost( httpClient.getHostConfiguration().setHost(
fromHttpUrl(metadataURL).build().toUri().getHost()); fromHttpUrl(metadataURL).build().toUri().getHost());
return httpClient; return httpClient;
} }
// SAML Authentication Provider responsible for validating of received SAML // SAML Authentication Provider responsible for validating of received SAML
// messages // messages
@Bean @Bean
...@@ -139,77 +145,77 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { ...@@ -139,77 +145,77 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
samlAuthenticationProvider.setForcePrincipalAsString(false); samlAuthenticationProvider.setForcePrincipalAsString(false);
return samlAuthenticationProvider; return samlAuthenticationProvider;
} }
// Provider of default SAML Context // Provider of default SAML Context
@Bean @Bean
public SAMLContextProviderImpl contextProvider() { public SAMLContextProviderImpl contextProvider() {
return new SAMLContextProviderImpl(); return new SAMLContextProviderImpl();
} }
// Initialization of OpenSAML library // Initialization of OpenSAML library
@Bean @Bean
public static SAMLBootstrap sAMLBootstrap() { public static SAMLBootstrap sAMLBootstrap() {
return new SAMLBootstrap(); return new SAMLBootstrap();
} }
// Logger for SAML messages and events // Logger for SAML messages and events
@Bean @Bean
public SAMLDefaultLogger samlLogger() { public SAMLDefaultLogger samlLogger() {
return new SAMLDefaultLogger(); return new SAMLDefaultLogger();
} }
// SAML 2.0 WebSSO Assertion Consumer // SAML 2.0 WebSSO Assertion Consumer
@Bean @Bean
public WebSSOProfileConsumer webSSOprofileConsumer() { public WebSSOProfileConsumer webSSOprofileConsumer() {
return new WebSSOProfileConsumerImpl(); return new WebSSOProfileConsumerImpl();
} }
// SAML 2.0 Holder-of-Key WebSSO Assertion Consumer // SAML 2.0 Holder-of-Key WebSSO Assertion Consumer
@Bean @Bean
public WebSSOProfileConsumerHoKImpl hokWebSSOprofileConsumer() { public WebSSOProfileConsumerHoKImpl hokWebSSOprofileConsumer() {
return new WebSSOProfileConsumerHoKImpl(); return new WebSSOProfileConsumerHoKImpl();
} }
// SAML 2.0 Web SSO profile // SAML 2.0 Web SSO profile
@Bean @Bean
public WebSSOProfile webSSOprofile() { public WebSSOProfile webSSOprofile() {
return new WebSSOProfileImpl(); return new WebSSOProfileImpl();
} }
// SAML 2.0 Holder-of-Key Web SSO profile // SAML 2.0 Holder-of-Key Web SSO profile
@Bean @Bean
public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() { public WebSSOProfileConsumerHoKImpl hokWebSSOProfile() {
return new WebSSOProfileConsumerHoKImpl(); return new WebSSOProfileConsumerHoKImpl();
} }
// SAML 2.0 ECP profile // SAML 2.0 ECP profile
@Bean @Bean
public WebSSOProfileECPImpl ecpprofile() { public WebSSOProfileECPImpl ecpprofile() {
return new WebSSOProfileECPImpl(); return new WebSSOProfileECPImpl();
} }
@Bean @Bean
public SingleLogoutProfile logoutprofile() { public SingleLogoutProfile logoutprofile() {
return new SingleLogoutProfileImpl(); return new SingleLogoutProfileImpl();
} }
@Bean @Bean
public KeyManager keyManager() { public KeyManager keyManager() {
DefaultResourceLoader loader = new DefaultResourceLoader(); DefaultResourceLoader loader = new DefaultResourceLoader();
Resource storeFile = loader.getResource(config.getKeyStore()); Resource storeFile = loader.getResource(config.getKeyStore());
String storePass = config.getKeystorePassword(); String storePass = config.getKeystorePassword();
Map<String, String> passwords = new HashMap<String, String>(); Map<String, String> passwords = new HashMap<String, String>();
passwords.put(config.getKeystoreAlias(), config.getKeystorePassword()); passwords.put(config.getKeystoreAlias(), config.getKeystorePassword());
return new JKSKeyManager(storeFile, storePass, passwords, config.getKeystoreAlias()); return new JKSKeyManager(storeFile, storePass, passwords, config.getKeystoreAlias());
} }
@Bean @Bean
public WebSSOProfileOptions defaultWebSSOProfileOptions() { public WebSSOProfileOptions defaultWebSSOProfileOptions() {
WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions(); WebSSOProfileOptions webSSOProfileOptions = new WebSSOProfileOptions();
webSSOProfileOptions.setIncludeScoping(false); webSSOProfileOptions.setIncludeScoping(false);
return webSSOProfileOptions; return webSSOProfileOptions;
} }
// Entry point to initialize authentication, default values taken from // Entry point to initialize authentication, default values taken from
// properties file // properties file
@Bean @Bean
...@@ -218,19 +224,19 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { ...@@ -218,19 +224,19 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
samlEntryPoint.setDefaultProfileOptions(defaultWebSSOProfileOptions()); samlEntryPoint.setDefaultProfileOptions(defaultWebSSOProfileOptions());
return samlEntryPoint; return samlEntryPoint;
} }
// Setup advanced info about metadata // Setup advanced info about metadata
@Bean @Bean
public ExtendedMetadata extendedMetadata() { public ExtendedMetadata extendedMetadata() {
ExtendedMetadata extendedMetadata = new ExtendedMetadata(); ExtendedMetadata extendedMetadata = new ExtendedMetadata();
extendedMetadata.setIdpDiscoveryEnabled(false); extendedMetadata.setIdpDiscoveryEnabled(false);
extendedMetadata.setSignMetadata(false); extendedMetadata.setSignMetadata(false);
extendedMetadata.setEcpEnabled(false); extendedMetadata.setEcpEnabled(false);
extendedMetadata.setSslHostnameVerification("allowAll"); extendedMetadata.setSslHostnameVerification("allowAll");
extendedMetadata.setSigningKey("spring"); extendedMetadata.setSigningKey("spring");
return extendedMetadata; return extendedMetadata;
} }
// IDP Discovery Service // IDP Discovery Service
@Bean @Bean
public SAMLDiscovery samlIDPDiscovery() { public SAMLDiscovery samlIDPDiscovery() {
...@@ -238,46 +244,46 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { ...@@ -238,46 +244,46 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
idpDiscovery.setIdpSelectionPath("/saml/idpSelection"); idpDiscovery.setIdpSelectionPath("/saml/idpSelection");
return idpDiscovery; return idpDiscovery;
} }
@Bean(name = "metadata") @Bean(name = "metadata")
public CachingMetadataManager metadata() throws MetadataProviderException { public CachingMetadataManager metadata() throws MetadataProviderException {
HTTPMetadataProvider provider = new HTTPMetadataProvider(this.backgroundTaskTimer, httpClient(), HTTPMetadataProvider provider = new HTTPMetadataProvider(this.backgroundTaskTimer, httpClient(),
this.metadataURL); this.metadataURL);
//provider.setMinRefreshDelay(3600000); //provider.setMinRefreshDelay(3600000);
//provider.setMinRefreshDelay(3600000); //provider.setMinRefreshDelay(3600000);
provider.setParserPool(parserPool()); provider.setParserPool(parserPool());
ExtendedMetadataDelegate extendedMetadataDelegate = new ExtendedMetadataDelegate(provider, extendedMetadata()); ExtendedMetadataDelegate extendedMetadataDelegate = new ExtendedMetadataDelegate(provider, extendedMetadata());
extendedMetadataDelegate.setMetadataTrustCheck(false); extendedMetadataDelegate.setMetadataTrustCheck(false);
extendedMetadataDelegate.setMetadataRequireSignature(false); extendedMetadataDelegate.setMetadataRequireSignature(false);
backgroundTaskTimer.purge(); backgroundTaskTimer.purge();
List<MetadataProvider> providers = new ArrayList<MetadataProvider>(); List<MetadataProvider> providers = new ArrayList<MetadataProvider>();
providers.add(extendedMetadataDelegate); providers.add(extendedMetadataDelegate);
return new CachingMetadataManager(providers); return new CachingMetadataManager(providers);
} }
@Bean @Bean
public MetadataGenerator metadataGenerator() { public MetadataGenerator metadataGenerator() {
MetadataGenerator metadataGenerator = new MetadataGenerator(); MetadataGenerator metadataGenerator = new MetadataGenerator();
metadataGenerator.setEntityId(config.getEntityName()); metadataGenerator.setEntityId(config.getEntityName());
metadataGenerator.setEntityBaseURL(config.getEntityBaseURL()); metadataGenerator.setEntityBaseURL(config.getEntityBaseURL());
metadataGenerator.setExtendedMetadata(extendedMetadata()); metadataGenerator.setExtendedMetadata(extendedMetadata());
metadataGenerator.setIncludeDiscoveryExtension(false); metadataGenerator.setIncludeDiscoveryExtension(false);
metadataGenerator.setKeyManager(keyManager()); metadataGenerator.setKeyManager(keyManager());
return metadataGenerator; return metadataGenerator;
} }
// The filter is waiting for connections on URL suffixed with filterSuffix // The filter is waiting for connections on URL suffixed with filterSuffix
// and presents SP metadata there // and presents SP metadata there
@Bean @Bean
public MetadataDisplayFilter metadataDisplayFilter() { public MetadataDisplayFilter metadataDisplayFilter() {
return new MetadataDisplayFilter(); return new MetadataDisplayFilter();
} }
// Handler deciding where to redirect user after successful login // Handler deciding where to redirect user after successful login
@Bean @Bean
public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() { public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() {
...@@ -287,17 +293,17 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { ...@@ -287,17 +293,17 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
LOGGER.info("executing the authentication success handler"); LOGGER.info("executing the authentication success handler");
return successRedirectHandler; return successRedirectHandler;
} }
// Handler deciding where to redirect user after failed login // Handler deciding where to redirect user after failed login
@Bean @Bean
public SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() { public SimpleUrlAuthenticationFailureHandler authenticationFailureHandler() {
SimpleUrlAuthenticationFailureHandler failureHandler = SimpleUrlAuthenticationFailureHandler failureHandler =
new SimpleUrlAuthenticationFailureHandler(); new SimpleUrlAuthenticationFailureHandler();
failureHandler.setUseForward(true); failureHandler.setUseForward(true);
failureHandler.setDefaultFailureUrl("/error"); failureHandler.setDefaultFailureUrl("/error");
return failureHandler; return failureHandler;
} }
@Bean @Bean
public SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter() throws Exception { public SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter() throws Exception {
SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter = new SAMLWebSSOHoKProcessingFilter(); SAMLWebSSOHoKProcessingFilter samlWebSSOHoKProcessingFilter = new SAMLWebSSOHoKProcessingFilter();
...@@ -306,7 +312,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { ...@@ -306,7 +312,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
samlWebSSOHoKProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler()); samlWebSSOHoKProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
return samlWebSSOHoKProcessingFilter; return samlWebSSOHoKProcessingFilter;
} }
// Processing filter for WebSSO profile messages // Processing filter for WebSSO profile messages
@Bean @Bean
public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception { public SAMLProcessingFilter samlWebSSOProcessingFilter() throws Exception {
...@@ -316,12 +322,12 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { ...@@ -316,12 +322,12 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
samlWebSSOProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler()); samlWebSSOProcessingFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
return samlWebSSOProcessingFilter; return samlWebSSOProcessingFilter;
} }
@Bean @Bean
public MetadataGeneratorFilter metadataGeneratorFilter() { public MetadataGeneratorFilter metadataGeneratorFilter() {
return new MetadataGeneratorFilter(metadataGenerator()); return new MetadataGeneratorFilter(metadataGenerator());
} }
// Handler for successful logout // Handler for successful logout
@Bean @Bean
public SimpleUrlLogoutSuccessHandler successLogoutHandler() { public SimpleUrlLogoutSuccessHandler successLogoutHandler() {
...@@ -329,17 +335,17 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { ...@@ -329,17 +335,17 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
successLogoutHandler.setDefaultTargetUrl("/"); successLogoutHandler.setDefaultTargetUrl("/");
return successLogoutHandler; return successLogoutHandler;
} }
// Logout handler terminating local session // Logout handler terminating local session
@Bean @Bean
public SecurityContextLogoutHandler logoutHandler() { public SecurityContextLogoutHandler logoutHandler() {
SecurityContextLogoutHandler logoutHandler = SecurityContextLogoutHandler logoutHandler =
new SecurityContextLogoutHandler(); new SecurityContextLogoutHandler();
logoutHandler.setInvalidateHttpSession(true); logoutHandler.setInvalidateHttpSession(true);
logoutHandler.setClearAuthentication(true); logoutHandler.setClearAuthentication(true);
return logoutHandler; return logoutHandler;
} }
// Filter processing incoming logout messages // Filter processing incoming logout messages
// First argument determines URL user will be redirected to after successful // First argument determines URL user will be redirected to after successful
// global logout // global logout
...@@ -348,59 +354,59 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { ...@@ -348,59 +354,59 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
return new SAMLLogoutProcessingFilter(successLogoutHandler(), return new SAMLLogoutProcessingFilter(successLogoutHandler(),
logoutHandler()); logoutHandler());
} }
// Overrides default logout processing filter with the one processing SAML // Overrides default logout processing filter with the one processing SAML
// messages // messages
@Bean @Bean
public SAMLLogoutFilter samlLogoutFilter() { public SAMLLogoutFilter samlLogoutFilter() {
return new SAMLLogoutFilter(successLogoutHandler(), return new SAMLLogoutFilter(successLogoutHandler(),
new LogoutHandler[] { logoutHandler() }, new LogoutHandler[]{logoutHandler()},
new LogoutHandler[] { logoutHandler() }); new LogoutHandler[]{logoutHandler()});
} }
@Bean @Bean
public HTTPSOAP11Binding soapBinding() { public HTTPSOAP11Binding soapBinding() {
return new HTTPSOAP11Binding(parserPool()); return new HTTPSOAP11Binding(parserPool());
} }
@Bean @Bean
public HTTPPostBinding httpPostBinding() { public HTTPPostBinding httpPostBinding() {
return new HTTPPostBinding(parserPool(), velocityEngine()); return new HTTPPostBinding(parserPool(), velocityEngine());
} }
@Bean @Bean
public HTTPRedirectDeflateBinding httpRedirectDeflateBinding() { public HTTPRedirectDeflateBinding httpRedirectDeflateBinding() {
return new HTTPRedirectDeflateBinding(parserPool()); return new HTTPRedirectDeflateBinding(parserPool());
} }
@Bean @Bean
public HTTPSOAP11Binding httpSOAP11Binding() { public HTTPSOAP11Binding httpSOAP11Binding() {
return new HTTPSOAP11Binding(parserPool()); return new HTTPSOAP11Binding(parserPool());
} }
@Bean @Bean
public HTTPPAOS11Binding httpPAOS11Binding() { public HTTPPAOS11Binding httpPAOS11Binding() {
return new HTTPPAOS11Binding(parserPool()); return new HTTPPAOS11Binding(parserPool());
} }
// Processor // Processor
@Bean @Bean
public SAMLProcessorImpl processor() { public SAMLProcessorImpl processor() {
Collection<SAMLBinding> bindings = new ArrayList<SAMLBinding>(); Collection<SAMLBinding> bindings = new ArrayList<SAMLBinding>();
bindings.add(httpRedirectDeflateBinding()); bindings.add(httpRedirectDeflateBinding());
bindings.add(httpPostBinding()); bindings.add(httpPostBinding());
//bindings.add(artifactBinding(parserPool(), velocityEngine())); //bindings.add(artifactBinding(parserPool(), velocityEngine()));
bindings.add(httpSOAP11Binding()); bindings.add(httpSOAP11Binding());
bindings.add(httpPAOS11Binding()); bindings.add(httpPAOS11Binding());
return new SAMLProcessorImpl(bindings); return new SAMLProcessorImpl(bindings);
} }
/** /**
* Define the security filter chain in order to support SSO Auth by using SAML 2.0 * Define the security filter chain in order to support SSO Auth by using SAML 2.0
* *
* @return Filter chain proxy * @return Filter chain proxy
* @throws Exception * @throws Exception
*/ */
@Bean @Bean
public FilterChainProxy samlFilter() throws Exception { public FilterChainProxy samlFilter() throws Exception {
List<SecurityFilterChain> chains = new ArrayList<SecurityFilterChain>(); List<SecurityFilterChain> chains = new ArrayList<SecurityFilterChain>();
...@@ -420,57 +426,67 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter { ...@@ -420,57 +426,67 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
samlIDPDiscovery())); samlIDPDiscovery()));
return new FilterChainProxy(chains); return new FilterChainProxy(chains);
} }
/** /**
* Returns the authentication manager currently used by Spring. * Returns the authentication manager currently used by Spring.
* It represents a bean definition with the aim allow wiring from * It represents a bean definition with the aim allow wiring from
* other classes performing the Inversion of Control (IoC). * other classes performing the Inversion of Control (IoC).
* *
* @throws Exception * @throws Exception
*/ */
@Bean @Bean
@Override @Override
public AuthenticationManager authenticationManagerBean() throws Exception { public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean(); return super.authenticationManagerBean();
} }
/** /**
* Defines the web based security configuration. * Defines the web based security configuration.
* *
* @param http It allows configuring web based security for specific http requests. * @param http It allows configuring web based security for specific http requests.
* @throws Exception * @throws Exception
*/ */
@Override @Override
protected void configure(HttpSecurity http) throws Exception { protected void configure(HttpSecurity http) throws Exception {
http http
.httpBasic() .httpBasic()
.authenticationEntryPoint(samlEntryPoint()); .authenticationEntryPoint(samlEntryPoint());
http http
.csrf() .csrf()
.disable(); .disable();
http http
.addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class) .addFilterBefore(metadataGeneratorFilter(), ChannelProcessingFilter.class)
.addFilterAfter(samlFilter(), BasicAuthenticationFilter.class); .addFilterAfter(samlFilter(), BasicAuthenticationFilter.class);
http.authorizeRequests() http.authorizeRequests()
.antMatchers("/authenticate*/**").authenticated() .antMatchers("/authenticate*/**").authenticated()
.anyRequest().permitAll(); .anyRequest().permitAll();
http http
.logout() .logout()
.logoutSuccessUrl("/"); .logoutSuccessUrl("/");
} }
/** /**
* Sets a custom authentication provider. * Sets a custom authentication provider.
* *
* @param auth SecurityBuilder used to create an AuthenticationManager. * @param auth SecurityBuilder used to create an AuthenticationManager.
* @throws Exception * @throws Exception
*/ */
@Override @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception { protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth auth
.authenticationProvider(samlAuthenticationProvider()); .authenticationProvider(samlAuthenticationProvider());
} }
@Bean
public Map<String, LGUser.Group> groupMappings(SamlGroupMapping mappings) {
return mappings.groupMappings();
}
@Bean
public Map<SamlKeyMappings.Key, String> keyMappings(SamlKeyMappings mappings) {
return mappings.mappings();
}
} }
\ No newline at end of file
...@@ -21,9 +21,12 @@ import org.springframework.beans.factory.annotation.Autowired; ...@@ -21,9 +21,12 @@ import org.springframework.beans.factory.annotation.Autowired;
import java.io.PipedInputStream; import java.io.PipedInputStream;
import java.io.PipedOutputStream; import java.io.PipedOutputStream;
import java.util.Collections;
import static com.github.tomakehurst.wiremock.client.WireMock.get; import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static org.geant.lgservice.integration.Context.withDefaults; import static org.geant.lgservice.integration.Context.withDefaults;
import static org.geant.lgservice.security.LGUser.Group.EXTERNAL;
import static org.geant.lgservice.security.LGUser.Group.INTERNAL;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
@JGivenStage @JGivenStage
...@@ -59,23 +62,28 @@ public class Given extends ExtendedStage<Given> { ...@@ -59,23 +62,28 @@ public class Given extends ExtendedStage<Given> {
} }
public GivenUser logged_in() { public GivenUser logged_in() {
context.withUser(new LGUser( context.withUser(
"1", LGUser.builder()
"barry", .federatedUser("1")
"scott", .givenName("barry")
"barry.scott@geant.org", .surname("scott")
"GEANT Staff:All")); .mail("barry.scott@geant.org")
.group(INTERNAL)
.group(EXTERNAL)
.build());
return self(); return self();
} }
public GivenUser does_not_have_internal_access() { public GivenUser does_not_have_internal_access() {
LGUser user = context.getUser(); LGUser user = context.getUser();
context.withUser( new LGUser( context.withUser(
user.getFederatedUser(), LGUser.builder()
user.getGivenName(), .federatedUser(user.getFederatedUser())
user.getSurname(), .givenName(user.getGivenName())
user.getMail(), .surname(user.getSurname())
"GEANT_CO:GEANT Services:NREN Svc Mgmt:GEANT NRENs:members_GEANT NRENs")); .mail(user.getMail())
.group(EXTERNAL)
.build());
return self(); return self();
} }
} }
......
...@@ -80,7 +80,7 @@ public class SubmitCommandRequestIT extends BaseIT<Given, When, Then> { ...@@ -80,7 +80,7 @@ public class SubmitCommandRequestIT extends BaseIT<Given, When, Then> {
given() given()
.a().user() .a().user()
.that().is().logged_in() .that().is().logged_in()
.but().is().does_not_have_internal_access() .but().does_not_have_internal_access()
.backup() .backup()
.and().a().router() .and().a().router()
.that().is().internal(); .that().is().internal();
......
package org.geant.lgservice.security;
import com.google.common.collect.ImmutableMap;
import org.junit.Before;
import org.junit.Test;
import org.springframework.security.saml.SAMLCredential;
import static org.assertj.core.api.Assertions.assertThat;
import static org.geant.lgservice.security.LGUser.Group.EXTERNAL;
import static org.geant.lgservice.security.LGUser.Group.INTERNAL;
import static org.geant.lgservice.security.SamlKeyMappings.Key.EMAIL;
import static org.geant.lgservice.security.SamlKeyMappings.Key.GIVEN_NAME;
import static org.geant.lgservice.security.SamlKeyMappings.Key.GROUPS;
import static org.geant.lgservice.security.SamlKeyMappings.Key.ID;
import static org.geant.lgservice.security.SamlKeyMappings.Key.SURNAME;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.when;
public class LGUserServiceTest {
SAMLCredential credential = mock(SAMLCredential.class);
LGUserService subject = new LGUserService(
ImmutableMap.<SamlKeyMappings.Key, String>builder()
.put(ID, "uid")
.put(GIVEN_NAME, "givenName")
.put(SURNAME, "sn")
.put(EMAIL, "mail")
.put(GROUPS, "isMemberOf")
.build(),
ImmutableMap.<String, LGUser.Group>builder()
.put("internal", INTERNAL)
.put("external", EXTERNAL)
.build());
@Before
public void setup() {
reset(credential);
when(credential.getAttributeAsString("uid")).thenReturn("1");
when(credential.getAttributeAsString("givenName")).thenReturn("barry");
when(credential.getAttributeAsString("sn")).thenReturn("scott");
when(credential.getAttributeAsString("mail")).thenReturn("barry.scott@geant.org");
}
@Test
public void loadUserWithMultipleGroups() {
when(credential.getAttributeAsStringArray("isMemberOf"))
.thenReturn(new String[]{"internal", "external", "ignore"});
LGUser result = (LGUser) subject.loadUserBySAML(credential);
assertThat(result.getFederatedUser()).isEqualTo("1");
assertThat(result.getGivenName()).isEqualTo("barry");
assertThat(result.getSurname()).isEqualTo("scott");
assertThat(result.getMail()).isEqualTo("barry.scott@geant.org");
assertThat(result.getAuthorities()).containsExactlyInAnyOrder(INTERNAL, EXTERNAL);
}
@Test
public void loadUser() {
when(credential.getAttributeAsStringArray("isMemberOf"))
.thenReturn(new String[]{"external", "ignore"});
LGUser result = (LGUser) subject.loadUserBySAML(credential);
assertThat(result.getFederatedUser()).isEqualTo("1");
assertThat(result.getGivenName()).isEqualTo("barry");
assertThat(result.getSurname()).isEqualTo("scott");
assertThat(result.getMail()).isEqualTo("barry.scott@geant.org");
assertThat(result.getAuthorities()).containsExactlyInAnyOrder(EXTERNAL);
}
}
...@@ -17,3 +17,12 @@ saml: ...@@ -17,3 +17,12 @@ saml:
key-store: classpath:saml/keystore.jks key-store: classpath:saml/keystore.jks
key-alias: spring key-alias: spring
key-store-password: secret key-store-password: secret
key-mappings:
id: "id"
given-name: "givenName"
surname: "surname"
email: "mail"
groups: "isMemberOf"
group-mappings:
INTERNAL: "GEANT Staff:All"
EXTERNAL: "GEANT_CO:GEANT Services:NREN Svc Mgmt:GEANT NRENs:members_GEANT NRENs"
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment