Merge pull request #164 from dBucik/user_lookup

feat: 🎸 More user lookup methods
pull/1580/head
Dominik František Bučík 2022-04-04 08:43:06 +02:00 committed by GitHub
commit d0954ada62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 146 additions and 107 deletions

View File

@ -76,7 +76,9 @@
<prop key="saml.acrs.reserverdPrefixes">urn:cesnet:</prop>
<prop key="saml.acrs.enableComparison">false</prop>
<prop key="saml.acrs.onlyreserved.append">urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</prop>
<prop key="saml.user.attrIdentifier">eppn</prop><!-- eppn|epuid|eptid|uid|uniqueIdentifier -->
<prop key="saml.user.attrIdentifier">eppn</prop><!-- eppn|epuid|eptid|uid|uniqueIdentifier|perunUserId -->
<prop key="saml.user.lookup">original_auth</prop><!-- original_auth|perun_user_id|static_ext_source -->
<prop key="saml.static_ext_idp"/>
<!-- STATS JDBC -->
<prop key="stats.jdbc.url">jdbc:mariadb://localhost:3306/STATS</prop>
<prop key="stats.jdbc.user">user</prop>
@ -146,6 +148,8 @@
<property name="acrReservedPrefixes" value="#{'${saml.acrs.reserverdPrefixes}'.split('\s*,\s*')}"/>
<property name="acrsToBeAdded" value="#{'${saml.acrs.onlyreserved.append}'.split('\s*,\s*')}"/>
<property name="userIdentifierAttribute" value="${saml.user.attrIdentifier}"/>
<property name="userLookupMode" value="${saml.user.lookup}"/>
<property name="staticUserExtSource" value="${saml.static_ext_idp}"/>
</bean>
<bean id="nonOverwrittenAttributeProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">

View File

@ -23,7 +23,7 @@ public class PerunSamlUserDetailsService implements SAMLUserDetailsService {
@Override
public Object loadUserBySAML(SAMLCredential credential) throws UsernameNotFoundException {
log.debug("Loading user for SAML credential");
return FiltersUtils.getPerunUser(credential, perunAdapter, samlProperties.getUserIdentifierAttribute());
return FiltersUtils.getPerunUser(credential, perunAdapter, samlProperties);
}
}

View File

@ -1,17 +1,30 @@
package cz.muni.ics.oidc.saml;
import cz.muni.ics.oidc.exceptions.ConfigurationException;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.StringUtils;
@Slf4j
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class SamlProperties implements InitializingBean {
public static final String LOOKUP_ORIGINAL_AUTH = "original_auth";
public static final String LOOKUP_PERUN_USER_ID = "perun_user_id";
public static final String LOOKUP_STATIC_EXT_SOURCE = "static_ext_source";
private String entityID;
private String keystoreLocation;
private String keystorePassword;
@ -23,70 +36,8 @@ public class SamlProperties implements InitializingBean {
private String[] acrReservedPrefixes;
private String[] acrsToBeAdded;
private String userIdentifierAttribute;
public String getEntityID() {
return entityID;
}
public void setEntityID(String entityID) {
this.entityID = entityID;
}
public String getKeystoreLocation() {
return keystoreLocation;
}
public void setKeystoreLocation(String keystoreLocation) {
this.keystoreLocation = keystoreLocation;
}
public String getKeystorePassword() {
return keystorePassword;
}
public void setKeystorePassword(String keystorePassword) {
this.keystorePassword = keystorePassword;
}
public String getKeystoreDefaultKey() {
return keystoreDefaultKey;
}
public void setKeystoreDefaultKey(String keystoreDefaultKey) {
this.keystoreDefaultKey = keystoreDefaultKey;
}
public String getKeystoreDefaultKeyPassword() {
return keystoreDefaultKeyPassword;
}
public void setKeystoreDefaultKeyPassword(String keystoreDefaultKeyPassword) {
this.keystoreDefaultKeyPassword = keystoreDefaultKeyPassword;
}
public String getDefaultIdpEntityId() {
return defaultIdpEntityId;
}
public void setDefaultIdpEntityId(String defaultIdpEntityId) {
this.defaultIdpEntityId = defaultIdpEntityId;
}
public String getIdpMetadataFile() {
return idpMetadataFile;
}
public void setIdpMetadataFile(String idpMetadataFile) {
this.idpMetadataFile = idpMetadataFile;
}
public String getIdpMetadataUrl() {
return idpMetadataUrl;
}
public void setIdpMetadataUrl(String idpMetadataUrl) {
this.idpMetadataUrl = idpMetadataUrl;
}
private String userLookupMode;
private String staticUserExtSource;
@Override
public void afterPropertiesSet() throws Exception {
@ -105,10 +56,29 @@ public class SamlProperties implements InitializingBean {
if (!f.exists()) {
throw new IllegalStateException("File '" + idpMetadataFile + "' does not exist");
}
}
public String[] getAcrReservedPrefixes() {
return acrReservedPrefixes;
if (!StringUtils.hasText(userIdentifierAttribute)) {
throw new ConfigurationException("No user identifier attribute has been configured");
}
switch (userLookupMode) {
case LOOKUP_STATIC_EXT_SOURCE: {
if (!StringUtils.hasText(staticUserExtSource)) {
throw new ConfigurationException(
"No static ext source has been configured, while static ext source lookup has been set");
}
} break;
case LOOKUP_ORIGINAL_AUTH:
case LOOKUP_PERUN_USER_ID: {
// nothing that needs to be checked
} break;
default: {
throw new ConfigurationException(
"Invalid configuration - unknown user lookup method, check your config. Allowed values are: "
+ LOOKUP_ORIGINAL_AUTH + ", " + LOOKUP_PERUN_USER_ID + ", " + LOOKUP_STATIC_EXT_SOURCE
);
}
}
}
public void setAcrReservedPrefixes(String[] acrReservedPrefixes) {
@ -126,19 +96,4 @@ public class SamlProperties implements InitializingBean {
}
}
public String[] getAcrsToBeAdded() {
return acrsToBeAdded;
}
public void setAcrsToBeAdded(String[] acrsToBeAdded) {
this.acrsToBeAdded = acrsToBeAdded;
}
public String getUserIdentifierAttribute() {
return userIdentifierAttribute;
}
public void setUserIdentifierAttribute(String userIdentifierAttribute) {
this.userIdentifierAttribute = userIdentifierAttribute;
}
}

View File

@ -338,4 +338,6 @@ public interface PerunAdapterMethods {
boolean isUserInVo(Long userId, String voShortName);
PerunUser getPerunUser(Long userId);
}

View File

@ -400,4 +400,17 @@ public class PerunAdapterImpl extends PerunAdapter {
}
}
@Override
public PerunUser getPerunUser(Long userId) {
try {
return this.getAdapterPrimary().getPerunUser(userId);
} catch (UnsupportedOperationException e) {
if (this.isCallFallback()) {
return this.getAdapterFallback().getPerunUser(userId);
} else {
throw e;
}
}
}
}

View File

@ -100,20 +100,7 @@ public class PerunAdapterLdap extends PerunAdapterWithMappingServices implements
FilterBuilder filter = and(
equal(OBJECT_CLASS, PERUN_USER), equal(EDU_PERSON_PRINCIPAL_NAMES, extLogin)
);
SearchScope scope = SearchScope.ONELEVEL;
String[] attributes = new String[]{PERUN_USER_ID, GIVEN_NAME, SN};
EntryMapper<PerunUser> mapper = e -> {
if (!checkHasAttributes(e, new String[] { PERUN_USER_ID, SN })) {
return null;
}
long id = Long.parseLong(e.get(PERUN_USER_ID).getString());
String firstName = (e.get(GIVEN_NAME) != null) ? e.get(GIVEN_NAME).getString() : null;
String lastName = e.get(SN).getString();
return new PerunUser(id, firstName, lastName);
};
return connectorLdap.searchFirst(OU_PEOPLE, filter, scope, attributes, mapper);
return getPerunUser(filter);
}
@Override
@ -535,6 +522,31 @@ public class PerunAdapterLdap extends PerunAdapterWithMappingServices implements
return vo != null;
}
@Override
public PerunUser getPerunUser(Long userId) {
FilterBuilder filter = and(
equal(OBJECT_CLASS, PERUN_USER), equal(PERUN_USER_ID, userId.toString())
);
return getPerunUser(filter);
}
private PerunUser getPerunUser(FilterBuilder filter) {
SearchScope scope = SearchScope.ONELEVEL;
String[] attributes = new String[]{PERUN_USER_ID, GIVEN_NAME, SN};
EntryMapper<PerunUser> mapper = e -> {
if (!checkHasAttributes(e, new String[] { PERUN_USER_ID, SN })) {
return null;
}
long id = Long.parseLong(e.get(PERUN_USER_ID).getString());
String firstName = (e.get(GIVEN_NAME) != null) ? e.get(GIVEN_NAME).getString() : null;
String lastName = e.get(SN).getString();
return new PerunUser(id, firstName, lastName);
};
return connectorLdap.searchFirst(OU_PEOPLE, filter, scope, attributes, mapper);
}
private List<Group> getGroups(Collection<?> objects, String objectAttribute) {
List<Group> result;
if (objects == null || objects.size() <= 0) {

View File

@ -927,6 +927,20 @@ public class PerunAdapterRpc extends PerunAdapterWithMappingServices implements
return hasApplicationForm(vo.getId());
}
@Override
public PerunUser getPerunUser(Long userId) {
if (!this.connectorRpc.isEnabled()) {
return null;
} else if (userId == null) {
throw new IllegalArgumentException("No userId");
}
Map<String, Object> map = new LinkedHashMap<>();
map.put("id", userId);
JsonNode response = connectorRpc.post(USERS_MANAGER, "getUserById", map);
return RpcMapper.mapPerunUser(response);
}
private Member getMemberByUser(Long userId, Long voId) {
if (!this.connectorRpc.isEnabled()) {
return null;

View File

@ -93,8 +93,7 @@ public class AuthProcFiltersContainer extends GenericFilterBean {
AuthProcFiltersContainer.class.getSimpleName(), client.getClientId(), e);
}
}
PerunUser user = FiltersUtils.getPerunUser(req, perunAdapter,
samlProperties.getUserIdentifierAttribute());
PerunUser user = FiltersUtils.getPerunUser(req, perunAdapter, samlProperties);
FilterParams params = new FilterParams(client, facility, user);
for (AuthProcFilter filter : filters) {
if (!filter.doFilter(req, res, params)) {

View File

@ -9,6 +9,7 @@ import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
import cz.muni.ics.oidc.models.Facility;
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.FacilityAttrsConfig;
import cz.muni.ics.oidc.web.controllers.ControllerUtils;
@ -97,22 +98,42 @@ public class FiltersUtils {
public static PerunUser getPerunUser(HttpServletRequest request,
PerunAdapter perunAdapter,
String samlIdAttribute)
{
return getPerunUser(getSamlCredential(request), perunAdapter, samlIdAttribute);
SamlProperties samlProperties) {
return getPerunUser(getSamlCredential(request), perunAdapter, samlProperties);
}
public static PerunUser getPerunUser(SAMLCredential samlCredential,
PerunAdapter perunAdapter,
String samlIdAttribute) {
SamlProperties samlProperties) {
if (perunAdapter == null) {
throw new IllegalArgumentException("Cannot fetch user, no adapter passed");
}
if (samlCredential == null) {
return null;
}
String extLogin = getExtLogin(samlCredential, samlIdAttribute);
String extSourceName = getExtSourceName(samlCredential);
switch (samlProperties.getUserLookupMode()) {
case SamlProperties.LOOKUP_ORIGINAL_AUTH:
case SamlProperties.LOOKUP_STATIC_EXT_SOURCE: {
return getPerunUserByExtSourceAndExtLogin(perunAdapter, samlCredential, samlProperties);
}
case SamlProperties.LOOKUP_PERUN_USER_ID: {
return getPerunUserById(perunAdapter, samlCredential, samlProperties);
}
default: {
log.debug("Could not find user, invalid user lookup configured");
return null;
}
}
}
public static PerunUser getPerunUserByExtSourceAndExtLogin(PerunAdapter perunAdapter, SAMLCredential samlCredential, SamlProperties samlProperties) {
String extSourceName;
if (SamlProperties.LOOKUP_STATIC_EXT_SOURCE.equalsIgnoreCase(samlProperties.getUserLookupMode())) {
extSourceName = samlProperties.getStaticUserExtSource();
} else {
extSourceName = getExtSourceName(samlCredential);
}
String extLogin = getExtLogin(samlCredential, samlProperties.getUserIdentifierAttribute());
if (!StringUtils.hasText(extLogin)) {
return null;
} else if (!StringUtils.hasText(extSourceName)) {
@ -121,6 +142,23 @@ public class FiltersUtils {
return perunAdapter.getPreauthenticatedUserId(extLogin, extSourceName);
}
public static PerunUser getPerunUserById(PerunAdapter perunAdapter, SAMLCredential samlCredential, SamlProperties samlProperties) {
String userIdString = getExtLogin(samlCredential, samlProperties.getUserIdentifierAttribute());
if (!StringUtils.hasText(userIdString)) {
return null;
}
Long userId = null;
try {
userId = Long.parseLong(userIdString);
} catch (NumberFormatException e) {
log.debug("UserID '{}' cannot be parsed as long", userId);
}
if (userId == null) {
return null;
}
return perunAdapter.getPerunUser(userId);
}
public static SAMLCredential getSamlCredential(HttpServletRequest request) {
ExpiringUsernameAuthenticationToken p = (ExpiringUsernameAuthenticationToken) request.getUserPrincipal();
if (p == null) {

View File

@ -38,6 +38,7 @@ public class PerunFilterConstants {
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 SAML_PERUN_USERID_IDENTIFIER = "urn:cesnet:proxyidp:attribute:perunUserId";
public static final String REFEDS_MFA = "https://refeds.org/profile/mfa";
public static final String PROMPT_LOGIN = "login";
@ -50,6 +51,7 @@ public class PerunFilterConstants {
SAML_IDS.put("eptid", SAML_EPTID);
SAML_IDS.put("uid", SAML_UID);
SAML_IDS.put("uniqueIdentifier", SAML_UNIQUE_IDENTIFIER);
SAML_IDS.put("perunUserId", SAML_PERUN_USERID_IDENTIFIER);
}
}

View File

@ -106,7 +106,7 @@ public class PerunForceAupFilter extends AuthProcFilter {
return true;
}
PerunUser user = FiltersUtils.getPerunUser(req, perunAdapter, samlProperties.getUserIdentifierAttribute());
PerunUser user = FiltersUtils.getPerunUser(req, perunAdapter, samlProperties);
if (user == null || user.getId() == null) {
log.debug("{} - skip filter execution: no user provider", filterName);
return true;