Merge pull request #34 from dBucik/saml

Saml
pull/1580/head
Dominik František Bučík 2021-11-11 14:21:11 +01:00 committed by GitHub
commit e058cbbf7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 89 additions and 38 deletions

View File

@ -126,13 +126,14 @@
<prop key="saml.keystore.password">pass</prop>
<prop key="saml.keystore.defaultKey">pass</prop>
<prop key="saml.keystore.defaultKeyPass">pass</prop>
<prop key="saml.idp.defaultIdpEntityId">https://login.cesnet.cz/idp/</prop>
<prop key="saml.idp.defaultIdpEntityId"/>
<prop key="saml.idp.metadataLocation"/> <!-- i.e. /etc/perun/login-cesnet-metadata.xml -->
<prop key="saml.idp.metadataUrl"/> <!-- i.e. https://login.cesnet.cz/proxy/module.php/metadata -->
<prop key="saml.proxy.enabled">true</prop>
<prop key="saml.proxy.spEntityId">https://login.cesnet.cz/proxy/</prop>
<prop key="saml.proxy.spEntityId"/>
<prop key="saml.internalReferrers"/> <!-- comma separated list of URLs (which are matched as prefixes) -->
<prop key="saml.acrs.reserverdPrefixes">urn:cesnet:</prop>
<prop key="saml.acrs.enableComparison">false</prop>
<prop key="saml.user.attrIdentifier">eppn</prop><!-- eppn|epuid|eptid|uid|uniqueIdentifier -->
<!-- STATS JDBC -->
<prop key="stats.jdbc.url">jdbc:mariadb://localhost:3306/STATS</prop>
<prop key="stats.jdbc.user">user</prop>
@ -197,6 +198,7 @@
<property name="idpMetadataFile" value="${saml.idp.metadataLocation}"/>
<property name="idpMetadataUrl" value="${saml.idp.metadataUrl}"/>
<property name="acrReservedPrefixes" value="#{'${saml.acrs.reserverdPrefixes}'.split('\s*,\s*')}"/>
<property name="userIdentifierAttribute" value="${saml.user.attrIdentifier}"/>
</bean>
<bean id="nonOverwrittenAttributeProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
@ -485,8 +487,8 @@
<constructor-arg name="pattern" value="/authorize**"/>
<constructor-arg name="oidcIssuer" value="${main.oidc.issuer.url}"/>
<constructor-arg name="idpEntityId" value="${saml.idp.defaultIdpEntityId}"/>
<constructor-arg name="proxyEnabled" value="${saml.proxy.enabled}"/>
<constructor-arg name="proxySpEntityId" value="${saml.proxy.spEntityId}"/>
<constructor-arg name="internalReferrers" value="#{'${saml.internalReferrers}'.split('\s*,\s*')}"/>
<constructor-arg name="contextLogoutHandler" ref="logoutHandler"/>
</bean>
<bean id="samlDiscovery" class="org.springframework.security.saml.SAMLDiscovery">

View File

@ -15,16 +15,18 @@ public class PerunSamlUserDetailsService implements SAMLUserDetailsService {
private static final Logger log = LoggerFactory.getLogger(PerunSamlUserDetailsService.class);
private final PerunAdapter perunAdapter;
private final SamlProperties samlProperties;
@Autowired
public PerunSamlUserDetailsService(PerunAdapter perunAdapter) {
public PerunSamlUserDetailsService(PerunAdapter perunAdapter, SamlProperties samlProperties) {
this.perunAdapter = perunAdapter;
this.samlProperties = samlProperties;
}
@Override
public Object loadUserBySAML(SAMLCredential credential) throws UsernameNotFoundException {
log.debug("Loading user for SAML credential");
PerunPrincipal p = FiltersUtils.getPerunPrincipal(credential);
PerunPrincipal p = FiltersUtils.getPerunPrincipal(credential, samlProperties.getUserIdentifierAttribute());
log.debug("Fetching user from perun ({})", p);
return perunAdapter.getPreauthenticatedUserId(p);
}

View File

@ -3,6 +3,10 @@ package cz.muni.ics.oidc.saml;
import static org.springframework.http.HttpHeaders.REFERER;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
@ -21,25 +25,34 @@ public class SamlInvalidateSessionFilter extends GenericFilterBean {
private static final Logger log = LoggerFactory.getLogger(SamlInvalidateSessionFilter.class);
private final AntPathRequestMatcher matcher;
private final String idpEntityId;
private final String proxySpEntityId;
private final boolean proxyEnabled;
private final String oidcIssuer;
private final SecurityContextLogoutHandler contextLogoutHandler;
private final List<String> internalReferrers = new ArrayList<>();
public SamlInvalidateSessionFilter(String pattern,
String idpEntityId,
String oidcIssuer,
boolean proxyEnabled,
String proxySpEntityId,
SecurityContextLogoutHandler contextLogoutHandler)
SecurityContextLogoutHandler contextLogoutHandler,
String[] internalReferrers)
{
this.matcher = new AntPathRequestMatcher(pattern);
this.idpEntityId = idpEntityId;
this.oidcIssuer = oidcIssuer;
this.proxyEnabled = proxyEnabled;
this.proxySpEntityId = proxySpEntityId;
if (StringUtils.hasText(idpEntityId)) {
this.internalReferrers.add(idpEntityId);
}
if (StringUtils.hasText(oidcIssuer)) {
this.internalReferrers.add(oidcIssuer);
}
if (StringUtils.hasText(proxySpEntityId)) {
this.internalReferrers.add(proxySpEntityId);
}
this.contextLogoutHandler = contextLogoutHandler;
if (internalReferrers != null && internalReferrers.length > 0) {
List<String> referrers = Arrays.asList(internalReferrers);
referrers = referrers.stream().filter(StringUtils::hasText).collect(Collectors.toList());
if (!referrers.isEmpty()) {
this.internalReferrers.addAll(referrers);
}
}
}
@Override
@ -59,23 +72,15 @@ public class SamlInvalidateSessionFilter extends GenericFilterBean {
}
private boolean isInternalReferer(String referer) {
if (!StringUtils.hasText(referer)) {
// no referer, consider as internal
if (!StringUtils.hasText(referer)) { // no referer, consider as internal
return true;
}
boolean isInternal = referer.startsWith(oidcIssuer);
if (!isInternal) {
if (proxyEnabled) {
// check if referer is PROXY (SP part)
isInternal = referer.startsWith(proxySpEntityId);
} else {
// check if referer is IDP
isInternal = referer.startsWith(idpEntityId);
for (String internal : internalReferrers) {
if (referer.startsWith(internal)) {
return true;
}
}
log.debug("Referer {} is internal: {}", referer, isInternal);
return isInternal;
return false;
}
}

View File

@ -23,6 +23,7 @@ public class SamlProperties implements InitializingBean {
private String idpMetadataFile;
private String idpMetadataUrl;
private String[] acrReservedPrefixes;
private String userIdentifierAttribute;
public String getEntityID() {
return entityID;
@ -125,4 +126,12 @@ public class SamlProperties implements InitializingBean {
this.acrReservedPrefixes = nonNull.toArray(new String[0]);
}
}
public String getUserIdentifierAttribute() {
return userIdentifierAttribute;
}
public void setUserIdentifierAttribute(String userIdentifierAttribute) {
this.userIdentifierAttribute = userIdentifierAttribute;
}
}

View File

@ -3,6 +3,7 @@ package cz.muni.ics.oidc.server.filters;
import cz.muni.ics.oidc.BeanUtil;
import cz.muni.ics.oidc.models.Facility;
import cz.muni.ics.oidc.models.PerunUser;
import cz.muni.ics.oidc.saml.SamlProperties;
import cz.muni.ics.oidc.server.adapters.PerunAdapter;
import java.io.IOException;
import java.util.List;
@ -47,6 +48,9 @@ public class CallPerunFiltersFilter extends GenericFilterBean {
@Autowired
private PerunAdapter perunAdapter;
@Autowired
private SamlProperties samlProperties;
private PerunFiltersContext perunFiltersContext;
@PostConstruct
@ -72,7 +76,7 @@ public class CallPerunFiltersFilter extends GenericFilterBean {
CallPerunFiltersFilter.class.getSimpleName(), client.getClientId(), e);
}
}
PerunUser user = FiltersUtils.getPerunUser(request, perunAdapter);
PerunUser user = FiltersUtils.getPerunUser(request, perunAdapter, samlProperties.getUserIdentifierAttribute());
FilterParams params = new FilterParams(client, facility, user);
for (PerunRequestFilter filter : filters) {
if (!filter.doFilter(servletRequest, servletResponse, params)) {

View File

@ -2,7 +2,6 @@ package cz.muni.ics.oidc.server.filters;
import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.PARAM_FORCE_AUTHN;
import static cz.muni.ics.oidc.server.filters.PerunFilterConstants.SAML_EPUID;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;
import com.google.common.base.Strings;
import cz.muni.ics.oidc.models.Facility;
@ -31,6 +30,7 @@ import org.springframework.security.providers.ExpiringUsernameAuthenticationToke
import org.springframework.security.saml.SAMLCredential;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.StringUtils;
/**
* Utility class for filters. Contains common methods used by most of filter classes.
@ -102,12 +102,12 @@ public class FiltersUtils {
* @param perunAdapter Adapter of Perun interface
* @return Found PerunUser
*/
public static PerunUser getPerunUser(HttpServletRequest request, PerunAdapter perunAdapter) {
public static PerunUser getPerunUser(HttpServletRequest request, PerunAdapter perunAdapter, String samlIdAttribute) {
SAMLCredential samlCredential = getSamlCredential(request);
if (samlCredential == null) {
return null;
}
PerunPrincipal principal = getPerunPrincipal(samlCredential);
PerunPrincipal principal = getPerunPrincipal(samlCredential, samlIdAttribute);
log.debug("fetching Perun user with extLogin '{}' and extSourceName '{}'",
principal.getExtLogin(), principal.getExtSourceName());
return perunAdapter.getPreauthenticatedUserId(principal);
@ -121,8 +121,17 @@ public class FiltersUtils {
return (SAMLCredential) p.getCredentials();
}
public static PerunPrincipal getPerunPrincipal(SAMLCredential credential) {
String extLogin = credential.getAttributeAsString(SAML_EPUID);
public static PerunPrincipal getPerunPrincipal(SAMLCredential credential, String idAttribute) {
if (credential == null) {
throw new IllegalArgumentException("No SAML credential passed");
} else if (!StringUtils.hasText(idAttribute)) {
throw new IllegalArgumentException("No identifier from SAML configured");
}
String identifierAttrOid = PerunFilterConstants.SAML_IDS.getOrDefault(idAttribute, null);
if (identifierAttrOid == null) {
throw new IllegalStateException("SAML credentials has no value for attribute: " + idAttribute);
}
String extLogin = credential.getAttributeAsString(identifierAttrOid);
String extSourceName = credential.getRemoteEntityID();
return new PerunPrincipal(extLogin, extSourceName);
}
@ -136,7 +145,7 @@ public class FiltersUtils {
public static PerunPrincipal extractPerunPrincipal(HttpServletRequest req, String proxyExtSourceName) {
String extLogin = null;
String remoteUser = req.getRemoteUser();
if (isNotEmpty(remoteUser)) {
if (StringUtils.hasText(remoteUser)) {
extLogin = remoteUser;
} else if (req.getUserPrincipal() != null) {
extLogin = ((User)req.getUserPrincipal()).getUsername();

View File

@ -1,5 +1,8 @@
package cz.muni.ics.oidc.server.filters;
import java.util.HashMap;
import java.util.Map;
/**
* Class containing common constants used by Perun request filters.
*
@ -33,8 +36,22 @@ public class PerunFilterConstants {
public static final String EFILTER_PREFIX = "urn:cesnet:proxyidp:efilter:";
public static final String SAML_EPUID = "urn:oid:1.3.6.1.4.1.5923.1.1.1.13";
public static final String SAML_EPPN = "urn:oid:1.3.6.1.4.1.5923.1.1.1.6";
public static final String SAML_EPTID = "urn:oid:1.3.6.1.4.1.5923.1.1.1.10";
public static final String SAML_UID = "urn:oid:0.9.2342.19200300.100.1.1";
public static final String SAML_UNIQUE_IDENTIFIER = "urn:oid:0.9.2342.19200300.100.1.44";
public static final String REFEDS_MFA = "https://refeds.org/profile/mfa";
public static final String PROMPT_LOGIN = "login";
public static final String PROMPT_SELECT_ACCOUNT = "select_account";
public static final Map<String, String> SAML_IDS = new HashMap<>();
static {
SAML_IDS.put("eppn", SAML_EPPN);
SAML_IDS.put("epuid", SAML_EPUID);
SAML_IDS.put("eptid", SAML_EPTID);
SAML_IDS.put("uid", SAML_UID);
SAML_IDS.put("uniqueIdentifier", SAML_UNIQUE_IDENTIFIER);
}
}

View File

@ -9,6 +9,7 @@ import cz.muni.ics.oidc.models.Facility;
import cz.muni.ics.oidc.models.PerunAttribute;
import cz.muni.ics.oidc.models.PerunAttributeValue;
import cz.muni.ics.oidc.models.PerunUser;
import cz.muni.ics.oidc.saml.SamlProperties;
import cz.muni.ics.oidc.server.adapters.PerunAdapter;
import cz.muni.ics.oidc.server.configurations.PerunOidcConfig;
import cz.muni.ics.oidc.server.filters.FilterParams;
@ -74,6 +75,7 @@ public class PerunForceAupFilter extends PerunRequestFilter {
private final PerunAdapter perunAdapter;
private final PerunOidcConfig perunOidcConfig;
private final SamlProperties samlProperties;
private final String filterName;
public PerunForceAupFilter(PerunRequestFilterParams params) {
@ -81,6 +83,7 @@ public class PerunForceAupFilter extends PerunRequestFilter {
BeanUtil beanUtil = params.getBeanUtil();
this.perunAdapter = beanUtil.getBean(PerunAdapter.class);
this.perunOidcConfig = beanUtil.getBean(PerunOidcConfig.class);
this.samlProperties = beanUtil.getBean(SamlProperties.class);
this.perunOrgAupsAttrName = params.getProperty(ORG_AUPS_ATTR_NAME);
this.perunUserAupsAttrName = params.getProperty(USER_AUPS_ATTR_NAME);
@ -102,7 +105,7 @@ public class PerunForceAupFilter extends PerunRequestFilter {
return true;
}
PerunUser user = FiltersUtils.getPerunUser(request, perunAdapter);
PerunUser user = FiltersUtils.getPerunUser(request, perunAdapter, samlProperties.getUserIdentifierAttribute());
if (user == null || user.getId() == null) {
log.debug("{} - skip filter execution: no user provider", filterName);
return true;