feat: 🎸 Make SAML identifier attribute configurable

Attribute is configured via property _saml.user.attrIdentifier_ and has
to be one of values eppn|epuid|eptid|uid|uniqueIdentifier
pull/1580/head
Dominik Frantisek Bucik 2021-11-10 20:23:17 +01:00
parent cf3c19c046
commit 39498573c3
No known key found for this signature in database
GPG Key ID: 25014C8DB2E7E62D
7 changed files with 56 additions and 10 deletions

View File

@ -133,6 +133,7 @@
<prop key="saml.proxy.spEntityId">https://login.cesnet.cz/proxy/</prop>
<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">

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

@ -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;