Skip to content

Commit

Permalink
feat: ajout pairwise-id et eduPersonTargetedID attribute release policy
Browse files Browse the repository at this point in the history
  • Loading branch information
nathancailbourdin committed Dec 20, 2024
1 parent 7f1048f commit 3ab82dd
Show file tree
Hide file tree
Showing 7 changed files with 277 additions and 0 deletions.
32 changes: 32 additions & 0 deletions ci/python/flask-saml-client/saml/settings26.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"strict": true,
"debug": true,
"sp": {
"entityId": "http://localhost:8026/",
"assertionConsumerService": {
"url": "http://localhost:8026/?acs",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
},
"singleLogoutService": {
"url": "http://localhost:8026/?sls",
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
},
"NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
},
"security": {
"nameIdEncrypted": false,
"authnRequestsSigned": true,
"logoutRequestSigned": true,
"logoutResponseSigned": true,
"signMetadata": false,
"wantMessagesSigned": false,
"wantAssertionsSigned": false,
"wantNameId" : true,
"wantNameIdEncrypted": false,
"wantAssertionsEncrypted": false,
"allowSingleLabelDomains": false,
"signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
"digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256",
"rejectDeprecatedAlgorithm": true
}
}
3 changes: 3 additions & 0 deletions puppeteer/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ python3 index.py --port 8024 --settings "saml/settings24.json" &
pid_python_saml_client2=$!
python3 index.py --port 8025 --settings "saml/settings25.json" &
pid_python_saml_client3=$!
python3 index.py --port 8026 --settings "saml/settings26.json" &
pid_python_saml_client4=$!
cd "../flask-oidc-client"
python3 index.py --port 8018 --scopes "openid profile test" --clientid "client-testcas" --clientsecret "secret-testcas" &
pid_python_oidc_client=$!
Expand Down Expand Up @@ -181,6 +183,7 @@ kill -9 "$pid_python_externalid_api"
kill -9 "$pid_python_saml_client"
kill -9 "$pid_python_saml_client2"
kill -9 "$pid_python_saml_client3"
kill -9 "$pid_python_saml_client4"
kill -9 "$pid_python_oidc_client"
kill -9 "$pid_python_oidc_client2"
kill -9 "$pid_python_oidc_client3"
Expand Down
38 changes: 38 additions & 0 deletions puppeteer/scenarios/release_attribute_pairwiseid_saml.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
const cas = require("../cas.js");
const puppeteer = require('puppeteer');
const assert = require("assert");

(async () => {
const browser = await puppeteer.launch(cas.browserOptions());

try {
const page = await browser.newPage();
const client = await page.createCDPSession();

// Login to cas
await page.goto("http://localhost:8026/?sso");
await cas.typeCredentialsAndEnter(page, "test1", "test");
await page.waitForNavigation();

// Assert that TGC exists
await cas.verifyTGC(client)

// waitForNetworkIdle is necessary in that case to obtain pageContent, or we get an error "Execution context was destroyed"
await page.waitForNetworkIdle();
const pageContent = await page.content();

// Assert that attributes were received
assert(pageContent.includes("urn:oid:1.3.6.1.4.1.5923.1.1.1.10"));
assert(pageContent.includes("https://localhost:8443/cas/idp/metadata!http://localhost:8026/!JXQDG2DSYRU742NC7PYHAXNJST5LKX6M"));
assert(pageContent.includes("urn:oid:urn:oasis:names:tc:SAML:attribute:pairwise-id"));
assert(pageContent.includes("[email protected]"));

process.exit(0)

} catch (e) {
cas.loge(e);
process.exit(1)
} finally {
await browser.close();
}
})();
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package org.apereo.cas.support.saml.services;

import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.services.RegisteredServiceAttributeReleasePolicyContext;
import org.apereo.cas.support.saml.services.idp.metadata.SamlRegisteredServiceMetadataAdaptor;
import org.apereo.cas.support.saml.services.idp.metadata.cache.SamlRegisteredServiceCachingMetadataResolver;
import org.apereo.cas.util.EncodingUtils;
import org.apereo.cas.util.function.FunctionUtils;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.opensaml.saml.saml2.metadata.EntityDescriptor;

import java.io.Serial;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* This is {@link PairwiseIdSamlRegisteredServiceAttributeReleasePolicy}.
*
* @author Nathan Cailbourdin
* @since 7.1.3
*/
@Slf4j
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class PairwiseIdSamlRegisteredServiceAttributeReleasePolicy extends BaseSamlRegisteredServiceAttributeReleasePolicy {

@Serial
private static final long serialVersionUID = 2387874851513467956L;

private String salt;
private String attributeName;
private String separator = "!";
private String algorithm = "SHA";

@Override
protected Map<String, List<Object>> getAttributesForSamlRegisteredService(
final Map<String, List<Object>> attributes,
final SamlRegisteredServiceCachingMetadataResolver resolver,
final SamlRegisteredServiceMetadataAdaptor facade,
final EntityDescriptor entityDescriptor,
final RegisteredServiceAttributeReleasePolicyContext context) {

val casProperties = context.getApplicationContext().getBean(CasConfigurationProperties.class);

val entityId = entityDescriptor.getEntityID();
LOGGER.debug("SP entityId is [{}]", entityId);

val attribute = attributes.get(attributeName).getFirst().toString();
LOGGER.debug("Attribute is [{}] based on attribute name [{}]", attribute, attributeName);

return FunctionUtils.doUnchecked(() -> {

val md = MessageDigest.getInstance(algorithm);
if (StringUtils.isNotBlank(entityId)) {
md.update(entityId.getBytes(StandardCharsets.UTF_8));
md.update(separator.getBytes());
}
md.update(attribute.getBytes(StandardCharsets.UTF_8));
md.update(separator.getBytes());

val digestedMessage = md.digest(salt.getBytes(StandardCharsets.UTF_8));
val encodedMessage = EncodingUtils.encodeBase32(digestedMessage, false);
LOGGER.trace("Encoded digested message to base32 : [{}]", encodedMessage);
val finalValue = encodedMessage + "@" + casProperties.getServer().getScope();
LOGGER.debug("Final value for pairwise-id is [{}]", finalValue);

Map<String, List<Object>> toRelease = new HashMap<>(1);
toRelease.put("urn:oid:urn:oasis:names:tc:SAML:attribute:pairwise-id", Collections.singletonList(finalValue));
return toRelease;
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package org.apereo.cas.support.saml.services;

import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.services.RegisteredServiceAttributeReleasePolicyContext;
import org.apereo.cas.support.saml.services.idp.metadata.SamlRegisteredServiceMetadataAdaptor;
import org.apereo.cas.support.saml.services.idp.metadata.cache.SamlRegisteredServiceCachingMetadataResolver;
import org.apereo.cas.util.EncodingUtils;
import org.apereo.cas.util.function.FunctionUtils;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.opensaml.saml.saml2.metadata.EntityDescriptor;

import java.io.Serial;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* This is {@link TargetedIdSamlRegisteredServiceAttributeReleasePolicy}.
*
* @author Nathan Cailbourdin
* @since 7.1.3
*/
@Slf4j
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class TargetedIdSamlRegisteredServiceAttributeReleasePolicy extends BaseSamlRegisteredServiceAttributeReleasePolicy {

@Serial
private static final long serialVersionUID = 2387874851513467956L;

private String salt;
private String attributeName;
private String separator = "!";
private String algorithm = "SHA";

@Override
protected Map<String, List<Object>> getAttributesForSamlRegisteredService(
final Map<String, List<Object>> attributes,
final SamlRegisteredServiceCachingMetadataResolver resolver,
final SamlRegisteredServiceMetadataAdaptor facade,
final EntityDescriptor entityDescriptor,
final RegisteredServiceAttributeReleasePolicyContext context) {

val casProperties = context.getApplicationContext().getBean(CasConfigurationProperties.class);

val attribute = attributes.get(attributeName).getFirst().toString();
LOGGER.debug("Attribute is [{}] based on attribute name [{}]", attribute, attributeName);

val sp = entityDescriptor.getEntityID();
LOGGER.debug("SP entityId is [{}]", sp);

val idp = casProperties.getAuthn().getSamlIdp().getCore().getEntityId();
LOGGER.debug("IDP entityId is [{}]", idp);

return FunctionUtils.doUnchecked(() -> {

val md = MessageDigest.getInstance(algorithm);
if (StringUtils.isNotBlank(sp)) {
md.update(sp.getBytes(StandardCharsets.UTF_8));
md.update(separator.getBytes());
}
md.update(attribute.getBytes(StandardCharsets.UTF_8));
md.update(separator.getBytes());

val digestedMessage = md.digest(salt.getBytes(StandardCharsets.UTF_8));
val encodedMessage = EncodingUtils.encodeBase32(digestedMessage, false);
LOGGER.trace("Encoded digested message to base32 : [{}]", encodedMessage);
val finalValue = idp + separator + sp + separator + encodedMessage;
LOGGER.debug("Final value for pairwise-id is [{}]", finalValue);

Map<String, List<Object>> toRelease = new HashMap<>(1);
toRelease.put("eduPersonTargetedID", Collections.singletonList(finalValue));
return toRelease;
});

}
}
1 change: 1 addition & 0 deletions src/main/resources/application-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ server.port: 8443
spring.main.lazy-initialization: false
cas.server.name: https://localhost:8443
cas.server.prefix: https://localhost:8443/cas
cas.server.scope: cas-ci.git

# Monitoring endpoints
# Health
Expand Down
29 changes: 29 additions & 0 deletions src/main/resources/services-test/servicetest-26.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"@class": "org.apereo.cas.support.saml.services.SamlRegisteredService",
"serviceId": "^http:\/\/localhost:8026\/.*",
"name": "Service Test",
"description": "Service de test pour pairwise-id et eduPersonTargetedId",
"id": 26,
"metadataLocation": "http://localhost:8026/metadata",
"attributeReleasePolicy": {
"@class": "org.apereo.cas.services.ChainingAttributeReleasePolicy",
"policies": [ "java.util.ArrayList",
[
{
"@class" : "org.apereo.cas.support.saml.services.PairwiseIdSamlRegisteredServiceAttributeReleasePolicy",
"attributeName" : "uid",
"salt": "XfpLvtJ72E"
},
{
"@class" : "org.apereo.cas.support.saml.services.TargetedIdSamlRegisteredServiceAttributeReleasePolicy",
"attributeName" : "uid",
"salt": "XfpLvtJ72E"
}
]
]
},
"webflowInterruptPolicy": {
"@class": "org.apereo.cas.services.DefaultRegisteredServiceWebflowInterruptPolicy",
"enabled": false
}
}

0 comments on commit 3ab82dd

Please sign in to comment.