Added custom InMemory SAML2 Authentication Request Repository
Added attributes mapping in application*.yml config Added attributes default values in application*.yml config Corrected MOCK-IDP to return InResponseTo and complex attribute names
This commit is contained in:
parent
f548a0b31e
commit
eb3a621b17
7 changed files with 458 additions and 75 deletions
|
@ -144,14 +144,14 @@ resilient:
|
|||
enabled: false
|
||||
port: 8081
|
||||
mock-idp:
|
||||
enabled: true
|
||||
enabled: false
|
||||
path: classpath:mock-idp/idp.js
|
||||
security:
|
||||
saml2: # ADDED to support SAMLv2 authentication to IDP.
|
||||
# Metadata endpoint ${base-url}/saml2/service-provider-metadata/mock-idp
|
||||
enabled: true
|
||||
idp-id: unl-idp # The id of the IDP to use. One from the collection in relyingparty.registration
|
||||
base-url: https://resilient.localhost # old: https://localhost:8443
|
||||
idp-id: mock-idp # The id of the IDP to use. One from the collection in relyingparty.registration
|
||||
base-url: http://resilient.localhost # old: https://localhost:8443
|
||||
success-url: http://resilient.localhost/
|
||||
failure-url: http://resilient.localhost/login
|
||||
relyingparty:
|
||||
|
@ -163,10 +163,22 @@ resilient:
|
|||
url: http://mock-idp.localhost/saml/sso # old: http://localhost:3000/saml/sso
|
||||
# OPTIONAL. A list of query parameters to add to single-sign-on.url. This is usefull for mock-idp, to give instructions on how to behave
|
||||
query-parameters:
|
||||
spUrl: https://resilient.localhost # The callback to Service Provider, after IDP authentication (OK | KO). Appends the encoded url: acs=https%3A%2F%2Fresilient.localhost%2Flogin%2Fsaml2%2Fsso%2Fmock-idp
|
||||
spUrl: http://resilient.localhost # The callback to Service Provider, after IDP authentication (OK | KO). Appends the encoded url: acs=https%3A%2F%2Fresilient.localhost%2Flogin%2Fsaml2%2Fsso%2Fmock-idp
|
||||
issuerUrl: http://mock-idp.localhost/saml/metadata # The IDP entity-id. This is needed for mock-idp to build saml2 response
|
||||
single-logout:
|
||||
url: http://mock-idp.localhost/saml/slo # old: http://localhost:3000/saml/slo
|
||||
attributes: # This is a mapping between the needed attributes, and the names of the attributes in the SAML2 Response
|
||||
# Leave BLANK if SAML doesn't provide that attribute'
|
||||
name: name # the user display name [OPTIONAL]
|
||||
username: urn:mace:dir:attribute-def:mail # the username, typically for authentication. Fallsback to email. [MANDATORY]
|
||||
email: email # the user email [MANDATORY]
|
||||
organization-code: organization_code # organization unit code [OPTIONAL]
|
||||
security-group-code: security_group # security group code [OPTIONAL]
|
||||
role: roles # a single role is expected [OPTIONAL]
|
||||
defaults: # For some attributes defaults can be given. This will be used if SAML2 response doesn't have them
|
||||
organization-code: NOVA # default organization unit code
|
||||
security-group-code: GRP_USER # default security group code
|
||||
role: ROLE_USER # default role
|
||||
verification:
|
||||
credentials:
|
||||
- certificate-location: classpath:saml/idp-public.cert
|
||||
|
@ -183,15 +195,28 @@ resilient:
|
|||
url: http://unl-idp.localhost/saml/sso
|
||||
# OPTIONAL. A list of query parameters to add to single-sign-on.url. This is usefull for mock-idp, to give instructions on how to behave
|
||||
query-parameters:
|
||||
spUrl: https://resilient.localhost # The callback to Service Provider, after IDP authentication (OK | KO). Appends the encoded url: acs=https%3A%2F%2Fresilient.localhost%2Flogin%2Fsaml2%2Fsso%2Fmock-idp
|
||||
spUrl: http://resilient.localhost # The callback to Service Provider, after IDP authentication (OK | KO). Appends the encoded url: acs=https%3A%2F%2Fresilient.localhost%2Flogin%2Fsaml2%2Fsso%2Fmock-idp
|
||||
issuerUrl: http://unl-idp.localhost/saml/metadata # The IDP entity-id. This is needed for mock-idp to build saml2 response
|
||||
single-logout:
|
||||
url: http://unl-idp.localhost/saml/slo # old: http://localhost:3000/saml/slo
|
||||
attributes: # This is a mapping between the needed attributes, and the names of the attributes in the SAML2 Response
|
||||
# Leave BLANK if SAML doesn't provide that attribute'
|
||||
name: urn:mace:dir:attribute-def:displayName # the user display name [OPTIONAL]
|
||||
username: urn:mace:dir:attribute-def:mail # the username, typically for authentication. Fallsback to email. [MANDATORY]
|
||||
email: urn:mace:dir:attribute-def:mail # the user email [MANDATORY]
|
||||
organization-code: # organization unit code [OPTIONAL]
|
||||
security-group-code: # security group code [OPTIONAL]
|
||||
role: # a single role is expected [OPTIONAL]
|
||||
defaults: # For some attributes defaults can be given. This will be used if SAML2 response doesn't have them
|
||||
organization-code: NOVA # default organization unit code
|
||||
security-group-code: GRP_USER # default security group code
|
||||
role: ROLE_USER # default role
|
||||
verification:
|
||||
credentials:
|
||||
- certificate-location: classpath:saml/idp-public.cert
|
||||
want-authn-signed: false # Validate signature in entire message response (true-validates/false-doesn't validate)
|
||||
want-assertion-signed: true # Validate signature in assertions message response (true-validates/false-doesn't validate)
|
||||
check-in-response-to: false # The UNL IDP doesn't implement this. Must be false.
|
||||
signing:
|
||||
credentials:
|
||||
- private-key-location: classpath:saml/private.key
|
||||
|
|
|
@ -132,6 +132,91 @@ function createFakeSamlResponse(nameId, email, orgCode, securityGroupCode, roles
|
|||
console.log('SAML Destination :', destination);
|
||||
console.log('SAML Issuer :', issuer);
|
||||
|
||||
const assertion = `
|
||||
<saml:Assertion
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:xs="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
|
||||
ID="${assertionId}"
|
||||
IssueInstant="${now}"
|
||||
Version="2.0">
|
||||
<saml:Issuer>${issuer}</saml:Issuer>
|
||||
<saml:Subject>
|
||||
<saml:NameID>${nameId}</saml:NameID>
|
||||
<saml:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
|
||||
<saml:SubjectConfirmationData NotOnOrAfter="${fiveMinutesLater}" Recipient="${recipient}" />
|
||||
</saml:SubjectConfirmation>
|
||||
</saml:Subject>
|
||||
<saml:Conditions NotBefore="${now}" NotOnOrAfter="${fiveMinutesLater}">
|
||||
<saml:AudienceRestriction>
|
||||
<saml:Audience>${audience}</saml:Audience>
|
||||
</saml:AudienceRestriction>
|
||||
</saml:Conditions>
|
||||
<saml:AuthnStatement AuthnInstant="${now}">
|
||||
<saml:AuthnContext>
|
||||
<saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport</saml:AuthnContextClassRef>
|
||||
</saml:AuthnContext>
|
||||
</saml:AuthnStatement>
|
||||
<saml:AttributeStatement>
|
||||
<saml:Attribute Name="email">
|
||||
<saml:AttributeValue>${email}</saml:AttributeValue>
|
||||
</saml:Attribute>
|
||||
<saml:Attribute Name="organization_code">
|
||||
<saml:AttributeValue>${orgCode}</saml:AttributeValue>
|
||||
</saml:Attribute>
|
||||
<saml:Attribute Name="security_group">
|
||||
<saml:AttributeValue>${securityGroupCode}</saml:AttributeValue>
|
||||
</saml:Attribute>
|
||||
<saml:Attribute Name="roles">
|
||||
<saml:AttributeValue>${roles}</saml:AttributeValue>
|
||||
</saml:Attribute>
|
||||
<saml:Attribute Name="urn:mace:dir:attribute-def:displayName" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
|
||||
<saml:AttributeValue xsi:type="xs:string">Pedro Reis</saml:AttributeValue>
|
||||
</saml:Attribute>
|
||||
<saml:Attribute Name="urn:oid:2.16.840.1.113730.3.1.241" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
|
||||
<saml:AttributeValue xsi:type="xs:string">Pedro Reis</saml:AttributeValue>
|
||||
</saml:Attribute>
|
||||
<saml:Attribute Name="urn:mace:dir:attribute-def:mail" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
|
||||
<saml:AttributeValue xsi:type="xs:string">pedro.reis@unl.pt</saml:AttributeValue>
|
||||
</saml:Attribute>
|
||||
<saml:Attribute Name="urn:oid:0.9.2342.19200300.100.1.3" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
|
||||
<saml:AttributeValue xsi:type="xs:string">pedro.reis@unl.pt</saml:AttributeValue>
|
||||
</saml:Attribute>
|
||||
</saml:AttributeStatement>
|
||||
</saml:Assertion>`;
|
||||
|
||||
const privateKey = fs.readFileSync(path.join(__dirname, 'certs', 'idp-private.key'), 'utf8');
|
||||
const certificate = fs.readFileSync(path.join(__dirname, 'certs', 'idp-public.cert'), 'utf8');
|
||||
const certBase64 = certificate
|
||||
.replace(/-----BEGIN CERTIFICATE-----/, '')
|
||||
.replace(/-----END CERTIFICATE-----/, '')
|
||||
.replace(/\r?\n|\r/g, '');
|
||||
|
||||
const doc = new DOMParser().parseFromString(assertion);
|
||||
const sig = new SignedXml();
|
||||
sig.prefix = 'ds';
|
||||
sig.addReference("//*[local-name(.)='Assertion']", [
|
||||
"http://www.w3.org/2000/09/xmldsig#enveloped-signature",
|
||||
"http://www.w3.org/2001/10/xml-exc-c14n#",
|
||||
], "http://www.w3.org/2000/09/xmldsig#sha1");
|
||||
sig.signingKey = privateKey;
|
||||
sig.keyInfoProvider = {
|
||||
getKeyInfo: () => `<ds:X509Data><ds:X509Certificate>${certBase64}</ds:X509Certificate></ds:X509Data>`
|
||||
};
|
||||
sig.computeSignature(assertion, {
|
||||
prefix: 'ds',
|
||||
location: { reference: "//*[local-name(.)='Issuer']", action: 'after' }
|
||||
});
|
||||
|
||||
const signedAssertion = sig.getSignedXml();
|
||||
const samlResponse = `<?xml version="1.0" encoding="UTF-8"?><samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="${responseId}" Version="2.0" IssueInstant="${now}" Destination="${destination}" InResponseTo="${inResponseTo}"><saml:Issuer>${issuer}</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status>${signedAssertion}</samlp:Response>`;
|
||||
|
||||
const samlResponseToUse = samlResponse.trim().replace(/^\uFEFF/, '');
|
||||
return Buffer.from(samlResponseToUse, 'utf-8').toString('base64');
|
||||
}
|
||||
|
||||
// OLD assertion string. This contains also some extra attributes
|
||||
function assertionOLD() {
|
||||
const assertion = `
|
||||
<saml:Assertion xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="${assertionId}" IssueInstant="${now}" Version="2.0">
|
||||
<saml:Issuer>${issuer}</saml:Issuer>
|
||||
|
@ -166,35 +251,6 @@ function createFakeSamlResponse(nameId, email, orgCode, securityGroupCode, roles
|
|||
</saml:Attribute>
|
||||
</saml:AttributeStatement>
|
||||
</saml:Assertion>`;
|
||||
|
||||
const privateKey = fs.readFileSync(path.join(__dirname, 'certs', 'idp-private.key'), 'utf8');
|
||||
const certificate = fs.readFileSync(path.join(__dirname, 'certs', 'idp-public.cert'), 'utf8');
|
||||
const certBase64 = certificate
|
||||
.replace(/-----BEGIN CERTIFICATE-----/, '')
|
||||
.replace(/-----END CERTIFICATE-----/, '')
|
||||
.replace(/\r?\n|\r/g, '');
|
||||
|
||||
const doc = new DOMParser().parseFromString(assertion);
|
||||
const sig = new SignedXml();
|
||||
sig.prefix = 'ds';
|
||||
sig.addReference("//*[local-name(.)='Assertion']", [
|
||||
"http://www.w3.org/2000/09/xmldsig#enveloped-signature",
|
||||
"http://www.w3.org/2001/10/xml-exc-c14n#",
|
||||
], "http://www.w3.org/2000/09/xmldsig#sha1");
|
||||
sig.signingKey = privateKey;
|
||||
sig.keyInfoProvider = {
|
||||
getKeyInfo: () => `<ds:X509Data><ds:X509Certificate>${certBase64}</ds:X509Certificate></ds:X509Data>`
|
||||
};
|
||||
sig.computeSignature(assertion, {
|
||||
prefix: 'ds',
|
||||
location: { reference: "//*[local-name(.)='Issuer']", action: 'after' }
|
||||
});
|
||||
|
||||
const signedAssertion = sig.getSignedXml();
|
||||
const samlResponse = `<?xml version="1.0" encoding="UTF-8"?><samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="${responseId}" Version="2.0" IssueInstant="${now}" Destination="${destination}" InResponseTo="${inResponseTo}"><saml:Issuer>${issuer}</saml:Issuer><samlp:Status><samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/></samlp:Status>${signedAssertion}</samlp:Response>`;
|
||||
|
||||
const samlResponseToUse = samlResponse.trim().replace(/^\uFEFF/, '');
|
||||
return Buffer.from(samlResponseToUse, 'utf-8').toString('base64');
|
||||
}
|
||||
|
||||
function createLoginFormOLD() {
|
||||
|
@ -350,5 +406,11 @@ function extractIssuerFromSAMLRequest(samlRequestBase64) {
|
|||
}
|
||||
|
||||
app.listen(port, () => {
|
||||
const now = new Date();
|
||||
const localISOTime = now.toLocaleString('pt-PT'); // ISO-like, local time
|
||||
|
||||
const nowUTC = new Date().toISOString();
|
||||
|
||||
console.log(`Mock IDP running at http://localhost:${port}`);
|
||||
console.log(`Started at ${localISOTime} (UTC is ${now})`);
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue