Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
175 changes: 175 additions & 0 deletions e2e/sts/rest-sts-test.spec.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/*
* The contents of this file are subject to the terms of the Common Development and
* Distribution License (the License). You may not use this file except in compliance with the
* License.
*
* You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
* specific language governing permission and limitations under the License.
*
* When distributing Covered Software, include this CDDL Header Notice in each file and include
* the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
* Header, with the fields enclosed by brackets [] replaced by your own identifying
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2026 3A Systems, LLC.
*/

import { test, expect } from "@playwright/test";
import { resolve } from "path";
import { fileURLToPath } from "url";

// ─── Configuration ────────────────────────────────────────────────────────────
const BASE_URL = process.env.OPENAM_BASE_URL ?? "http://openam.example.org:8080/openam";
const ADMIN_USER = process.env.OPENAM_ADMIN_USER ?? "amadmin";
const ADMIN_PASS = process.env.OPENAM_ADMIN_PASS ?? "ampassword";

const STS_INSTANCE_NAME = "openam-to-saml-sts";
const SP_ENTITY_ID = "https://sp.example.com";
const SP_ACS_URL = "https://sp.example.com/acs";

const __filename = fileURLToPath(import.meta.url);
const __dirname = resolve(__filename, "..");

// Get admin token
async function getAuthToken(request, username, password) {
const resp = await request.post(`${BASE_URL}/json/authenticate`, {
headers: {
"Content-Type": "application/json",
"X-OpenAM-Username": username,
"X-OpenAM-Password": password,
"Content-Type": "application/json",
"Accept-API-Version": "resource=2.0, protocol=1.0",
}
});
const json = await resp.json();
return json.tokenId;
}

async function setupSts(request) {

}

// ─── Tests ────────────────────────────────────────────────────────────────────
test.describe("REST STS - OpenAM Token → SAML2", () => {

let adminToken;


async function stsExists(request) {
const response = await request.get(`${BASE_URL}/sts-publish/rest`, {
headers: {
"Content-Type": "application/json",
"iPlanetDirectoryPro": adminToken
},
});

if(!response.ok()) {
return false;
}
const json = await response.json();
return !!json[STS_INSTANCE_NAME]
}

async function setupSts(request) {
const payload = {
invocation_context: "invocation_context_client_sdk",
instance_state: {
"deployment-config": {
"deployment-url-element": STS_INSTANCE_NAME,
"deployment-realm": "/",
"deployment-auth-target-mappings": {}
},
"saml2-config": {
"issuer-name": `${BASE_URL}`,
"saml2-sp-entity-id": SP_ENTITY_ID,
"saml2-sp-acs-url": SP_ACS_URL,
"saml2-signature-key-alias": "test",
"saml2-sign-assertion": "false",
"saml2-encrypt-assertion": "false",
"saml2-encrypt-attributes": "false",
"saml2-encrypt-nameid": "false",
"saml2-name-id-format": "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
"saml2-token-lifetime-seconds": "600",
"saml2-encryption-algorithm-strength": "128",
"saml2-attribute-map": {
"email": "mail"
}
},
"persist-issued-tokens-in-cts": "false",
"supported-token-transforms": [
{
"inputTokenType": "OPENAM",
"outputTokenType": "SAML2",
"invalidateInterimOpenAMSession": false
}
],
"token-lifetime": 600
}
};

const response = await request.post(`${BASE_URL}/sts-publish/rest?_action=create`, {
headers: {
"Content-Type": "application/json",
"iPlanetDirectoryPro": adminToken
},
data: payload
});

expect(response.ok()).toBeTruthy();
const body = await response.json();
console.log("REST STS instance created:", body);
expect(body).toHaveProperty("_id");
}

test.beforeAll(async ({ request }) => {
adminToken = await getAuthToken(request, ADMIN_USER, ADMIN_PASS);
expect(adminToken).toBeTruthy();
console.log(`Admin token obtained: ${adminToken.slice(0, 20)}...`);
const haveSts = await stsExists(request)
if(!haveSts) {
await setupSts(request);
}
});


test("should translate OpenAM token to SAML2 assertion", async ({ request }) => {

const userSession = await getAuthToken(request, "demo", "changeit");

const translatePayload = {
input_token_state: {
token_type: "OPENAM",
session_id: userSession
},
output_token_state: {
token_type: "SAML2",
subject_confirmation: "BEARER"
}
};

const response = await request.post(
`${BASE_URL}/rest-sts/${STS_INSTANCE_NAME}?_action=translate`,
{
headers: {
"Content-Type": "application/json",
"iPlanetDirectoryPro": userSession
},
data: translatePayload
}
);

expect(response.ok()).toBeTruthy();
const result = await response.json();

expect(result).toHaveProperty("issued_token");
const assertion = result.issued_token;

console.log(`SAML Assertion received: ${assertion.substring(0, 300)}...`,);

// Basic XML validation
expect(assertion).toContain("<saml:Assertion");
expect(assertion).toContain(`<saml:Issuer>${BASE_URL}`);
expect(assertion).toContain(SP_ENTITY_ID);
});

});
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
*
* Copyright 2015-2016 ForgeRock AS.
* Portions copyright 2019 Open Source Solution Technology Corporation
* Portions copyright 2025 3A Systems LLC.
* Portions copyright 2025-2026 3A Systems LLC.
*/
package org.forgerock.openam.authentication.modules.saml2;

Expand Down Expand Up @@ -222,23 +222,23 @@ private int initiateSAMLLoginAtIDP(final HttpServletResponse response, final Htt
bundle.getString("samlLocalConfigFailed"));
}

List<SingleSignOnServiceElement> ssoServiceList = idpsso.getSingleSignOnService();
List<SingleSignOnServiceElement> ssoServiceList = idpsso.getValue().getSingleSignOnService();
final SingleSignOnServiceElement endPoint = SPSSOFederate
.getSingleSignOnServiceEndpoint(ssoServiceList, reqBinding);

if (endPoint == null || StringUtils.isEmpty(endPoint.getLocation())) {
if (endPoint == null || StringUtils.isEmpty(endPoint.getValue().getLocation())) {
throw new SAML2Exception(SAML2Utils.bundle.getString("ssoServiceNotfound"));
}
if (reqBinding == null) {
SAML2Utils.debug.message("SAML2 :: initiateSAMLLoginAtIDP() reqBinding is null using endpoint binding: {}",
endPoint.getBinding());
reqBinding = endPoint.getBinding();
endPoint.getValue().getBinding());
reqBinding = endPoint.getValue().getBinding();
if (reqBinding == null) {
throw new SAML2Exception(SAML2Utils.bundle.getString("UnableTofindBinding"));
}
}

String ssoURL = endPoint.getLocation();
String ssoURL = endPoint.getValue().getLocation();
SAML2Utils.debug.message("SAML2 :: initiateSAMLLoginAtIDP() ssoURL : {}", ssoURL);

final List extensionsList = SPSSOFederate.getExtensionsList(spEntityID, realm);
Expand Down Expand Up @@ -619,7 +619,7 @@ private NameID getNameId() throws SAML2Exception, AuthLoginException {
final EncryptedID encId = assertionSubject.getEncryptedID();
final String spName = metaManager.getEntityByMetaAlias(metaAlias);
final SPSSOConfigElement spssoconfig = metaManager.getSPSSOConfig(realm, spName);
final Set<PrivateKey> decryptionKeys = KeyUtil.getDecryptionKeys(spssoconfig);
final Set<PrivateKey> decryptionKeys = KeyUtil.getDecryptionKeys(spssoconfig.getValue());

NameID nameId = assertionSubject.getNameID();

Expand Down Expand Up @@ -670,7 +670,7 @@ private void linkAttributeValues(Assertion assertion, String userName)
SAML2Constants.WANT_ASSERTION_ENCRYPTED));
final boolean needAttributeEncrypted =
SPACSUtils.getNeedAttributeEncrypted(needAssertionEncrypted, spssoconfig);
final Set<PrivateKey> decryptionKeys = KeyUtil.getDecryptionKeys(spssoconfig);
final Set<PrivateKey> decryptionKeys = KeyUtil.getDecryptionKeys(spssoconfig.getValue());
final List<Attribute> attrs = SPACSUtils.getAttrs(assertion, needAttributeEncrypted, decryptionKeys);

final SPAttributeMapper attrMapper = SAML2Utils.getSPAttributeMapper(realm, spName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
* information: "Portions copyright [year] [name of copyright owner]".
*
* Copyright 2015-2016 ForgeRock AS.
* Portions copyright 2025 3A Systems LLC.
* Portions copyright 2025-2026 3A Systems LLC.
*/
package org.forgerock.openam.authentication.modules.saml2;

Expand Down Expand Up @@ -54,8 +54,11 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.xml.bind.JAXBElement;
import org.forgerock.guice.core.InjectorHolder;
import org.forgerock.openam.federation.saml2.SAML2TokenRepositoryException;
import org.forgerock.openam.saml2.SAML2Store;
Expand Down Expand Up @@ -178,7 +181,8 @@ private void setupSingleLogOut(SSOToken ssoToken, String metaAlias, String sessi
final String binding = SAML2Constants.HTTP_REDIRECT;
final IDPSSODescriptorElement idpsso = sm.getIDPSSODescriptor(realm, idpEntityId);

final List<EndpointType> slosList = idpsso.getSingleLogoutService();
final List<EndpointType> slosList = idpsso.getValue().getSingleLogoutService().stream()
.map(JAXBElement::getValue).collect(Collectors.toList());

EndpointType logoutEndpoint = null;
for (EndpointType endpoint : slosList) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
* $Id: CreateMetaDataTemplate.java,v 1.38 2009/10/29 00:03:50 exu Exp $
*
* Portions Copyrighted 2013-2016 ForgeRock AS.
* Portions Copyrighted 2026 3A Systems LLC.
*/
package com.sun.identity.federation.cli;

Expand Down Expand Up @@ -60,7 +61,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.bind.JAXBException;
import jakarta.xml.bind.JAXBException;

/**
* Create Meta Data Template.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

/**
* Portions Copyrighted 2013 ForgeRock AS
* Portions Copyrighted 2026 3A Systems LLC.
*/
package com.sun.identity.federation.cli;

Expand Down Expand Up @@ -63,7 +64,7 @@
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.MessageFormat;
import javax.xml.bind.JAXBException;
import jakarta.xml.bind.JAXBException;
import org.w3c.dom.Document;
import java.util.logging.Level;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
* $Id: ImportMetaData.java,v 1.15 2009/10/29 00:03:50 exu Exp $
*
* Portions Copyrighted 2012-2016 ForgeRock AS.
* Portions Copyrighted 2026 3A Systems LLC.
*/
package com.sun.identity.federation.cli;

Expand Down Expand Up @@ -62,7 +63,10 @@
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import javax.xml.bind.JAXBException;
import java.util.stream.Collectors;

import jakarta.xml.bind.JAXBElement;
import jakarta.xml.bind.JAXBException;

import org.forgerock.openam.utils.CollectionUtils;
import org.forgerock.openam.utils.StringUtils;
Expand Down Expand Up @@ -185,9 +189,10 @@ private void handleSAML2Request(RequestContext rc)
* see note at the end of this class for how we decide
* the realm value
*/
if (configElt != null && configElt.isHosted()) {
List<BaseConfigType> config = configElt.
getIDPSSOConfigOrSPSSOConfigOrAuthnAuthorityConfig();
if (configElt != null && configElt.getValue().isHosted()) {
List<BaseConfigType> config = configElt.getValue().
getIDPSSOConfigOrSPSSOConfigOrAuthnAuthorityConfig()
.stream().map(JAXBElement::getValue).collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(config)) {
realm = SAML2MetaUtils.getRealmByMetaAlias(config.get(0).getMetaAlias());
newMetaAliases = getMetaAliases(config);
Expand Down Expand Up @@ -256,18 +261,18 @@ private void handleIDFFRequest(RequestContext rc)
* see note at the end of this class for how we decide
* the realm value
*/
if ((configElt != null) && configElt.isHosted()) {
if ((configElt != null) && configElt.getValue().isHosted()) {
IDPDescriptorConfigElement idpConfig =
IDFFMetaUtils.getIDPDescriptorConfig(configElt);
if (idpConfig != null) {
realm = SAML2MetaUtils.getRealmByMetaAlias(
idpConfig.getMetaAlias());
idpConfig.getValue().getMetaAlias());
} else {
SPDescriptorConfigElement spConfig =
IDFFMetaUtils.getSPDescriptorConfig(configElt);
if (spConfig != null) {
realm = SAML2MetaUtils.getRealmByMetaAlias(
spConfig.getMetaAlias());
spConfig.getValue().getMetaAlias());
}
}
}
Expand Down Expand Up @@ -316,9 +321,10 @@ private void handleWSFedRequest(RequestContext rc)
* see note at the end of this class for how we decide
* the realm value
*/
if (configElt != null && configElt.isHosted()) {
if (configElt != null && configElt.getValue().isHosted()) {
List<com.sun.identity.wsfederation.jaxb.entityconfig.BaseConfigType> config =
configElt.getIDPSSOConfigOrSPSSOConfig();
configElt.getValue().getIDPSSOConfigOrSPSSOConfig()
.stream().map(JAXBElement::getValue).collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(config)) {
realm = WSFederationMetaUtils.getRealmByMetaAlias(config.get(0).getMetaAlias());
newMetaAliases = getMetaAliasesWsFed(config);
Expand Down Expand Up @@ -454,7 +460,7 @@ private String importIDFFMetaData(String realm, IDFFMetaManager metaManager)
descriptor =
(com.sun.identity.liberty.ws.meta.jaxb.EntityDescriptorElement)
obj;
entityID = descriptor.getProviderID();
entityID = descriptor.getValue().getProviderID();
//TODO: signature
//SAML2MetaSecurityUtils.verifySignature(doc);
//
Expand Down Expand Up @@ -514,14 +520,14 @@ private String importWSFedMetaData()
if (obj instanceof com.sun.identity.wsfederation.jaxb.wsfederation.FederationMetadataElement) {
// Just get the first element for now...
// TODO - loop through Federation elements?
obj = ((com.sun.identity.wsfederation.jaxb.wsfederation.FederationMetadataElement)obj).getAny().get(0);
obj = ((com.sun.identity.wsfederation.jaxb.wsfederation.FederationMetadataElement)obj).getValue().getAny().get(0);
}

if (obj instanceof com.sun.identity.wsfederation.jaxb.wsfederation.FederationElement) {
com.sun.identity.wsfederation.jaxb.wsfederation.FederationElement
federation =
(com.sun.identity.wsfederation.jaxb.wsfederation.FederationElement)obj;
federationID = federation.getFederationID();
federationID = federation.getValue().getFederationID();
if ( federationID == null )
{
federationID = WSFederationConstants.DEFAULT_FEDERATION_ID;
Expand Down
Loading
Loading