#7680: Support SAML Authentication
This commit is contained in:
parent
a5061759b8
commit
8ac8f9588c
|
@ -120,6 +120,56 @@
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-starter-tomcat</artifactId>
|
<artifactId>spring-boot-starter-tomcat</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.opensaml</groupId>
|
||||||
|
<artifactId>opensaml-core</artifactId>
|
||||||
|
<version>${opensaml.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.opensaml</groupId>
|
||||||
|
<artifactId>opensaml-saml-api</artifactId>
|
||||||
|
<version>${opensaml.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.opensaml</groupId>
|
||||||
|
<artifactId>opensaml-saml-impl</artifactId>
|
||||||
|
<version>${opensaml.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.opensaml</groupId>
|
||||||
|
<artifactId>opensaml-soap-api</artifactId>
|
||||||
|
<version>${opensaml.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.opensaml</groupId>
|
||||||
|
<artifactId>opensaml-xmlsec-api</artifactId>
|
||||||
|
<version>${opensaml.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.opensaml</groupId>
|
||||||
|
<artifactId>opensaml-security-api</artifactId>
|
||||||
|
<version>${opensaml.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.opensaml</groupId>
|
||||||
|
<artifactId>opensaml-security-impl</artifactId>
|
||||||
|
<version>${opensaml.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.opensaml</groupId>
|
||||||
|
<artifactId>opensaml-profile-api</artifactId>
|
||||||
|
<version>${opensaml.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.opensaml</groupId>
|
||||||
|
<artifactId>xmltooling</artifactId>
|
||||||
|
<version>1.4.4</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@ -155,5 +205,6 @@
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<start-class>eu.eudat.EuDatApplication</start-class>
|
<start-class>eu.eudat.EuDatApplication</start-class>
|
||||||
|
<opensaml.version>4.0.1</opensaml.version>
|
||||||
</properties>
|
</properties>
|
||||||
</project>
|
</project>
|
|
@ -7,6 +7,8 @@ import eu.eudat.models.data.login.LoginInfo;
|
||||||
import eu.eudat.models.data.principal.PrincipalModel;
|
import eu.eudat.models.data.principal.PrincipalModel;
|
||||||
import eu.eudat.models.data.security.Principal;
|
import eu.eudat.models.data.security.Principal;
|
||||||
import eu.eudat.logic.security.validators.TokenValidatorFactory;
|
import eu.eudat.logic.security.validators.TokenValidatorFactory;
|
||||||
|
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
|
||||||
|
import net.shibboleth.utilities.java.support.resolver.ResolverException;
|
||||||
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;
|
||||||
|
@ -27,7 +29,7 @@ public class CustomAuthenticationProvider {
|
||||||
String token = credentials.getTicket();
|
String token = credentials.getTicket();
|
||||||
try {
|
try {
|
||||||
Principal principal = this.tokenValidatorFactory.getProvider(credentials.getProvider()).validateToken(credentials);
|
Principal principal = this.tokenValidatorFactory.getProvider(credentials.getProvider()).validateToken(credentials);
|
||||||
return PrincipalModel.fromEntity(principal);
|
return (principal != null) ? PrincipalModel.fromEntity(principal) : null;
|
||||||
} catch (NonValidTokenException e) {
|
} catch (NonValidTokenException e) {
|
||||||
logger.error("Could not validate a user by his token! Reason: " + e.getMessage(), e);
|
logger.error("Could not validate a user by his token! Reason: " + e.getMessage(), e);
|
||||||
throw new UnauthorisedException("Token validation failed - Not a valid token");
|
throw new UnauthorisedException("Token validation failed - Not a valid token");
|
||||||
|
@ -37,6 +39,9 @@ public class CustomAuthenticationProvider {
|
||||||
} catch (NullEmailException e) {
|
} catch (NullEmailException e) {
|
||||||
logger.error(e.getMessage(), e);
|
logger.error(e.getMessage(), e);
|
||||||
throw new NullEmailException();
|
throw new NullEmailException();
|
||||||
|
} catch (ResolverException | ComponentInitializationException e){
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
throw new GeneralSecurityException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
package eu.eudat.logic.security.customproviders.ConfigurableProvider;
|
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;
|
import eu.eudat.logic.security.validators.configurableProvider.helpers.ConfigurableProviderResponseToken;
|
||||||
|
|
||||||
public interface ConfigurableProviderCustomProvider {
|
public interface ConfigurableProviderCustomProvider {
|
||||||
|
|
||||||
ConfigurableProviderResponseToken getAccessToken(String code, String redirectUri, String clientId, String clientSecret, String accessTokenUrl, String grantType, String access_token, String expires_in);
|
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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package eu.eudat.logic.security.customproviders.ConfigurableProvider;
|
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;
|
import eu.eudat.logic.security.validators.configurableProvider.helpers.ConfigurableProviderResponseToken;
|
||||||
import org.springframework.http.HttpEntity;
|
import org.springframework.http.HttpEntity;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
|
@ -43,7 +43,7 @@ public class ConfigurableProviderCustomProviderImpl implements ConfigurableProvi
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ConfigurableProviderUser getUser(String accessToken, ConfigurableProviderUserSettings user) {
|
public ConfigurableProviderUser getUser(String accessToken, Oauth2ConfigurableProviderUserSettings user) {
|
||||||
RestTemplate restTemplate = new RestTemplate();
|
RestTemplate restTemplate = new RestTemplate();
|
||||||
HttpHeaders headers = this.createBearerAuthHeaders(accessToken);
|
HttpHeaders headers = this.createBearerAuthHeaders(accessToken);
|
||||||
HttpEntity<String> entity = new HttpEntity<>(headers);
|
HttpEntity<String> entity = new HttpEntity<>(headers);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
package eu.eudat.logic.security.customproviders.ConfigurableProvider;
|
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 java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ public class ConfigurableProviderUser {
|
||||||
this.email = email;
|
this.email = email;
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigurableProviderUser getConfigurableProviderUser(Map data, ConfigurableProviderUserSettings user) {
|
ConfigurableProviderUser getConfigurableProviderUser(Map data, Oauth2ConfigurableProviderUserSettings user) {
|
||||||
if (user.getId() != null && !user.getId().isEmpty())
|
if (user.getId() != null && !user.getId().isEmpty())
|
||||||
this.id = (String) data.get(user.getId());
|
this.id = (String) data.get(user.getId());
|
||||||
if (user.getName() != null && !user.getName().isEmpty())
|
if (user.getName() != null && !user.getName().isEmpty())
|
||||||
|
|
|
@ -1,22 +1,23 @@
|
||||||
package eu.eudat.logic.security.customproviders.ConfigurableProvider.entities;
|
package eu.eudat.logic.security.customproviders.ConfigurableProvider.entities;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.*;
|
||||||
|
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.oauth2.Oauth2ConfigurableProvider;
|
||||||
|
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.saml2.Saml2ConfigurableProvider;
|
||||||
|
|
||||||
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", visible = true)
|
||||||
|
@JsonSubTypes({
|
||||||
|
@JsonSubTypes.Type(value = Oauth2ConfigurableProvider.class, name = "oauth2"),
|
||||||
|
@JsonSubTypes.Type(value = Saml2ConfigurableProvider.class, name = "saml2")
|
||||||
|
})
|
||||||
public class ConfigurableProvider {
|
public class ConfigurableProvider {
|
||||||
|
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
private String configurableLoginId;
|
private String configurableLoginId;
|
||||||
|
private String type;
|
||||||
private String name;
|
private String name;
|
||||||
private String clientId;
|
private String logoUrl;
|
||||||
private String clientSecret;
|
|
||||||
private String redirect_uri;
|
|
||||||
private String access_token_url;
|
|
||||||
private String grant_type;
|
|
||||||
private ConfigurableProviderToken token;
|
|
||||||
private ConfigurableProviderUserSettings user;
|
|
||||||
private String oauthUrl;
|
|
||||||
private String scope;
|
|
||||||
private String state;
|
|
||||||
|
|
||||||
public boolean getEnabled() {
|
public boolean isEnabled() {
|
||||||
return enabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
public void setEnabled(boolean enabled) {
|
public void setEnabled(boolean enabled) {
|
||||||
|
@ -30,6 +31,13 @@ public class ConfigurableProvider {
|
||||||
this.configurableLoginId = configurableLoginId;
|
this.configurableLoginId = configurableLoginId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
@ -37,73 +45,11 @@ public class ConfigurableProvider {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getClientId() {
|
public String getLogoUrl() {
|
||||||
return clientId;
|
return logoUrl;
|
||||||
}
|
}
|
||||||
public void setClientId(String clientId) {
|
public void setLogoUrl(String logoUrl) {
|
||||||
this.clientId = clientId;
|
this.logoUrl = logoUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 ConfigurableProviderToken getToken() {
|
|
||||||
return token;
|
|
||||||
}
|
|
||||||
public void setToken(ConfigurableProviderToken token) {
|
|
||||||
this.token = token;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConfigurableProviderUserSettings getUser() {
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
public void setUser(ConfigurableProviderUserSettings 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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 access_token;
|
||||||
private String expires_in;
|
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 id;
|
||||||
private String name;
|
private String name;
|
||||||
private String email;
|
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;
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
|
@ -5,12 +5,9 @@ import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.Con
|
||||||
public class ConfigurableProviderModel {
|
public class ConfigurableProviderModel {
|
||||||
|
|
||||||
private String configurableLoginId;
|
private String configurableLoginId;
|
||||||
|
private String type;
|
||||||
private String name;
|
private String name;
|
||||||
private String clientId;
|
private String logoUrl;
|
||||||
private String redirect_uri;
|
|
||||||
private String oauthUrl;
|
|
||||||
private String scope;
|
|
||||||
private String state;
|
|
||||||
|
|
||||||
public String getConfigurableLoginId() {
|
public String getConfigurableLoginId() {
|
||||||
return configurableLoginId;
|
return configurableLoginId;
|
||||||
|
@ -19,6 +16,13 @@ public class ConfigurableProviderModel {
|
||||||
this.configurableLoginId = configurableLoginId;
|
this.configurableLoginId = configurableLoginId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
public void setType(String type) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
@ -26,51 +30,20 @@ public class ConfigurableProviderModel {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getClientId() {
|
public String getLogoUrl() {
|
||||||
return clientId;
|
return logoUrl;
|
||||||
}
|
}
|
||||||
public void setClientId(String clientId) {
|
public void setLogoUrl(String logoUrl) {
|
||||||
this.clientId = clientId;
|
this.logoUrl = logoUrl;
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConfigurableProviderModel fromDataModel(ConfigurableProvider entity) {
|
public ConfigurableProviderModel fromDataModel(ConfigurableProvider entity) {
|
||||||
ConfigurableProviderModel model = new ConfigurableProviderModel();
|
ConfigurableProviderModel model = new ConfigurableProviderModel();
|
||||||
model.setConfigurableLoginId(entity.getConfigurableLoginId());
|
model.setConfigurableLoginId(entity.getConfigurableLoginId());
|
||||||
|
model.setType(entity.getType());
|
||||||
model.setName(entity.getName());
|
model.setName(entity.getName());
|
||||||
model.setClientId(entity.getClientId());
|
model.setLogoUrl(entity.getLogoUrl());
|
||||||
model.setRedirect_uri(entity.getRedirect_uri());
|
|
||||||
model.setOauthUrl(entity.getOauthUrl());
|
|
||||||
model.setScope(entity.getScope());
|
|
||||||
model.setState(entity.getState());
|
|
||||||
|
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,10 @@ package eu.eudat.logic.security.customproviders.ConfigurableProvider.models;
|
||||||
|
|
||||||
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.ConfigurableProvider;
|
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.ConfigurableProvider;
|
||||||
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.ConfigurableProviders;
|
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.ConfigurableProviders;
|
||||||
|
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.oauth2.Oauth2ConfigurableProvider;
|
||||||
|
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.saml2.Saml2ConfigurableProvider;
|
||||||
|
import eu.eudat.logic.security.customproviders.ConfigurableProvider.models.oauth2.Oauth2ConfigurableProviderModel;
|
||||||
|
import eu.eudat.logic.security.customproviders.ConfigurableProvider.models.saml2.Saml2ConfigurableProviderModel;
|
||||||
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -21,8 +25,14 @@ public class ConfigurableProvidersModel {
|
||||||
List<ConfigurableProviderModel> providerModelList = new LinkedList<>();
|
List<ConfigurableProviderModel> providerModelList = new LinkedList<>();
|
||||||
if (entity != null) {
|
if (entity != null) {
|
||||||
for (ConfigurableProvider entityProvider : entity.getProviders()) {
|
for (ConfigurableProvider entityProvider : entity.getProviders()) {
|
||||||
if (entityProvider.getEnabled())
|
if (entityProvider.isEnabled()){
|
||||||
providerModelList.add(new ConfigurableProviderModel().fromDataModel(entityProvider));
|
if(entityProvider instanceof Oauth2ConfigurableProvider)
|
||||||
|
providerModelList.add(new Oauth2ConfigurableProviderModel().fromDataModel(entityProvider));
|
||||||
|
else if(entityProvider instanceof Saml2ConfigurableProvider)
|
||||||
|
providerModelList.add(new Saml2ConfigurableProviderModel().fromDataModel(entityProvider));
|
||||||
|
else
|
||||||
|
providerModelList.add(new ConfigurableProviderModel().fromDataModel(entityProvider));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
model.setProviders(providerModelList);
|
model.setProviders(providerModelList);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -4,12 +4,14 @@ import eu.eudat.exceptions.security.NonValidTokenException;
|
||||||
import eu.eudat.exceptions.security.NullEmailException;
|
import eu.eudat.exceptions.security.NullEmailException;
|
||||||
import eu.eudat.models.data.login.LoginInfo;
|
import eu.eudat.models.data.login.LoginInfo;
|
||||||
import eu.eudat.models.data.security.Principal;
|
import eu.eudat.models.data.security.Principal;
|
||||||
|
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
|
||||||
|
import net.shibboleth.utilities.java.support.resolver.ResolverException;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
public interface TokenValidator {
|
public interface TokenValidator {
|
||||||
|
|
||||||
Principal validateToken(LoginInfo credentials) throws NonValidTokenException, IOException, GeneralSecurityException, NullEmailException;
|
Principal validateToken(LoginInfo credentials) throws NonValidTokenException, IOException, GeneralSecurityException, NullEmailException, ResolverException, ComponentInitializationException;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@ import eu.eudat.logic.proxy.config.configloaders.ConfigLoader;
|
||||||
import eu.eudat.logic.security.customproviders.ConfigurableProvider.ConfigurableProviderCustomProvider;
|
import eu.eudat.logic.security.customproviders.ConfigurableProvider.ConfigurableProviderCustomProvider;
|
||||||
import eu.eudat.logic.security.customproviders.ConfigurableProvider.ConfigurableProviderUser;
|
import eu.eudat.logic.security.customproviders.ConfigurableProvider.ConfigurableProviderUser;
|
||||||
import eu.eudat.logic.security.customproviders.ConfigurableProvider.entities.ConfigurableProvider;
|
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.entities.saml2.Saml2ConfigurableProvider;
|
||||||
import eu.eudat.logic.security.validators.TokenValidator;
|
import eu.eudat.logic.security.validators.TokenValidator;
|
||||||
import eu.eudat.logic.security.validators.configurableProvider.helpers.ConfigurableProviderRequest;
|
import eu.eudat.logic.security.validators.configurableProvider.helpers.ConfigurableProviderRequest;
|
||||||
import eu.eudat.logic.security.validators.configurableProvider.helpers.ConfigurableProviderResponseToken;
|
import eu.eudat.logic.security.validators.configurableProvider.helpers.ConfigurableProviderResponseToken;
|
||||||
|
@ -13,14 +15,22 @@ import eu.eudat.logic.services.operations.authentication.AuthenticationService;
|
||||||
import eu.eudat.models.data.login.LoginInfo;
|
import eu.eudat.models.data.login.LoginInfo;
|
||||||
import eu.eudat.models.data.loginprovider.LoginProviderUser;
|
import eu.eudat.models.data.loginprovider.LoginProviderUser;
|
||||||
import eu.eudat.models.data.security.Principal;
|
import eu.eudat.models.data.security.Principal;
|
||||||
|
|
||||||
|
import org.opensaml.saml.saml2.core.*;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Component("configurableProviderTokenValidator")
|
@Component("configurableProviderTokenValidator")
|
||||||
public class ConfigurableProviderTokenValidator implements TokenValidator {
|
public class ConfigurableProviderTokenValidator implements TokenValidator {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ConfigurableProviderTokenValidator.class);
|
||||||
|
|
||||||
private ConfigurableProviderCustomProvider configurableProvider;
|
private ConfigurableProviderCustomProvider configurableProvider;
|
||||||
private AuthenticationService nonVerifiedUserAuthenticationService;
|
private AuthenticationService nonVerifiedUserAuthenticationService;
|
||||||
private ConfigLoader configLoader;
|
private ConfigLoader configLoader;
|
||||||
|
@ -32,7 +42,7 @@ public class ConfigurableProviderTokenValidator implements TokenValidator {
|
||||||
}
|
}
|
||||||
|
|
||||||
public ConfigurableProviderResponseToken getAccessToken(ConfigurableProviderRequest configurableProviderRequest) {
|
public ConfigurableProviderResponseToken getAccessToken(ConfigurableProviderRequest configurableProviderRequest) {
|
||||||
ConfigurableProvider provider = getConfigurableProviderFromId(configurableProviderRequest.getConfigurableLoginId());
|
Oauth2ConfigurableProvider provider = (Oauth2ConfigurableProvider)getConfigurableProviderFromId(configurableProviderRequest.getConfigurableLoginId());
|
||||||
return this.configurableProvider.getAccessToken(configurableProviderRequest.getCode(),
|
return this.configurableProvider.getAccessToken(configurableProviderRequest.getCode(),
|
||||||
provider.getRedirect_uri(), provider.getClientId(), provider.getClientSecret(),
|
provider.getRedirect_uri(), provider.getClientId(), provider.getClientSecret(),
|
||||||
provider.getAccess_token_url(), provider.getGrant_type(), provider.getToken().getAccess_token(), provider.getToken().getExpires_in());
|
provider.getAccess_token_url(), provider.getGrant_type(), provider.getToken().getAccess_token(), provider.getToken().getExpires_in());
|
||||||
|
@ -41,15 +51,83 @@ public class ConfigurableProviderTokenValidator implements TokenValidator {
|
||||||
public Principal validateToken(LoginInfo credentials) throws NullEmailException {
|
public Principal validateToken(LoginInfo credentials) throws NullEmailException {
|
||||||
String configurableLoginId = ((Map) credentials.getData()).get("configurableLoginId").toString();
|
String configurableLoginId = ((Map) credentials.getData()).get("configurableLoginId").toString();
|
||||||
ConfigurableProvider configurableProvider = getConfigurableProviderFromId(configurableLoginId);
|
ConfigurableProvider configurableProvider = getConfigurableProviderFromId(configurableLoginId);
|
||||||
ConfigurableProviderUser configurableUser = this.configurableProvider.getUser(credentials.getTicket(), configurableProvider.getUser());
|
|
||||||
LoginProviderUser user = new LoginProviderUser();
|
|
||||||
user.setId(configurableUser.getId());
|
|
||||||
user.setEmail(configurableUser.getEmail());
|
|
||||||
user.setName(configurableUser.getName());
|
|
||||||
user.setProvider(credentials.getProvider());
|
|
||||||
user.setSecret(credentials.getTicket());
|
|
||||||
|
|
||||||
return this.nonVerifiedUserAuthenticationService.Touch(user);
|
LoginProviderUser user = new LoginProviderUser();
|
||||||
|
if (configurableProvider.getType().equals("oauth2")) {
|
||||||
|
ConfigurableProviderUser configurableUser = this.configurableProvider.getUser(credentials.getTicket(), ((Oauth2ConfigurableProvider)configurableProvider).getUser());
|
||||||
|
user.setId(configurableUser.getId());
|
||||||
|
user.setEmail(configurableUser.getEmail());
|
||||||
|
user.setName(configurableUser.getName());
|
||||||
|
user.setProvider(credentials.getProvider());
|
||||||
|
user.setSecret(credentials.getTicket());
|
||||||
|
return this.nonVerifiedUserAuthenticationService.Touch(user);
|
||||||
|
}
|
||||||
|
else if (configurableProvider.getType().equals("saml2")) {
|
||||||
|
|
||||||
|
Response saml2Response = null;
|
||||||
|
try {
|
||||||
|
Saml2ConfigurableProvider saml2ConfigurableProvider = (Saml2ConfigurableProvider)configurableProvider;
|
||||||
|
saml2Response = Saml2SSOUtils.processResponse(credentials.getTicket(), saml2ConfigurableProvider.getIdpEntityId(), saml2ConfigurableProvider.getSpEntityId(),
|
||||||
|
saml2ConfigurableProvider.getIdpMetadataUrl(), saml2ConfigurableProvider.isResponseSigned(), saml2ConfigurableProvider.isAssertionSigned());
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Assertion> assertions = saml2Response.getAssertions();
|
||||||
|
if(assertions != null && !assertions.isEmpty()){
|
||||||
|
|
||||||
|
List<AttributeStatement> attributeStatements = assertions.get(0).getAttributeStatements();
|
||||||
|
if(attributeStatements != null && !attributeStatements.isEmpty()){
|
||||||
|
|
||||||
|
List<Attribute> attributes = attributeStatements.get(0).getAttributes();
|
||||||
|
if(attributes != null && !attributes.isEmpty()){
|
||||||
|
|
||||||
|
Saml2ConfigurableProvider.SAML2UsingFormat usingFormat = ((Saml2ConfigurableProvider)configurableProvider).getUsingFormat();
|
||||||
|
Map<String, String> attributeMapping = ((Saml2ConfigurableProvider)configurableProvider).getConfigurableUserFromAttributes();
|
||||||
|
Map<String, Saml2ConfigurableProvider.SAML2AttributeType> attributeType = ((Saml2ConfigurableProvider)configurableProvider).getAttributeTypes();
|
||||||
|
Map<String, Object> saml2User = new HashMap<>();
|
||||||
|
for(Attribute attribute: attributes){
|
||||||
|
|
||||||
|
String attributeName = Saml2SSOUtils.getAttributeName(attribute, usingFormat);
|
||||||
|
if(attributeName != null && attributeMapping.containsValue(attributeName)){
|
||||||
|
|
||||||
|
Saml2ConfigurableProvider.SAML2AttributeType attrType = attributeType.get(attributeName);
|
||||||
|
if(attribute.getAttributeValues() != null && !attribute.getAttributeValues().isEmpty() && attrType != null){
|
||||||
|
Object attributeValue = Saml2SSOUtils.getAttributeType(attribute.getAttributeValues().get(0), attrType);
|
||||||
|
if(attributeValue != null) {
|
||||||
|
saml2User.put(attributeName, attributeValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
String subjectNameId = assertions.get(0).getSubject().getNameID().getValue();
|
||||||
|
String userId = configurableLoginId + ": " + subjectNameId;
|
||||||
|
user.setId(userId);
|
||||||
|
} catch(NullPointerException e){
|
||||||
|
logger.error("Could not get Subject NameID value of assertion");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
user.setEmail((String)saml2User.get(attributeMapping.get("email")));
|
||||||
|
user.setName((String)saml2User.get(attributeMapping.get("name")));
|
||||||
|
user.setProvider(credentials.getProvider());
|
||||||
|
user.setSecret(credentials.getTicket());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return this.nonVerifiedUserAuthenticationService.Touch(user);
|
||||||
|
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ConfigurableProvider getConfigurableProviderFromId(String configurableId) {
|
private ConfigurableProvider getConfigurableProviderFromId(String configurableId) {
|
||||||
|
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -2,7 +2,8 @@
|
||||||
"providers": [
|
"providers": [
|
||||||
{
|
{
|
||||||
"enabled": false,
|
"enabled": false,
|
||||||
"configurableLoginId": "myId",
|
"type": "oauth2",
|
||||||
|
"configurableLoginId": "oauth2-localhost",
|
||||||
"name": "myApp",
|
"name": "myApp",
|
||||||
"clientId": "",
|
"clientId": "",
|
||||||
"clientSecret": "",
|
"clientSecret": "",
|
||||||
|
@ -21,7 +22,32 @@
|
||||||
},
|
},
|
||||||
"oauthUrl": "/authorize",
|
"oauthUrl": "/authorize",
|
||||||
"scope": "email",
|
"scope": "email",
|
||||||
"state": "123562"
|
"state": "123562",
|
||||||
|
"logoUrl": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"type": "saml2",
|
||||||
|
"configurableLoginId": "keycloak-saml2",
|
||||||
|
"name": "keycloak",
|
||||||
|
"spEntityId": "argos",
|
||||||
|
"idpEntityId": "http://localhost:8080/auth/realms/master",
|
||||||
|
"idpUrl": "http://localhost:8080/auth/realms/master/protocol/saml",
|
||||||
|
"idpMetadataUrl": "http://localhost:8080/auth/realms/master/protocol/saml/descriptor",
|
||||||
|
"responseSigned": false,
|
||||||
|
"signedResponseCredential": null,
|
||||||
|
"assertionSigned": true,
|
||||||
|
"usingFormat": "friendly_name",
|
||||||
|
"configurableUserFromAttributes": {
|
||||||
|
"email": "email",
|
||||||
|
"name": "givenName"
|
||||||
|
},
|
||||||
|
"attributeTypes": {
|
||||||
|
"email": "XSString",
|
||||||
|
"givenName": "XSString"
|
||||||
|
},
|
||||||
|
"binding": "Redirect",
|
||||||
|
"logoUrl": "https://upload.wikimedia.org/wikipedia/commons/2/29/Keycloak_Logo.png"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
"ngx-dropzone": "^3.0.0",
|
"ngx-dropzone": "^3.0.0",
|
||||||
"ngx-guided-tour": "^1.1.11",
|
"ngx-guided-tour": "^1.1.11",
|
||||||
"ngx-matomo": "^0.1.4",
|
"ngx-matomo": "^0.1.4",
|
||||||
|
"pako": "^1.0.11",
|
||||||
"rxjs": "^6.3.2",
|
"rxjs": "^6.3.2",
|
||||||
"tinymce": "^5.9.2",
|
"tinymce": "^5.9.2",
|
||||||
"tslib": "^2.0.0",
|
"tslib": "^2.0.0",
|
||||||
|
@ -47,12 +48,12 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-devkit/build-angular": "~12.2.7",
|
"@angular-devkit/build-angular": "~12.2.7",
|
||||||
"@angular/cdk": "^12.2.7",
|
"@angular/cdk": "^12.2.7",
|
||||||
"@angular/material": "^12.2.7",
|
|
||||||
"@angular/cli": "12.2.7",
|
"@angular/cli": "12.2.7",
|
||||||
"@angular/compiler-cli": "^12.2.7",
|
"@angular/compiler-cli": "^12.2.7",
|
||||||
|
"@angular/language-service": "^12.2.7",
|
||||||
|
"@angular/material": "^12.2.7",
|
||||||
"@angular/platform-browser-dynamic": "^12.2.7",
|
"@angular/platform-browser-dynamic": "^12.2.7",
|
||||||
"@angular/router": "^12.2.7",
|
"@angular/router": "^12.2.7",
|
||||||
"@angular/language-service": "^12.2.7",
|
|
||||||
"@types/facebook-js-sdk": "^3.3.5",
|
"@types/facebook-js-sdk": "^3.3.5",
|
||||||
"@types/file-saver": "^2.0.3",
|
"@types/file-saver": "^2.0.3",
|
||||||
"@types/gapi": "^0.0.41",
|
"@types/gapi": "^0.0.41",
|
||||||
|
@ -61,6 +62,7 @@
|
||||||
"@types/jasminewd2": "~2.0.10",
|
"@types/jasminewd2": "~2.0.10",
|
||||||
"@types/moment-timezone": "^0.5.13",
|
"@types/moment-timezone": "^0.5.13",
|
||||||
"@types/node": "^12.11.1",
|
"@types/node": "^12.11.1",
|
||||||
|
"@types/pako": "^1.0.3",
|
||||||
"codelyzer": "^6.0.2",
|
"codelyzer": "^6.0.2",
|
||||||
"ts-node": "~10.2.1",
|
"ts-node": "~10.2.1",
|
||||||
"tslint": "~6.1.0",
|
"tslint": "~6.1.0",
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
export enum ConfigurableProviderType {
|
||||||
|
Oauth2 = "oauth2",
|
||||||
|
Saml2 = "saml2"
|
||||||
|
}
|
|
@ -1,9 +1,6 @@
|
||||||
export class ConfigurableProvider {
|
export class ConfigurableProvider {
|
||||||
configurableLoginId: string;
|
configurableLoginId: string;
|
||||||
|
type: string;
|
||||||
name: string;
|
name: string;
|
||||||
clientId: string;
|
logoUrl: string;
|
||||||
redirect_uri: string;
|
|
||||||
oauthUrl: string;
|
|
||||||
scope: string;
|
|
||||||
state: 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);
|
||||||
|
}
|
||||||
|
const base64String = btoa(pk.deflateRaw(uint, { to: 'string' }));
|
||||||
|
const relayState = this.buildRelayState(spEntityID, configurableLoginId);
|
||||||
|
const url = idpUrl + '?RelayState=' + relayState + '&SAMLRequest=' + encodeURIComponent(base64String);
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -10,6 +10,10 @@ import { BaseComponent } from '@common/base/base.component';
|
||||||
import { environment } from 'environments/environment';
|
import { environment } from 'environments/environment';
|
||||||
import { takeUntil } from 'rxjs/operators';
|
import { takeUntil } from 'rxjs/operators';
|
||||||
import { ConfigurationService } from '@app/core/services/configuration/configuration.service';
|
import { ConfigurationService } from '@app/core/services/configuration/configuration.service';
|
||||||
|
import { SamlLoginService } from '@app/core/services/saml-login.service';
|
||||||
|
import { Oauth2ConfigurableProvider } from '@app/core/model/configurable-provider/oauth2ConfigurableProvider';
|
||||||
|
import { Saml2ConfigurableProvider } from '@app/core/model/configurable-provider/saml2ConfigurableProvider';
|
||||||
|
import { ConfigurableProviderType } from '@app/core/common/enum/configurable-provider-type';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-configurable-login',
|
selector: 'app-configurable-login',
|
||||||
|
@ -35,7 +39,8 @@ export class ConfigurableLoginComponent extends BaseComponent implements OnInit
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private httpClient: HttpClient,
|
private httpClient: HttpClient,
|
||||||
private providers: ConfigurableProvidersService,
|
private providers: ConfigurableProvidersService,
|
||||||
private configurationService: ConfigurationService
|
private configurationService: ConfigurationService,
|
||||||
|
private samlLoginService: SamlLoginService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
@ -70,16 +75,23 @@ export class ConfigurableLoginComponent extends BaseComponent implements OnInit
|
||||||
}
|
}
|
||||||
|
|
||||||
public configurableAuthorize() {
|
public configurableAuthorize() {
|
||||||
let authUrl = this.provider.oauthUrl
|
if(this.provider.type === ConfigurableProviderType.Oauth2){
|
||||||
+ '?response_type=code&client_id=' + this.provider.clientId
|
let provider = this.provider as Oauth2ConfigurableProvider;
|
||||||
+ '&redirect_uri=' + this.provider.redirect_uri
|
let authUrl = provider.oauthUrl
|
||||||
+ '&scope=' + this.provider.scope;
|
+ '?response_type=code&client_id=' + provider.clientId
|
||||||
if (this.provider.state.length > 0) authUrl = authUrl + '&state=' + this.provider.state
|
+ '&redirect_uri=' + provider.redirect_uri
|
||||||
window.location.href = authUrl;
|
+ '&scope=' + provider.scope;
|
||||||
|
if (provider.state.length > 0) authUrl = authUrl + '&state=' + provider.state
|
||||||
|
window.location.href = authUrl;
|
||||||
|
}
|
||||||
|
else if(this.provider.type === ConfigurableProviderType.Saml2){
|
||||||
|
let provider = this.provider as Saml2ConfigurableProvider;
|
||||||
|
window.location.href = this.samlLoginService.getSamlLoginUrl(provider.spEntityId, provider.idpUrl, provider.binding, provider.configurableLoginId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public configurableLoginUser(code: string, state: string) {
|
public configurableLoginUser(code: string, state: string) {
|
||||||
if (state !== this.provider.state) {
|
if (state !== (<Oauth2ConfigurableProvider>this.provider).state) {
|
||||||
this.router.navigate(['/login'])
|
this.router.navigate(['/login'])
|
||||||
}
|
}
|
||||||
this.httpClient.post(this.configurationService.server + 'auth/configurableProviderRequestToken', { code: code, provider: AuthProvider.Configurable, configurableLoginId: this.providerId })
|
this.httpClient.post(this.configurationService.server + 'auth/configurableProviderRequestToken', { code: code, provider: AuthProvider.Configurable, configurableLoginId: this.providerId })
|
||||||
|
|
|
@ -50,17 +50,24 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center pt-4">
|
||||||
<div *ngIf="hasConfigurableProviders()" class="row pt-2 mb-4 accesss-methods">
|
<ng-template [ngIf]="hasConfigurableProviders()">
|
||||||
<div *ngFor="let provider of this.configurableProviderService.providers"
|
<div *ngFor="let provider of this.configurableProviderService.providers; index as i" class="flex-column">
|
||||||
class="col-auto configurable-logo">
|
<div class="col-auto"
|
||||||
<button mat-icon-button class="configurable-button" (click)="configurableLogin(provider)"
|
[ngClass]="{'pr-4': (i % this.configurableProviderService.providers.length) == 0 || (i % this.configurableProviderService.providers.length) == 1 ||
|
||||||
class="login-social-button">
|
this.configurableProviderService.providers.length == 1 || this.configurableProviderService.providers.length == 2,
|
||||||
<span class="configurableIcon">{{provider.name}}</span>
|
'pl-4': (i % this.configurableProviderService.providers.length) == 2 || (i % this.configurableProviderService.providers.length) == 1 ||
|
||||||
</button>
|
this.configurableProviderService.providers.length == 1 || this.configurableProviderService.providers.length == 2}">
|
||||||
|
<button mat-icon-button class="iconmediumButton d-flex justify-content-center align-items-center" (click)="configurableLogin(provider)">
|
||||||
|
<span *ngIf="provider.logoUrl; else elseBlock" class="configurableIcon" style="background: url({{provider.logoUrl}}) no-repeat; background-size: cover;"></span>
|
||||||
|
<ng-template #elseBlock>
|
||||||
|
<span class="configurableIcon">{{provider.name}}</span>
|
||||||
|
</ng-template>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</ng-template>
|
||||||
<div *ngIf="hasZenodoOauth()" class="col-auto mt-4">
|
<div *ngIf="hasZenodoOauth()" class="col-auto">
|
||||||
<button mat-icon-button (click)="zenodoLogin()" class="d-flex justify-content-center">
|
<button mat-icon-button (click)="zenodoLogin()" class="d-flex justify-content-center">
|
||||||
<span class="zenodoIcon"></span>
|
<span class="zenodoIcon"></span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -294,8 +294,9 @@ span.iconmedium {
|
||||||
|
|
||||||
span.configurableIcon {
|
span.configurableIcon {
|
||||||
float: right;
|
float: right;
|
||||||
width: 80px;
|
transform: scale(0.85);
|
||||||
height: 56px;
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
span.zenodoIcon {
|
span.zenodoIcon {
|
||||||
|
|
|
@ -16,6 +16,8 @@ import { CommonUiModule } from '@common/ui/common-ui.module';
|
||||||
import { MergeEmailConfirmation } from './merge-email-confirmation/merge-email-confirmation.component';
|
import { MergeEmailConfirmation } from './merge-email-confirmation/merge-email-confirmation.component';
|
||||||
import { MergeLoginService } from './utilities/merge-login.service';
|
import { MergeLoginService } from './utilities/merge-login.service';
|
||||||
import { ZenodoLoginComponent } from './zenodo-login/zenodo-login.component';
|
import { ZenodoLoginComponent } from './zenodo-login/zenodo-login.component';
|
||||||
|
import { SamlLoginService } from '@app/core/services/saml-login.service';
|
||||||
|
import { SamlResponseLoginComponent } from './saml/saml-login-response/saml-login-response.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -34,11 +36,12 @@ import { ZenodoLoginComponent } from './zenodo-login/zenodo-login.component';
|
||||||
OpenAireLoginComponent,
|
OpenAireLoginComponent,
|
||||||
ConfigurableLoginComponent,
|
ConfigurableLoginComponent,
|
||||||
ZenodoLoginComponent,
|
ZenodoLoginComponent,
|
||||||
MergeEmailConfirmation
|
MergeEmailConfirmation,
|
||||||
|
SamlResponseLoginComponent
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
LoginComponent
|
LoginComponent
|
||||||
],
|
],
|
||||||
providers: [LoginService, MergeLoginService, ConfigurableProvidersService]
|
providers: [LoginService, MergeLoginService, ConfigurableProvidersService, SamlLoginService]
|
||||||
})
|
})
|
||||||
export class LoginModule { }
|
export class LoginModule { }
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { ConfigurableLoginComponent } from "./configurable-login/configurable-lo
|
||||||
import { ZenodoLoginComponent } from './zenodo-login/zenodo-login.component';
|
import { ZenodoLoginComponent } from './zenodo-login/zenodo-login.component';
|
||||||
import { Oauth2DialogComponent } from '@app/ui/misc/oauth2-dialog/oauth2-dialog.component';
|
import { Oauth2DialogComponent } from '@app/ui/misc/oauth2-dialog/oauth2-dialog.component';
|
||||||
import { MergeEmailConfirmation } from './merge-email-confirmation/merge-email-confirmation.component';
|
import { MergeEmailConfirmation } from './merge-email-confirmation/merge-email-confirmation.component';
|
||||||
|
import { SamlResponseLoginComponent } from './saml/saml-login-response/saml-login-response.component';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{ path: '', component: LoginComponent },
|
{ path: '', component: LoginComponent },
|
||||||
|
@ -23,7 +24,8 @@ const routes: Routes = [
|
||||||
{ path: 'confirmation', component: EmailConfirmation },
|
{ path: 'confirmation', component: EmailConfirmation },
|
||||||
{ path: 'openaire', component: Oauth2DialogComponent},
|
{ path: 'openaire', component: Oauth2DialogComponent},
|
||||||
{ path: 'configurable/:id', component: ConfigurableLoginComponent},
|
{ path: 'configurable/:id', component: ConfigurableLoginComponent},
|
||||||
{ path: 'external/zenodo', component: Oauth2DialogComponent }
|
{ path: 'external/zenodo', component: Oauth2DialogComponent },
|
||||||
|
{ path: 'external/saml', component: SamlResponseLoginComponent }
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { Component, NgZone, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { BaseComponent } from '@common/base/base.component';
|
||||||
|
import { LoggingService } from '@app/core/services/logging/logging-service';
|
||||||
|
import { SamlLoginService } from '@app/core/services/saml-login.service';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { takeUntil } from 'rxjs/operators';
|
||||||
|
import { AuthService } from '@app/core/services/auth/auth.service';
|
||||||
|
import { AuthProvider } from '@app/core/common/enum/auth-provider';
|
||||||
|
import { SnackBarNotificationLevel, UiNotificationService } from '@app/core/services/notification/ui-notification-service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: ''
|
||||||
|
})
|
||||||
|
export class SamlResponseLoginComponent extends BaseComponent implements OnInit {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
private samlLoginService: SamlLoginService,
|
||||||
|
private router: Router,
|
||||||
|
private uiNotificationService: UiNotificationService,
|
||||||
|
private loggingService: LoggingService,
|
||||||
|
private zone: NgZone,
|
||||||
|
private language: TranslateService,
|
||||||
|
private authService: AuthService,
|
||||||
|
) { super(); }
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.route.queryParams
|
||||||
|
.pipe(takeUntil(this._destroyed))
|
||||||
|
.subscribe(routeParams => {
|
||||||
|
let samlResponse = null;
|
||||||
|
if (routeParams.SAMLart) {
|
||||||
|
samlResponse = routeParams.SAMLart;
|
||||||
|
} else if (routeParams.SAMLResponse) {
|
||||||
|
samlResponse = routeParams.SAMLResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (samlResponse == null) return;
|
||||||
|
|
||||||
|
const spId = this.samlLoginService.resolveSpId(routeParams.RelayState);
|
||||||
|
const configurableLoginId = this.samlLoginService.resolveConfigurableLoginId(routeParams.RelayState);
|
||||||
|
|
||||||
|
this.authService.login({ ticket: samlResponse, provider: AuthProvider.Configurable, data: { configurableLoginId: configurableLoginId } })
|
||||||
|
.pipe(takeUntil(this._destroyed))
|
||||||
|
.subscribe((result) => this.onAuthenticateSuccess(), (error) => this.onAuthenticateError(error));
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onAuthenticateSuccess(): void {
|
||||||
|
this.loggingService.info('Successful Login');
|
||||||
|
this.uiNotificationService.snackBarNotification(this.language.instant('GENERAL.SNACK-BAR.SUCCESSFUL-LOGIN'), SnackBarNotificationLevel.Success);
|
||||||
|
this.zone.run(() => this.router.navigate(['/']));
|
||||||
|
}
|
||||||
|
|
||||||
|
onAuthenticateError(errorResponse: HttpErrorResponse) {
|
||||||
|
this.uiNotificationService.snackBarNotification(errorResponse.error.message, SnackBarNotificationLevel.Warning);
|
||||||
|
this.zone.run(() => this.router.navigate(['/']));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue