Commit 8003a97e authored by michael.simon's avatar michael.simon
Browse files

first commit for supporting SAML2 pairwise and subject id

parent fcd93e09
...@@ -91,6 +91,7 @@ import edu.kit.scc.webreg.service.saml.MetadataHelper; ...@@ -91,6 +91,7 @@ import edu.kit.scc.webreg.service.saml.MetadataHelper;
import edu.kit.scc.webreg.service.saml.Saml2AssertionService; import edu.kit.scc.webreg.service.saml.Saml2AssertionService;
import edu.kit.scc.webreg.service.saml.Saml2ResponseValidationService; import edu.kit.scc.webreg.service.saml.Saml2ResponseValidationService;
import edu.kit.scc.webreg.service.saml.SamlHelper; import edu.kit.scc.webreg.service.saml.SamlHelper;
import edu.kit.scc.webreg.service.saml.SamlIdentifier;
import edu.kit.scc.webreg.service.saml.SsoHelper; import edu.kit.scc.webreg.service.saml.SsoHelper;
import edu.kit.scc.webreg.service.saml.exc.SamlAuthenticationException; import edu.kit.scc.webreg.service.saml.exc.SamlAuthenticationException;
import edu.kit.scc.webreg.service.twofa.TwoFaException; import edu.kit.scc.webreg.service.twofa.TwoFaException;
...@@ -559,8 +560,9 @@ public class UserLoginServiceImpl implements UserLoginService, Serializable { ...@@ -559,8 +560,9 @@ public class UserLoginServiceImpl implements UserLoginService, Serializable {
Assertion assertion = saml2AssertionService.processSamlResponse(samlResponse, idp, idpEntityDescriptor, spEntity); Assertion assertion = saml2AssertionService.processSamlResponse(samlResponse, idp, idpEntityDescriptor, spEntity);
String persistentId = saml2AssertionService.extractPersistentId(assertion, spEntity, null); SamlIdentifier samlIdentifier = saml2AssertionService.extractPersistentId(idp, assertion, spEntity, null);
String persistentId = saml2AssertionService.resolveIdentifier(samlIdentifier, idp, null);
SamlUserEntity user = samlUserDao.findByPersistent(spEntity.getEntityId(), SamlUserEntity user = samlUserDao.findByPersistent(spEntity.getEntityId(),
idp.getEntityId(), persistentId); idp.getEntityId(), persistentId);
......
...@@ -21,10 +21,15 @@ import java.util.Map.Entry; ...@@ -21,10 +21,15 @@ import java.util.Map.Entry;
import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject; import javax.inject.Inject;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import org.opensaml.saml.common.SAMLObject; import org.opensaml.saml.common.SAMLObject;
import org.opensaml.saml.saml2.core.Assertion; import org.opensaml.saml.saml2.core.Assertion;
import org.opensaml.saml.saml2.core.Attribute; import org.opensaml.saml.saml2.core.Attribute;
import org.opensaml.saml.saml2.core.AttributeStatement;
import org.opensaml.saml.saml2.core.EncryptedAssertion; import org.opensaml.saml.saml2.core.EncryptedAssertion;
import org.opensaml.saml.saml2.core.EncryptedID; import org.opensaml.saml.saml2.core.EncryptedID;
import org.opensaml.saml.saml2.core.NameID; import org.opensaml.saml.saml2.core.NameID;
...@@ -44,8 +49,13 @@ import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver; ...@@ -44,8 +49,13 @@ import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver;
import org.opensaml.xmlsec.keyinfo.impl.StaticKeyInfoCredentialResolver; import org.opensaml.xmlsec.keyinfo.impl.StaticKeyInfoCredentialResolver;
import org.slf4j.Logger; import org.slf4j.Logger;
import edu.kit.scc.webreg.entity.SamlIdpMetadataEntity;
import edu.kit.scc.webreg.entity.SamlIdpScopeEntity;
import edu.kit.scc.webreg.entity.SamlMetadataEntity; import edu.kit.scc.webreg.entity.SamlMetadataEntity;
import edu.kit.scc.webreg.entity.SamlSpConfigurationEntity; import edu.kit.scc.webreg.entity.SamlSpConfigurationEntity;
import edu.kit.scc.webreg.entity.ScriptEntity;
import edu.kit.scc.webreg.hook.UserUpdateHookException;
import edu.kit.scc.webreg.script.ScriptingEnv;
import edu.kit.scc.webreg.service.saml.exc.NoAssertionException; import edu.kit.scc.webreg.service.saml.exc.NoAssertionException;
import edu.kit.scc.webreg.service.saml.exc.SamlAuthenticationException; import edu.kit.scc.webreg.service.saml.exc.SamlAuthenticationException;
...@@ -64,6 +74,9 @@ public class Saml2AssertionService { ...@@ -64,6 +74,9 @@ public class Saml2AssertionService {
@Inject @Inject
private Saml2ResponseValidationService saml2ValidationService; private Saml2ResponseValidationService saml2ValidationService;
@Inject
private ScriptingEnv scriptingEnv;
public Assertion processSamlResponse(Response samlResponse, SamlMetadataEntity idpEntity, public Assertion processSamlResponse(Response samlResponse, SamlMetadataEntity idpEntity,
EntityDescriptor idpEntityDescriptor, SamlSpConfigurationEntity spEntity) EntityDescriptor idpEntityDescriptor, SamlSpConfigurationEntity spEntity)
throws IOException, DecryptionException, SamlAuthenticationException { throws IOException, DecryptionException, SamlAuthenticationException {
...@@ -131,10 +144,84 @@ public class Saml2AssertionService { ...@@ -131,10 +144,84 @@ public class Saml2AssertionService {
return assertion; return assertion;
} }
public String extractPersistentId(Assertion assertion, SamlSpConfigurationEntity spEntity, StringBuffer debugLog) public String resolveIdentifier(SamlIdentifier samlIdentifier, SamlIdpMetadataEntity idpEntity, StringBuffer debugLog)
throws SamlAuthenticationException {
if (idpEntity.getGenericStore().containsKey("extract_id_script")) {
String scriptName = idpEntity.getGenericStore().get("extract_id_script");
ScriptEntity scriptEntity = scriptingEnv.getScriptDao().findByName(scriptName);
if (scriptEntity != null && scriptEntity.getScriptType().equalsIgnoreCase("javascript")) {
ScriptEngine engine = (new ScriptEngineManager()).getEngineByName(scriptEntity.getScriptEngine());
if (engine != null) {
try {
engine.eval(scriptEntity.getScript());
Invocable invocable = (Invocable) engine;
Object o = invocable.invokeFunction("extractId", scriptingEnv, samlIdentifier, logger, debugLog);
if (o != null) {
return o.toString();
}
} catch (NoSuchMethodException | ScriptException e) {
logger.warn("Script execution failed", e);
}
}
}
}
/*
* prefer subject-id over pairwise-id over persistent
*/
if (samlIdentifier.getSubjectId() != null) {
return samlIdentifier.getSubjectId();
}
else if (samlIdentifier.getPairwiseId() != null) {
return samlIdentifier.getPairwiseId();
}
else if (samlIdentifier.getPersistentId() != null) {
return samlIdentifier.getPersistentId();
}
else {
throw new SamlAuthenticationException("No usable identifier found. Acceptable identifiers are Pairwise-ID, Subject-ID or Persistent ID");
}
}
public SamlIdentifier extractPersistentId(SamlIdpMetadataEntity idpEntity, Assertion assertion, SamlSpConfigurationEntity spEntity, StringBuffer debugLog)
throws IOException, DecryptionException, SamlAuthenticationException { throws IOException, DecryptionException, SamlAuthenticationException {
logger.debug("Fetching name Id from assertion"); logger.debug("Fetching name Id from assertion");
String persistentId; String persistentId = null;
String pairwiseId = null;
String subjectId = null;
String transientId = null;
logger.debug("Looking up pairwise ID\n");
for (AttributeStatement as : assertion.getAttributeStatements()) {
for (Attribute attribute : as.getAttributes()) {
if (debugLog != null)
debugLog.append("Check " + attribute.getName());
if (attribute.getName().equals("urn:oasis:names:tc:SAML:attribute:pairwise-id")) {
pairwiseId = getIdFromAttribute(idpEntity, attribute, debugLog);
}
else if (attribute.getName().equals("urn:oasis:names:tc:SAML:attribute:subject-id")) {
subjectId = getIdFromAttribute(idpEntity, attribute, debugLog);
}
if (debugLog != null)
debugLog.append("\n");
}
if (debugLog != null && as.getEncryptedAttributes() != null && as.getEncryptedAttributes().size() > 0) {
debugLog.append("EncryptedAttributes are not supported\n");
}
}
if (pairwiseId != null) {
if (debugLog != null)
debugLog.append("\nResulting pairwise ID: " + pairwiseId + "\n\n");
}
/* /*
* Assertion needs a Subject and a NameID, encrypted or not * Assertion needs a Subject and a NameID, encrypted or not
...@@ -180,10 +267,9 @@ public class Saml2AssertionService { ...@@ -180,10 +267,9 @@ public class Saml2AssertionService {
debugLog.append("Resulting NameID (XML):\n").append(samlHelper.prettyPrint(nid)).append("\n"); debugLog.append("Resulting NameID (XML):\n").append(samlHelper.prettyPrint(nid)).append("\n");
logger.debug("NameId format {} value {}", nid.getFormat(), nid.getValue()); logger.debug("NameId format {} value {}", nid.getFormat(), nid.getValue());
if (nid.getFormat().equals(NameID.TRANSIENT)) { if (nid.getFormat().equals(NameID.TRANSIENT)) {
if (debugLog != null) transientId = nid.getValue();
debugLog.append("NameID is Transient. At the moment, only Persistent NameID is supported\n");
throw new SamlAuthenticationException("NameID is Transient but must be Persistent");
} }
else if (nid.getFormat().equals(NameID.PERSISTENT)) { else if (nid.getFormat().equals(NameID.PERSISTENT)) {
persistentId = nid.getValue(); persistentId = nid.getValue();
...@@ -194,8 +280,66 @@ public class Saml2AssertionService { ...@@ -194,8 +280,66 @@ public class Saml2AssertionService {
.append("). At the moment, only Persistent NameID is supported\n"); .append("). At the moment, only Persistent NameID is supported\n");
throw new SamlAuthenticationException("Unsupported SAML2 NameID Type"); throw new SamlAuthenticationException("Unsupported SAML2 NameID Type");
} }
return new SamlIdentifier(persistentId, pairwiseId, subjectId, transientId);
}
protected String getIdFromAttribute(SamlIdpMetadataEntity idpEntity, Attribute attribute, StringBuffer debugLog) {
String id = null;
if (debugLog != null) {
debugLog.append(" ...found:\n");
debugLog.append(samlHelper.prettyPrint(attribute) + "\n");
}
List<Object> attributeList = samlHelper.getAttribute(attribute);
if (attributeList.size() == 1) {
Object o = attributeList.get(0);
if (o.toString().contains("@")) {
String[] s = o.toString().split("@");
if (s.length != 2 && debugLog != null)
debugLog.append("Pairwise ID contains more than one '@'\n");
else {
String p = s[0];
String scope = s[1];
boolean scopeMatch = false;
for (SamlIdpScopeEntity idpScope : idpEntity.getScopes()) {
if (idpScope.getRegex()) {
if (scope.matches(idpScope.getScope())) {
scopeMatch = true;
break;
}
}
else {
if (idpScope.getScope().equals(scope)) {
scopeMatch = true;
break;
}
}
}
if ((! scopeMatch) && debugLog != null) {
debugLog.append("Scope " + scope + " is NOT matching IDP " + idpEntity.getEntityId() + "\n");
debugLog.append("Not using pairwise ID!\n");
}
else {
if (debugLog != null)
debugLog.append("Scope " + scope + " is matching IDP " + idpEntity.getEntityId() + "\n");
id = p;
}
}
}
else {
if (debugLog != null)
debugLog.append("Pairwise ID is not scoped. Can't use it. Looking for persistent ID.\n");
}
}
else {
if (debugLog != null)
debugLog.append("Pairwise ID does not contain exactly one value. Can't use it. Looking for persistent ID.\n");
}
return persistentId; return id;
} }
public Assertion decryptAssertion(EncryptedAssertion encryptedAssertion, public Assertion decryptAssertion(EncryptedAssertion encryptedAssertion,
......
package edu.kit.scc.webreg.service.saml;
import java.io.Serializable;
public class SamlIdentifier implements Serializable {
private static final long serialVersionUID = 1L;
private String persistentId;
private String pairwiseId;
private String subjectId;
private String transientId;
public SamlIdentifier(String persistentId, String pairwiseId, String subjectId, String transientId) {
super();
this.persistentId = persistentId;
this.pairwiseId = pairwiseId;
this.subjectId = subjectId;
this.transientId = transientId;
}
public String getPersistentId() {
return persistentId;
}
public String getPairwiseId() {
return pairwiseId;
}
public String getSubjectId() {
return subjectId;
}
public String getTransientId() {
return transientId;
}
}
...@@ -87,6 +87,7 @@ public class SamlSpPostServiceImpl implements SamlSpPostService { ...@@ -87,6 +87,7 @@ public class SamlSpPostServiceImpl implements SamlSpPostService {
Assertion assertion = null; Assertion assertion = null;
Response samlResponse = null; Response samlResponse = null;
SamlIdentifier samlIdentifier;
String persistentId; String persistentId;
try { try {
...@@ -111,8 +112,9 @@ public class SamlSpPostServiceImpl implements SamlSpPostService { ...@@ -111,8 +112,9 @@ public class SamlSpPostServiceImpl implements SamlSpPostService {
if (debugLog != null) { if (debugLog != null) {
debugLog.append("Extract Persistent NameID...\n"); debugLog.append("Extract Persistent NameID...\n");
} }
persistentId = saml2AssertionService.extractPersistentId(assertion, spConfig, debugLog); samlIdentifier = saml2AssertionService.extractPersistentId(idpEntity, assertion, spConfig, debugLog);
persistentId = saml2AssertionService.resolveIdentifier(samlIdentifier, idpEntity, debugLog);
if (debugLog != null) { if (debugLog != null) {
debugLog.append("Resulting Persistent NameID: ").append(persistentId).append("\n"); debugLog.append("Resulting Persistent NameID: ").append(persistentId).append("\n");
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment