#7680: Support SAML Authentication
parent
a5061759b8
commit
8ac8f9588c
@ -1,11 +1,11 @@
|
||||
package eu.eudat.logic.security.customproviders.ConfigurableProvider;
|
||||
|
||||
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.ConfigurableProviderUserSettings;
|
||||
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.oauth2.Oauth2ConfigurableProviderUserSettings;
|
||||
import eu.eudat.logic.security.validators.configurableProvider.helpers.ConfigurableProviderResponseToken;
|
||||
|
||||
public interface ConfigurableProviderCustomProvider {
|
||||
|
||||
ConfigurableProviderResponseToken getAccessToken(String code, String redirectUri, String clientId, String clientSecret, String accessTokenUrl, String grantType, String access_token, String expires_in);
|
||||
|
||||
ConfigurableProviderUser getUser(String accessToken, ConfigurableProviderUserSettings user);
|
||||
ConfigurableProviderUser getUser(String accessToken, Oauth2ConfigurableProviderUserSettings user);
|
||||
}
|
||||
|
@ -0,0 +1,88 @@
|
||||
package eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.oauth2;
|
||||
|
||||
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.ConfigurableProvider;
|
||||
|
||||
public class Oauth2ConfigurableProvider extends ConfigurableProvider {
|
||||
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private String redirect_uri;
|
||||
private String access_token_url;
|
||||
private String grant_type;
|
||||
private Oauth2ConfigurableProviderToken token;
|
||||
private Oauth2ConfigurableProviderUserSettings user;
|
||||
private String oauthUrl;
|
||||
private String scope;
|
||||
private String state;
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getClientSecret() {
|
||||
return clientSecret;
|
||||
}
|
||||
public void setClientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
public String getRedirect_uri() {
|
||||
return redirect_uri;
|
||||
}
|
||||
public void setRedirect_uri(String redirect_uri) {
|
||||
this.redirect_uri = redirect_uri;
|
||||
}
|
||||
|
||||
public String getAccess_token_url() {
|
||||
return access_token_url;
|
||||
}
|
||||
public void setAccess_token_url(String access_token_url) {
|
||||
this.access_token_url = access_token_url;
|
||||
}
|
||||
|
||||
public String getGrant_type() {
|
||||
return grant_type;
|
||||
}
|
||||
public void setGrant_type(String grant_type) {
|
||||
this.grant_type = grant_type;
|
||||
}
|
||||
|
||||
public Oauth2ConfigurableProviderToken getToken() {
|
||||
return token;
|
||||
}
|
||||
public void setToken(Oauth2ConfigurableProviderToken token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
public Oauth2ConfigurableProviderUserSettings getUser() {
|
||||
return user;
|
||||
}
|
||||
public void setUser(Oauth2ConfigurableProviderUserSettings user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public String getOauthUrl() {
|
||||
return oauthUrl;
|
||||
}
|
||||
public void setOauthUrl(String oauthUrl) {
|
||||
this.oauthUrl = oauthUrl;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
public void setScope(String scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package eu.eudat.logic.security.customproviders.ConfigurableProvider.entities;
|
||||
package eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.oauth2;
|
||||
|
||||
public class ConfigurableProviderToken {
|
||||
public class Oauth2ConfigurableProviderToken {
|
||||
private String access_token;
|
||||
private String expires_in;
|
||||
|
@ -1,6 +1,6 @@
|
||||
package eu.eudat.logic.security.customproviders.ConfigurableProvider.entities;
|
||||
package eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.oauth2;
|
||||
|
||||
public class ConfigurableProviderUserSettings {
|
||||
public class Oauth2ConfigurableProviderUserSettings {
|
||||
private String id;
|
||||
private String name;
|
||||
private String email;
|
@ -0,0 +1,147 @@
|
||||
package eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.saml2;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.ConfigurableProvider;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class Saml2ConfigurableProvider extends ConfigurableProvider {
|
||||
|
||||
public enum SAML2UsingFormat {
|
||||
NAME("name"), FRIENDLY_NAME("friendly_name");
|
||||
|
||||
private String name;
|
||||
SAML2UsingFormat(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
@JsonValue
|
||||
public String getName() { return name; }
|
||||
|
||||
public static SAML2UsingFormat fromName(String name) {
|
||||
for (SAML2UsingFormat type: SAML2UsingFormat.values()) {
|
||||
if (name.equals(type.getName())) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unsupported SAML2 Attribute " + name);
|
||||
}
|
||||
}
|
||||
|
||||
public enum SAML2AttributeType {
|
||||
XSSTRING("XSString"), XSINTEGER("XSInteger"), XSDATETIME("XSDateTime"), XSBOOLEAN("XSBoolean"), XSBASE64BINARY("XSBase64Binary"), XSURI("XSURI"), XSQNAME("XSQName"), XSANY("XSAny");
|
||||
|
||||
private String type;
|
||||
SAML2AttributeType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
@JsonValue
|
||||
public String getType() { return type; }
|
||||
|
||||
public static SAML2AttributeType fromType(String type) {
|
||||
for (SAML2AttributeType t: SAML2AttributeType.values()) {
|
||||
if (type.equals(t.getType())) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Unsupported SAML2 Attribute Type " + type);
|
||||
}
|
||||
}
|
||||
|
||||
private String spEntityId;
|
||||
private String idpEntityId;
|
||||
private String idpUrl;
|
||||
private String idpMetadataUrl;
|
||||
private boolean responseSigned;
|
||||
private String signedResponseCredential;
|
||||
private boolean assertionSigned;
|
||||
private SAML2UsingFormat usingFormat;
|
||||
private Map<String, SAML2AttributeType> attributeTypes;
|
||||
private Map<String, String> configurableUserFromAttributes;
|
||||
private String binding;
|
||||
//private String assertionConsumerServiceUrl;
|
||||
|
||||
public String getSpEntityId() {
|
||||
return spEntityId;
|
||||
}
|
||||
public void setSpEntityId(String spEntityId) {
|
||||
this.spEntityId = spEntityId;
|
||||
}
|
||||
|
||||
public String getIdpEntityId() {
|
||||
return idpEntityId;
|
||||
}
|
||||
public void setIdpEntityId(String idpEntityId) {
|
||||
this.idpEntityId = idpEntityId;
|
||||
}
|
||||
|
||||
public String getIdpUrl() {
|
||||
return idpUrl;
|
||||
}
|
||||
public void setIdpUrl(String idpUrl) {
|
||||
this.idpUrl = idpUrl;
|
||||
}
|
||||
|
||||
public String getIdpMetadataUrl() {
|
||||
return idpMetadataUrl;
|
||||
}
|
||||
public void setIdpMetadataUrl(String idpMetadataUrl) {
|
||||
this.idpMetadataUrl = idpMetadataUrl;
|
||||
}
|
||||
|
||||
public boolean isResponseSigned() {
|
||||
return responseSigned;
|
||||
}
|
||||
public void setResponseSigned(boolean responseSigned) {
|
||||
this.responseSigned = responseSigned;
|
||||
}
|
||||
|
||||
public String getSignedResponseCredential() {
|
||||
return signedResponseCredential;
|
||||
}
|
||||
public void setSignedResponseCredential(String signedResponseCredential) {
|
||||
this.signedResponseCredential = signedResponseCredential;
|
||||
}
|
||||
|
||||
public boolean isAssertionSigned() {
|
||||
return assertionSigned;
|
||||
}
|
||||
public void setAssertionSigned(boolean assertionSigned) {
|
||||
this.assertionSigned = assertionSigned;
|
||||
}
|
||||
|
||||
public SAML2UsingFormat getUsingFormat() {
|
||||
return usingFormat;
|
||||
}
|
||||
public void setUsingFormat(SAML2UsingFormat usingFormat) {
|
||||
this.usingFormat = usingFormat;
|
||||
}
|
||||
|
||||
public Map<String, String> getConfigurableUserFromAttributes() {
|
||||
return configurableUserFromAttributes;
|
||||
}
|
||||
public void setConfigurableUserFromAttributes(Map<String, String> configurableUserFromAttributes) {
|
||||
this.configurableUserFromAttributes = configurableUserFromAttributes;
|
||||
}
|
||||
|
||||
public Map<String, SAML2AttributeType> getAttributeTypes() {
|
||||
return attributeTypes;
|
||||
}
|
||||
public void setAttributeTypes(Map<String, SAML2AttributeType> attributeTypes) {
|
||||
this.attributeTypes = attributeTypes;
|
||||
}
|
||||
|
||||
public String getBinding() {
|
||||
return binding;
|
||||
}
|
||||
public void setBinding(String binding) {
|
||||
this.binding = binding;
|
||||
}
|
||||
|
||||
// public String getAssertionConsumerServiceUrl() {
|
||||
// return assertionConsumerServiceUrl;
|
||||
// }
|
||||
// public void setAssertionConsumerServiceUrl(String assertionConsumerServiceUrl) {
|
||||
// this.assertionConsumerServiceUrl = assertionConsumerServiceUrl;
|
||||
// }
|
||||
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package eu.eudat.logic.security.customproviders.ConfigurableProvider.models.oauth2;
|
||||
|
||||
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.ConfigurableProvider;
|
||||
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.oauth2.Oauth2ConfigurableProvider;
|
||||
import eu.eudat.logic.security.customproviders.ConfigurableProvider.models.ConfigurableProviderModel;
|
||||
|
||||
public class Oauth2ConfigurableProviderModel extends ConfigurableProviderModel {
|
||||
|
||||
private String clientId;
|
||||
private String redirect_uri;
|
||||
private String oauthUrl;
|
||||
private String scope;
|
||||
private String state;
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getRedirect_uri() {
|
||||
return redirect_uri;
|
||||
}
|
||||
public void setRedirect_uri(String redirect_uri) {
|
||||
this.redirect_uri = redirect_uri;
|
||||
}
|
||||
|
||||
public String getOauthUrl() {
|
||||
return oauthUrl;
|
||||
}
|
||||
public void setOauthUrl(String oauthUrl) {
|
||||
this.oauthUrl = oauthUrl;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
public void setScope(String scope) {
|
||||
this.scope = scope;
|
||||
}
|
||||
|
||||
public String getState() {
|
||||
return state;
|
||||
}
|
||||
public void setState(String state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Oauth2ConfigurableProviderModel fromDataModel(ConfigurableProvider entity) {
|
||||
Oauth2ConfigurableProviderModel model = new Oauth2ConfigurableProviderModel();
|
||||
model.setConfigurableLoginId(entity.getConfigurableLoginId());
|
||||
model.setType(entity.getType());
|
||||
model.setName(entity.getName());
|
||||
model.setLogoUrl(entity.getLogoUrl());
|
||||
model.setClientId(((Oauth2ConfigurableProvider)entity).getClientId());
|
||||
model.setRedirect_uri(((Oauth2ConfigurableProvider)entity).getRedirect_uri());
|
||||
model.setOauthUrl(((Oauth2ConfigurableProvider)entity).getOauthUrl());
|
||||
model.setScope(((Oauth2ConfigurableProvider)entity).getScope());
|
||||
model.setState(((Oauth2ConfigurableProvider)entity).getState());
|
||||
return model;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
package eu.eudat.logic.security.customproviders.ConfigurableProvider.models.saml2;
|
||||
|
||||
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.ConfigurableProvider;
|
||||
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.saml2.Saml2ConfigurableProvider;
|
||||
import eu.eudat.logic.security.customproviders.ConfigurableProvider.models.ConfigurableProviderModel;
|
||||
|
||||
public class Saml2ConfigurableProviderModel extends ConfigurableProviderModel {
|
||||
|
||||
private String spEntityId;
|
||||
private String idpUrl;
|
||||
private String binding;
|
||||
//private String assertionConsumerServiceUrl;
|
||||
|
||||
public String getSpEntityId() {
|
||||
return spEntityId;
|
||||
}
|
||||
public void setSpEntityId(String spEntityId) {
|
||||
this.spEntityId = spEntityId;
|
||||
}
|
||||
|
||||
public String getIdpUrl() {
|
||||
return idpUrl;
|
||||
}
|
||||
public void setIdpUrl(String idpUrl) {
|
||||
this.idpUrl = idpUrl;
|
||||
}
|
||||
|
||||
public String getBinding() {
|
||||
return binding;
|
||||
}
|
||||
public void setBinding(String binding) {
|
||||
this.binding = binding;
|
||||
}
|
||||
|
||||
// public String getAssertionConsumerServiceUrl() {
|
||||
// return assertionConsumerServiceUrl;
|
||||
// }
|
||||
// public void setAssertionConsumerServiceUrl(String assertionConsumerServiceUrl) {
|
||||
// this.assertionConsumerServiceUrl = assertionConsumerServiceUrl;
|
||||
// }
|
||||
|
||||
@Override
|
||||
public Saml2ConfigurableProviderModel fromDataModel(ConfigurableProvider entity) {
|
||||
Saml2ConfigurableProviderModel model = new Saml2ConfigurableProviderModel();
|
||||
model.setConfigurableLoginId(entity.getConfigurableLoginId());
|
||||
model.setType(entity.getType());
|
||||
model.setName(entity.getName());
|
||||
model.setLogoUrl(entity.getLogoUrl());
|
||||
model.setSpEntityId(((Saml2ConfigurableProvider)entity).getSpEntityId());
|
||||
model.setIdpUrl(((Saml2ConfigurableProvider)entity).getIdpUrl());
|
||||
model.setBinding(((Saml2ConfigurableProvider)entity).getBinding());
|
||||
//model.setAssertionConsumerServiceUrl(((Saml2ConfigurableProvider)entity).getAssertionConsumerServiceUrl());
|
||||
return model;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,322 @@
|
||||
package eu.eudat.logic.security.validators.configurableProvider;
|
||||
|
||||
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.saml2.Saml2ConfigurableProvider;
|
||||
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
|
||||
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;
|
||||
import net.shibboleth.utilities.java.support.xml.BasicParserPool;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.opensaml.core.config.ConfigurationService;
|
||||
import org.opensaml.core.config.InitializationException;
|
||||
import org.opensaml.core.config.InitializationService;
|
||||
import org.opensaml.core.criterion.EntityIdCriterion;
|
||||
import org.opensaml.core.xml.XMLObject;
|
||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistry;
|
||||
import org.opensaml.core.xml.io.Unmarshaller;
|
||||
import org.opensaml.core.xml.io.UnmarshallerFactory;
|
||||
import org.opensaml.core.xml.io.UnmarshallingException;
|
||||
import org.opensaml.core.xml.schema.*;
|
||||
import org.opensaml.saml.common.xml.SAMLConstants;
|
||||
import org.opensaml.saml.criterion.EntityRoleCriterion;
|
||||
import org.opensaml.saml.criterion.ProtocolCriterion;
|
||||
import org.opensaml.saml.metadata.resolver.impl.HTTPMetadataResolver;
|
||||
import org.opensaml.saml.metadata.resolver.impl.PredicateRoleDescriptorResolver;
|
||||
import org.opensaml.saml.saml2.core.*;
|
||||
import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
|
||||
import org.opensaml.saml.security.impl.MetadataCredentialResolver;
|
||||
import org.opensaml.security.credential.Credential;
|
||||
import org.opensaml.security.credential.UsageType;
|
||||
import org.opensaml.security.criteria.UsageCriterion;
|
||||
import org.opensaml.security.x509.X509Credential;
|
||||
import org.opensaml.xml.util.Base64;
|
||||
import org.opensaml.xmlsec.config.impl.DefaultSecurityConfigurationBootstrap;
|
||||
import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver;
|
||||
import org.opensaml.xmlsec.signature.support.SignatureValidator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.w3c.dom.Document;
|
||||
import org.w3c.dom.Element;
|
||||
import org.xml.sax.SAXException;
|
||||
|
||||
import javax.xml.parsers.DocumentBuilder;
|
||||
import javax.xml.parsers.DocumentBuilderFactory;
|
||||
import javax.xml.parsers.ParserConfigurationException;
|
||||
import java.io.*;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.Inflater;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
public class Saml2SSOUtils {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(Saml2SSOUtils.class);
|
||||
private static boolean isBootStrapped = false;
|
||||
private static BasicParserPool parserPool;
|
||||
private static XMLObjectProviderRegistry registry;
|
||||
|
||||
private Saml2SSOUtils() {
|
||||
}
|
||||
|
||||
private static void doBootstrap() throws Exception {
|
||||
if (!isBootStrapped) {
|
||||
try {
|
||||
boostrap();
|
||||
isBootStrapped = true;
|
||||
} catch (Exception e) {
|
||||
throw new Exception("Error in bootstrapping the OpenSAML2 library", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void boostrap(){
|
||||
parserPool = new BasicParserPool();
|
||||
parserPool.setMaxPoolSize(100);
|
||||
parserPool.setCoalescing(true);
|
||||
parserPool.setIgnoreComments(true);
|
||||
parserPool.setIgnoreElementContentWhitespace(true);
|
||||
parserPool.setNamespaceAware(true);
|
||||
parserPool.setExpandEntityReferences(false);
|
||||
parserPool.setXincludeAware(false);
|
||||
|
||||
final Map<String, Boolean> features = new HashMap<String, Boolean>();
|
||||
features.put("http://xml.org/sax/features/external-general-entities", Boolean.FALSE);
|
||||
features.put("http://xml.org/sax/features/external-parameter-entities", Boolean.FALSE);
|
||||
features.put("http://apache.org/xml/features/disallow-doctype-decl", Boolean.TRUE);
|
||||
features.put("http://apache.org/xml/features/validation/schema/normalized-value", Boolean.FALSE);
|
||||
features.put("http://javax.xml.XMLConstants/feature/secure-processing", Boolean.TRUE);
|
||||
|
||||
parserPool.setBuilderFeatures(features);
|
||||
|
||||
parserPool.setBuilderAttributes(new HashMap<String, Object>());
|
||||
|
||||
try {
|
||||
parserPool.initialize();
|
||||
} catch (ComponentInitializationException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
}
|
||||
|
||||
registry = new XMLObjectProviderRegistry();
|
||||
ConfigurationService.register(XMLObjectProviderRegistry.class, registry);
|
||||
registry.setParserPool(parserPool);
|
||||
|
||||
try {
|
||||
InitializationService.initialize();
|
||||
} catch (InitializationException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static String getAttributeName(Attribute attribute, Saml2ConfigurableProvider.SAML2UsingFormat usingFormat){
|
||||
String friendlyName = attribute.getFriendlyName();
|
||||
String name = attribute.getName();
|
||||
if(usingFormat.getName().equals(Saml2ConfigurableProvider.SAML2UsingFormat.FRIENDLY_NAME.getName())){
|
||||
return (friendlyName != null) ? friendlyName : name;
|
||||
}
|
||||
else{
|
||||
return (name != null) ? name : friendlyName;
|
||||
}
|
||||
}
|
||||
|
||||
public static Object getAttributeType(XMLObject attribute, Saml2ConfigurableProvider.SAML2AttributeType attributeType){
|
||||
|
||||
if(attributeType.getType().equals(Saml2ConfigurableProvider.SAML2AttributeType.XSSTRING.getType())){
|
||||
return ((XSString)attribute).getValue();
|
||||
}
|
||||
else if(attributeType.getType().equals(Saml2ConfigurableProvider.SAML2AttributeType.XSINTEGER.getType())){
|
||||
return ((XSInteger)attribute).getValue();
|
||||
}
|
||||
else if(attributeType.getType().equals(Saml2ConfigurableProvider.SAML2AttributeType.XSDATETIME.getType())){
|
||||
return ((XSDateTime)attribute).getValue();
|
||||
}
|
||||
else if(attributeType.getType().equals(Saml2ConfigurableProvider.SAML2AttributeType.XSBOOLEAN.getType())){
|
||||
return ((XSBoolean)attribute).getValue();
|
||||
}
|
||||
else if(attributeType.getType().equals(Saml2ConfigurableProvider.SAML2AttributeType.XSBASE64BINARY.getType())){
|
||||
return ((XSBase64Binary)attribute).getValue();
|
||||
}
|
||||
else if(attributeType.getType().equals(Saml2ConfigurableProvider.SAML2AttributeType.XSURI.getType())){
|
||||
return ((XSURI)attribute).getURI();
|
||||
}
|
||||
else if(attributeType.getType().equals(Saml2ConfigurableProvider.SAML2AttributeType.XSQNAME.getType())){
|
||||
return ((XSQName)attribute).getValue();
|
||||
}
|
||||
else if(attributeType.getType().equals(Saml2ConfigurableProvider.SAML2AttributeType.XSANY.getType())){
|
||||
return ((XSAny)attribute).getTextContent();
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static XMLObject unmarshall(String saml2SSOString) throws Exception {
|
||||
|
||||
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||
//documentBuilderFactory.setExpandEntityReferences(false);
|
||||
documentBuilderFactory.setNamespaceAware(true);
|
||||
try {
|
||||
DocumentBuilder docBuilder = documentBuilderFactory.newDocumentBuilder();
|
||||
ByteArrayInputStream is = new ByteArrayInputStream(saml2SSOString.getBytes(StandardCharsets.UTF_8));
|
||||
Document document = docBuilder.parse(is);
|
||||
Element element = document.getDocumentElement();
|
||||
|
||||
UnmarshallerFactory unmarshallerFactory = registry.getUnmarshallerFactory();
|
||||
Unmarshaller unmarshaller = unmarshallerFactory.getUnmarshaller(element);
|
||||
return unmarshaller.unmarshall(element);
|
||||
} catch (ParserConfigurationException | UnmarshallingException | SAXException | IOException e) {
|
||||
throw new Exception("Error in unmarshalling SAML2SSO Request from the encoded String", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static Response processResponse(String saml2SSOResponse, String idPEntityId, String spEntityId, String metadataUrl, boolean responseSigned, boolean assertionSigned) throws Exception {
|
||||
|
||||
doBootstrap();
|
||||
if (saml2SSOResponse != null) {
|
||||
byte[] decodedResponse = Base64.decode(saml2SSOResponse);
|
||||
ByteArrayInputStream bytesIn = new ByteArrayInputStream(decodedResponse);
|
||||
InflaterInputStream inflater = new InflaterInputStream(bytesIn, new Inflater(true));
|
||||
String response = new BufferedReader(new InputStreamReader(inflater, StandardCharsets.UTF_8))
|
||||
.lines().collect(Collectors.joining("\n"));
|
||||
return processSSOResponse(response, idPEntityId, spEntityId, metadataUrl, responseSigned, assertionSigned);
|
||||
|
||||
} else {
|
||||
throw new Exception("Invalid SAML2 Response. SAML2 Response can not be null.");
|
||||
}
|
||||
}
|
||||
|
||||
private static Response processSSOResponse(String saml2SSOResponse, String idPEntityId, String spEntityId, String metadataUrl, boolean responseSigned, boolean assertionSigned) throws Exception {
|
||||
|
||||
Response saml2Response = (Response) Saml2SSOUtils.unmarshall(saml2SSOResponse);
|
||||
|
||||
Assertion assertion = null;
|
||||
// if (config.isAssertionEncrypted()) {
|
||||
// List<EncryptedAssertion> encryptedAssertions = saml2Response.getEncryptedAssertions();
|
||||
// EncryptedAssertion encryptedAssertion = null;
|
||||
// if (!CollectionUtils.isEmpty(encryptedAssertions)) {
|
||||
// encryptedAssertion = encryptedAssertions.get(0);
|
||||
// try {
|
||||
// assertion = getDecryptedAssertion(encryptedAssertion);
|
||||
// } catch (Exception e) {
|
||||
// if (log.isDebugEnabled()) {
|
||||
// log.debug("Assertion decryption failure : ", e);
|
||||
// }
|
||||
// throw new Exception("Unable to decrypt the SAML2 Assertion");
|
||||
// }
|
||||
// }
|
||||
// } else {
|
||||
List<Assertion> assertions = saml2Response.getAssertions();
|
||||
if (assertions != null && !assertions.isEmpty()) {
|
||||
assertion = assertions.get(0);
|
||||
}
|
||||
// }
|
||||
if (assertion == null) {
|
||||
throw new Exception("SAML2 Assertion not found in the Response");
|
||||
}
|
||||
|
||||
String idPEntityIdValue = assertion.getIssuer().getValue();
|
||||
if (idPEntityIdValue == null || idPEntityIdValue.isEmpty()) {
|
||||
throw new Exception("SAML2 Response does not contain an Issuer value");
|
||||
} else if (!idPEntityIdValue.equals(idPEntityId)) {
|
||||
throw new Exception("SAML2 Response Issuer verification failed");
|
||||
}
|
||||
|
||||
String subject = null;
|
||||
if (assertion.getSubject() != null && assertion.getSubject().getNameID() != null) {
|
||||
subject = assertion.getSubject().getNameID().getValue();
|
||||
}
|
||||
|
||||
if (subject == null) {
|
||||
throw new Exception("SAML2 Response does not contain the name of the subject");
|
||||
}
|
||||
|
||||
validateAudienceRestriction(assertion, spEntityId);
|
||||
|
||||
final HTTPMetadataResolver metadataResolver = new HTTPMetadataResolver(HttpClientBuilder.create().build(), metadataUrl);
|
||||
metadataResolver.setId(metadataResolver.getClass().getCanonicalName());
|
||||
metadataResolver.setParserPool(parserPool);
|
||||
metadataResolver.initialize();
|
||||
|
||||
final MetadataCredentialResolver metadataCredentialResolver = new MetadataCredentialResolver();
|
||||
final PredicateRoleDescriptorResolver roleResolver = new PredicateRoleDescriptorResolver(metadataResolver);
|
||||
final KeyInfoCredentialResolver keyResolver = DefaultSecurityConfigurationBootstrap.buildBasicInlineKeyInfoCredentialResolver();
|
||||
metadataCredentialResolver.setKeyInfoCredentialResolver(keyResolver);
|
||||
metadataCredentialResolver.setRoleDescriptorResolver(roleResolver);
|
||||
metadataCredentialResolver.initialize();
|
||||
roleResolver.initialize();
|
||||
|
||||
CriteriaSet criteriaSet = new CriteriaSet();
|
||||
criteriaSet.add(new UsageCriterion(UsageType.SIGNING));
|
||||
criteriaSet.add(new EntityRoleCriterion(IDPSSODescriptor.DEFAULT_ELEMENT_NAME));
|
||||
criteriaSet.add(new ProtocolCriterion(SAMLConstants.SAML20P_NS));
|
||||
criteriaSet.add(new EntityIdCriterion(idPEntityId));
|
||||
|
||||
Credential credential = metadataCredentialResolver.resolveSingle(criteriaSet);
|
||||
|
||||
validateSignature(saml2Response, assertion, responseSigned, null, assertionSigned, credential);
|
||||
|
||||
return saml2Response;
|
||||
|
||||
}
|
||||
|
||||
private static void validateAudienceRestriction(Assertion assertion, String requiredSPEntityId) throws Exception {
|
||||
|
||||
if (assertion != null) {
|
||||
Conditions conditions = assertion.getConditions();
|
||||
if (conditions != null) {
|
||||
List<AudienceRestriction> audienceRestrictions = conditions.getAudienceRestrictions();
|
||||
if (audienceRestrictions != null && !audienceRestrictions.isEmpty()) {
|
||||
boolean audienceFound = false;
|
||||
for (AudienceRestriction audienceRestriction : audienceRestrictions) {
|
||||
if (audienceRestriction.getAudiences() != null && !audienceRestriction.getAudiences().isEmpty()
|
||||
) {
|
||||
for (Audience audience : audienceRestriction.getAudiences()) {
|
||||
if (requiredSPEntityId.equals(audience.getURI())) {
|
||||
audienceFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (audienceFound) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!audienceFound) {
|
||||
throw new Exception("SAML2 Assertion Audience Restriction validation failed");
|
||||
}
|
||||
} else {
|
||||
throw new Exception("SAML2 Response doesn't contain AudienceRestrictions");
|
||||
}
|
||||
} else {
|
||||
throw new Exception("SAML2 Response doesn't contain Conditions");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateSignature(Response response, Assertion assertion, Boolean isResponseSigned, X509Credential signedResponseCredential, Boolean isAssertionSigned, Credential assertionCredential) throws Exception {
|
||||
|
||||
if (isResponseSigned) {
|
||||
if (response.getSignature() == null) {
|
||||
throw new Exception("SAML2 Response signing is enabled, but signature element not found in SAML2 Response element");
|
||||
} else {
|
||||
try {
|
||||
SignatureValidator.validate(response.getSignature(), signedResponseCredential);
|
||||
} catch (Exception e) {
|
||||
throw new Exception("Signature validation failed for SAML2 Response");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (isAssertionSigned) {
|
||||
if (assertion.getSignature() == null) {
|
||||
throw new Exception("SAML2 Assertion signing is enabled, but signature element not found in SAML2 Assertion element");
|
||||
} else {
|
||||
try {
|
||||
SignatureValidator.validate(assertion.getSignature(), assertionCredential);
|
||||
} catch (Exception e) {
|
||||
throw new Exception("Signature validation failed for SAML2 Assertion");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
export enum ConfigurableProviderType {
|
||||
Oauth2 = "oauth2",
|
||||
Saml2 = "saml2"
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
export class ConfigurableProvider {
|
||||
configurableLoginId: string;
|
||||
type: string;
|
||||
name: string;
|
||||
clientId: string;
|
||||
redirect_uri: string;
|
||||
oauthUrl: string;
|
||||
scope: string;
|
||||
state: string;
|
||||
logoUrl: string;
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
import { ConfigurableProvider } from "./configurableProvider";
|
||||
|
||||
export class Oauth2ConfigurableProvider extends ConfigurableProvider{
|
||||
clientId: string;
|
||||
redirect_uri: string;
|
||||
oauthUrl: string;
|
||||
scope: string;
|
||||
state: string;
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
import { ConfigurableProvider } from "./configurableProvider";
|
||||
|
||||
export class Saml2ConfigurableProvider extends ConfigurableProvider{
|
||||
spEntityId: string;
|
||||
idpUrl: string;
|
||||
binding: string;
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Guid } from '@common/types/guid';
|
||||
import * as pk from 'pako';
|
||||
|
||||
@Injectable()
|
||||
export class SamlLoginService {
|
||||
|
||||
constructor() {}
|
||||
|
||||
buildRelayState(spId: string, configurableLoginId: string): string {
|
||||
let uri = 'spId=' + spId;
|
||||
uri += '&configurableLoginId=' + configurableLoginId;
|
||||
return encodeURIComponent(uri);
|
||||
}
|
||||
|
||||
resolveConfigurableLoginId(relayState: string): string {
|
||||
const decoded = decodeURIComponent(relayState);
|
||||
const routeParams = new URLSearchParams(decoded);
|
||||
return routeParams.has('configurableLoginId') ? routeParams.get('configurableLoginId') : undefined;
|
||||
}
|
||||
resolveSpId(relayState: string): string {
|
||||
const decoded = decodeURIComponent(relayState);
|
||||
const routeParams = new URLSearchParams(decoded);
|
||||
return routeParams.has('spId') ? routeParams.get('spId') : '';
|
||||
}
|
||||
|
||||
getSamlLoginUrl(spEntityID: string, idpUrl: string, binding: string, configurableLoginId: string) {
|
||||
const now = new Date();
|
||||
let protocolBinding = '';
|
||||
switch (binding) {
|
||||
case "Redirect": protocolBinding = 'ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" '; break;
|
||||
case "Artifact": protocolBinding = 'ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact" '; break;
|
||||
case "Post": protocolBinding = 'ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Post" '; break;
|
||||
}
|
||||
const authenticationRequest = '<saml2p:AuthnRequest xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" ID="_' + Guid.create() + '" Version="2.0" ' +
|
||||
'IssueInstant="' + now.toISOString() + '" ' +
|
||||
protocolBinding +
|
||||
'Destination="' + idpUrl + '">' +
|
||||
'<saml2:Issuer>' + spEntityID + '</saml2:Issuer>' +
|
||||
'</saml2p:AuthnRequest>';
|
||||
const uint = new Uint8Array(authenticationRequest.length);
|
||||
for (let i = 0, j = authenticationRequest.length; i < j; ++i) {
|
||||
uint[i] = authenticationRequest.charCodeAt(i);
|
||||