/**
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2016 ForgeRock AS. All Rights Reserved
*
* 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 Code, 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 Copyrighted [year] [name of copyright owner]"
*
*/
/**
* This class demonstrates consumption of rest-sts token transformations.
*/
public class RestSTSConsumer {
/**
*
* @param restSTSInstanceTranslateUrl The full url of the rest-sts instance, with the translate action specfied.
* For example, for a rest-sts instance with a deployment url of instanceId, published to the
* root realm, the url would be: http://amhost.com:8080/openam/rest-sts/instanceId?_action=translate
* A rest-sts instance published to realm fred with a deployment url of instanceId2, the
* A rest-sts instance published to realm bobo, a sub-realm of fred, with a deployment url of
* instanceId3, the url would be: http://amhost.com:8080/openam/rest-sts/fred/bobo/instanceId3?_action=translate
* @param restSTSInstanceValidateUrl The full url of the rest-sts instance, with the translate action specfied.
* For example, for a rest-sts instance with a deployment url of instanceId, published to the
* root realm, the url would be: http://amhost.com:8080/openam/rest-sts/instanceId?_action=validate
* A rest-sts instance published to realm fred with a deployment url of instanceId2, the
* A rest-sts instance published to realm bobo, a sub-realm of fred, with a deployment url of
* instanceId3, the url would be: http://amhost.com:8080/openam/rest-sts/fred/bobo/instanceId3?_action=validate
* @throws MalformedURLException In case the specified restSTSInstanceTranslateUrl is mal-formed.
*/
this.logger.log(Level.FINE, "RestSTSConsumer will consume the REST STS at url: " + this.restSTSInstanceTranslateUrl.toString());
}
/**
* Invokes a UsernameToken->SAML2 token transformation.
*
* Sample json posted at the rest-sts instance in this method:
*
{ "input_token_state": { "token_type": "USERNAME", "username": "unt_user1767572069", "password": "password" }, "output_token_state": { "token_type": "SAML2", "subject_confirmation": "BEARER" } }
{ "input_token_state": { "token_type": "USERNAME", "username": "unt_user1683257432", "password": "password" }, "output_token_state": { "token_type": "SAML2", "subject_confirmation": "HOLDER_OF_KEY", "proof_token_state": { "base64EncodedCertificate": "MIICQDCCAakCBEeNB0...wWigmrW0Y0Q==" } } }
{ "input_token_state": { "token_type": "USERNAME", "username": "unt_user1683257432", "password": "password" }, "output_token_state": { "token_type": "SAML2", "subject_confirmation": "SENDER_VOUCHES" } }
*
* @param username the username in the UsernameToken
* @param password the password in the UsernameToken
* @param subjectConfirmation The SAML2 SubjectConfirmation. For HoK, the certificate in the file /cert.jks on the
* classpath will be included.
* @return The string representation of the SAML2 Assertion
* @throws Exception If transformation fails
*/
throws IOException {
.build();
RestSTSTokenTranslationInvocationState invocationState = RestSTSTokenTranslationInvocationState.builder()
.build();
}
RestSTSTokenTranslationInvocationState invocationState = RestSTSTokenTranslationInvocationState.builder()
.build();
}
public String transformCustomTokenToCustomToken(JsonValue customTokenInput, JsonValue customTokenOutput) throws IOException {
RestSTSTokenTranslationInvocationState invocationState = RestSTSTokenTranslationInvocationState.builder()
.build();
}
public String transformUntToCustomToken(String username, String password, JsonValue customTokenOutput) throws IOException {
.build();
RestSTSTokenTranslationInvocationState invocationState = RestSTSTokenTranslationInvocationState.builder()
.build();
}
/*
example invocation state:
{ "input_token_state": { "token_type": "USERNAME", "username": "unt_user1767572069", "password": "password" }, "output_token_state": { "token_type": "OPENIDCONNECT", "nonce": "521828576", "allow_access": true } }
*/
.build();
RestSTSTokenTranslationInvocationState invocationState = RestSTSTokenTranslationInvocationState.builder()
.build();
}
/*
Transforming oidc->oidc does make sense - an oidc token issued by e.g. google can be transformed into an oidc token
intended for another audience. In this way, OpenAM can function as a 'meta' IdP - it can accept tokens from other
IdPs (google), authenticate them, and act as an idp creating a oidc token for another sp.
{ "input_token_state": { "token_type": "OPENIDCONNECT", "oidc_id_token": "eyAidHlwIjogIkpXVCIsICJhbGciOiAiSFMyNTYiIH0.eyAidG9rZW5OYW1AxNDQ4MDUzNjkz...pZW50IiBdIH0.YJFTbrlyAoZ3JP--zfcr8TwG_B0q6bPeUt0bBrx7bEw" }, "output_token_state": { "token_type": "OPENIDCONNECT", "nonce": "247885108", "allow_access": true } }
*/
OpenIdConnectTokenState oidcTokenState = OpenIdConnectTokenState.builder().tokenValue(oidcTokenValue).build();
RestSTSTokenTranslationInvocationState invocationState = RestSTSTokenTranslationInvocationState.builder()
.build();
}
/**
* Invokes a OpenAMToken->SAML2 token transformation.
*
* Sample json posted at the rest-sts instance in this method:
* { "input_token_state": { "token_type": "OPENAM", "session_id": "AQIC5wM...1MjYyAAJTMQAA*" }, "output_token_state": { "token_type": "SAML2", "subject_confirmation": "BEARER" } }
*
* { "input_token_state": { "token_type": "OPENAM", "session_id": "AQIC5...TQ1MjYyAAJTMQAA*" }, "output_token_state": { "token_type": "SAML2", "subject_confirmation": "HOLDER_OF_KEY", "proof_token_state": { "base64EncodedCertificate": "MIICQDCCAakCB...wWigmrW0Y0Q==" } } }
* { "input_token_state": { "token_type": "OPENAM", "session_id": "AQIC5...TMQAA*" }, "output_token_state": { "token_type": "SAML2", "subject_confirmation": "SENDER_VOUCHES" } }
*
* @param sessionId the OpenAM session ID. Corresponds to the iPlanetDirectoryPro (or equivalent) cookie.
* @param subjectConfirmation The SAML2 SubjectConfirmation. For HoK, the certificate in the file /cert.jks on the
* classpath will be included.
* @return The string representation of the SAML2 Assertion
* @throws Exception If transformation fails
*/
throws IOException {
RestSTSTokenTranslationInvocationState invocationState = RestSTSTokenTranslationInvocationState.builder()
.build();
}
/*
Example invocation state:
{ "input_token_state": { "token_type": "OPENAM", "session_id": "AQIC5wM2...TMQAA*" }, "output_token_state": { "token_type": "OPENIDCONNECT", "nonce": "471564333", "allow_access": true } }
*/
RestSTSTokenTranslationInvocationState invocationState = RestSTSTokenTranslationInvocationState.builder()
.build();
}
/**
* Invokes a OIDCToken->SAML2 token transformation
* Sample json posted at the rest-sts instance in this method (HoK SubjectConfirmation, with token elements truncated):
{ "input_token_state": { "token_type": "OPENIDCONNECT", "oidc_id_token": "eyAiYWxQ.euTNnNDExNTkyMjEyIH0.kuNlKwyvZJqaC8EYpDyPJMiEcII" },"output_token_state": { "token_type": "SAML2", "subject_confirmation": "HOLDER_OF_KEY", "proof_token_state": { "base64EncodedCertificate": "MIMbFAAOBjQAwgYkCgYEArSQ...c/U75GB2AtKhbGS5pimrW0Y0Q==" } } }
{ "input_token_state": { "token_type": "OPENIDCONNECT", "oidc_id_token": "eyAidHlwIjogIkpXVCIsICJhbGciOiAiSFMyNTYiIH0.eyAidG9rZW5OYW1lIjogImlkX3...gMTQ0ODA1MzY52xpZW50IiBdIH0.yKVp4kInTR-6TZGL3cjvA-adhbIfLqjf8E7ZQWHCm9c" }, "output_token_state": { "token_type": "SAML2", "subject_confirmation": "BEARER" } }
{ "input_token_state": { "token_type": "OPENIDCONNECT", "oidc_id_token": "eyAidHlwIjogIkpXVCIsICJhbGciOiAiSFMyNTYiIH0.eyAidG9rZW5O...Q2xpZW50IiBdIH0.yKVp4kInTR-6TZGL3cjvA-adhbIfLqjf8E7ZQWHCm9c" }, "output_token_state": { "token_type": "SAML2", "subject_confirmation": "SENDER_VOUCHES" } }
To set up oauth2, you have to:
1. configure the OAuth2 provider using the common tasks
2. then create a client using the agents tab - reflecting the client id and the redirect uri defined in my
integration test. Also add the openid scope
2.5. Make sure you specify a hmac-based based signing in the OAuth2 client.
3. then create the oidc module - the issuer should be the url of the openam deployment, with oauth2 appended (e.g.
http://macbook.dirk.internal.forgerock.com:8080/openam/oauth2), the type should be
client-secret, and the secret itself should be that configured for the oauth2 client - we are authN-ing a HMAC-signed jwt.
4. if the OpenAM provider is issuing the oidc token, then the azp and aud in the oidc module should be set to
the name of the oauth2 client
* @param oidcTokenValue the OpenIdConnect ID token. Note that the targeted rest-sts instance has to be deployed with
* a AuthTargetMapping which references an instance of the OIDC module with configuration state
* necessary to validate an OIDC token from a specific issuer.
* @param subjectConfirmation The SAML2 SubjectConfirmation. For HoK, the certificate in the file /cert.jks on the
* classpath will be included.
* @param hokProofCert The X509Certificate used as the HolderOfKey proof cert. Null if non-HolderOfKey SubjectConfirmation
* is specified.
* @return The string representation of the SAML2 Assertion
* @throws IOException If transformation fails
*/
public String transformOpenIdConnectToSAML2(SAML2SubjectConfirmation subjectConfirmation, String oidcTokenValue,
throws IOException {
if (oidcTokenValue == null) {
throw new IOException("OIDC token is null!");
}
OpenIdConnectTokenState tokenState = OpenIdConnectTokenState.builder().tokenValue(oidcTokenValue).build();
RestSTSTokenTranslationInvocationState invocationState = RestSTSTokenTranslationInvocationState.builder()
.build();
}
/**
* Invokes a X509->SAML2 token transformation
*
* Sample json posted at the rest-sts instance in this method:
* { "input_token_state": { "token_type": "X509" }, "output_token_state": { "token_type": "SAML2", "subject_confirmation": "SENDER_VOUCHES" } }
*
* { "input_token_state": { "token_type": "X509" }, "output_token_state": { "token_type": "SAML2", "subject_confirmation": "BEARER" } }
*
{ "input_token_state": { "token_type": "X509" }, "output_token_state": { "token_type": "SAML2", "subject_confirmation": "HOLDER_OF_KEY", "proof_token_state": { "base64EncodedCertificate": "MIICQDCCAakCBEeNB0swDQYJKoZIhvcNAQEEB...Fax0JDC/FfwWigmrW0Y0Q==" } } }
*
* Note that the caller's X509 token must be specified either 1. in a header specified in the publshed rest-sts instance and invoked
* from one of the trusted hosts, again specified in the published rest-sts instance, or 2. via two-way TLS.
* Note that the targeted rest-sts module has to be deployed with an AuthTargetMapping which reference an instance
* of the Certificate module configured to reference the client's certificate from the header specified in the
* AuthTargetMapping, and configured to trust the local OpenAM instance. In addition, the
* 'Certificate Field Used to Access User Profile' should be set to subject CN. The CN
* used in the test cert deployed with OpenAM, and used in this integration test, is 'test', so a subject with a uid of
* 'test' has to be created for account mapping to work.
* Likewise the published rest-sts instance
* must also be configured to trust the host running this test, and must be configured to reference the client's certificate
* in the header specified by stsClientCertHeaderName (unless the rest-sts is being consumed via two-way-tls, in which
* case the stsClientCertHeaderName is irrelevant, as the rest-sts will reference the client's certificate via the
* javax.servlet.request.X509Certificate ServletRequest attribute.
* @param subjectConfirmation The SAML2 SubjectConfirmation. For HoK, the certificate in the file /cert.jks on the
* classpath will be included.
* @param stsClientCertHeaderName The header name specification of where the published sts expects to find the client
* cert (must correspond to published sts state)
* @param clientCertificate the X509 Certificate that will be authenticated to establish the caller's identity
* @param hokProofCert For SAML2 assertions with HoK SubjectConfirmation, the holder's x509 certificate. Null for non-HoK
* SubjectConfirmations
* @return The string representation of the SAML2 Assertion
* @throws IOException If transformation fails
*/
public String transformX509ToSAML2(SAML2SubjectConfirmation subjectConfirmation, String stsClientCertHeaderName,
throws IOException {
RestSTSTokenTranslationInvocationState invocationState = RestSTSTokenTranslationInvocationState.builder()
.build();
return invokeTokenTranslation(invocationState.toJson().toString(), stsClientCertHeaderName, clientCertificate);
}
/*
Example invocation state:
* Note that the caller's X509 token must be specified either 1. in a header specified in the publshed rest-sts instance and invoked
* from one of the trusted hosts, again specified in the published rest-sts instance, or 2. via two-way TLS.
{ "input_token_state": { "token_type": "X509" }, "output_token_state": { "token_type": "OPENIDCONNECT", "nonce": "2003851386", "allow_access": true } }
*/
public String transformX509ToOIDC(String nonce, String stsClientCertHeaderName, X509Certificate clientCertificate)
throws IOException {
RestSTSTokenTranslationInvocationState invocationState = RestSTSTokenTranslationInvocationState.builder()
.build();
return invokeTokenTranslation(invocationState.toJson().toString(), stsClientCertHeaderName, clientCertificate);
}
private SAML2TokenCreationState buildSAML2TokenCreationState(SAML2SubjectConfirmation subjectConfirmation,
return SAML2TokenCreationState.builder()
.build();
} else {
return SAML2TokenCreationState.builder()
.build();
}
}
/*
Example invocation state:
Note: full token, as returned by transform operation, must be included in the invocation. Token details shortened below.
{ "validated_token_state": { "token_type": "OPENIDCONNECT", "oidc_id_token": "eyAidHlwIjogIkpXVCIsICJhbGciOiAiSFMyNTYiIH0.eyAiYXV0...LWE0ZmUtNDQ2YmZhODIzNGEyIiB9.TjhBCB9K64dHoDiBh7I5RWkJ6_y_EEjInuCBiD3F3tc" } }
{ "validated_token_state": { "token_type": "SAML2", "saml2_token": "<saml:Assertion xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" ...</saml:AuthnContext></saml:AuthnStatement></saml:Assertion>" } }
*/
.build();
.build();
} else {
throw new TokenValidationException(ResourceException.BAD_REQUEST, "Invalid integration test invocation: " +
}
return parseTokenValidationResponse(response);
}
/*
Example invocation state:
Note: full token, as returned by transform operation, must be included in the invocation. Token details shortened below.
{ "cancelled_token_state": { "token_type": "OPENIDCONNECT", "oidc_id_token": "eyAidHlwIjogIkpXVCIsICJhbGciOiAiSFMyNTYiIH0.eyAiYXV0...LWE0ZmUtNDQ2YmZhODIzNGEyIiB9.TjhBCB9K64dHoDiBh7I5RWkJ6_y_EEjInuCBiD3F3tc" } }
{ "cancelled_token_state": { "token_type": "SAML2", "saml2_token": ""<saml:Assertion xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" ...</saml:AuthnContext></saml:AuthnStatement></saml:Assertion>" } }
*/
.build();
.build();
} else {
throw new TokenCancellationException(ResourceException.BAD_REQUEST, "Invalid integration test invocation: " +
}
}
}
throws IOException {
}
private String invokeTokenTranslation(String invocationPayload, String stsClientCertHeaderName, X509Certificate userCertificate)
throws IOException {
logger.log(Level.FINE, "Invoking token translation with the following payload: " + invocationPayload);
connection.setDoOutput(true);
connection.setRequestProperty(SharedSTSConstants.CONTENT_TYPE, SharedSTSConstants.APPLICATION_JSON);
if (stsClientCertHeaderName != null) {
try {
connection.setRequestProperty(stsClientCertHeaderName, Base64.encode(userCertificate.getEncoded()));
} catch (CertificateEncodingException e) {
}
}
} else {
}
}
throws IOException {
logger.log(Level.FINE, "Invoking token validation on url " + restSTSInstanceValidateUrl + " with payload: " + invocationPayload);
connection.setDoOutput(true);
connection.setRequestProperty(SharedSTSConstants.CONTENT_TYPE, SharedSTSConstants.APPLICATION_JSON);
return getSuccessMessage(connection);
} else {
}
}
logger.log(Level.FINE, "Invoking token cancellation on url " + restSTSInstanceCancelUrl + " with payload: " + invocationPayload);
connection.setDoOutput(true);
connection.setRequestProperty(SharedSTSConstants.CONTENT_TYPE, SharedSTSConstants.APPLICATION_JSON);
return getSuccessMessage(connection);
} else {
}
}
}
} else {
}
}
if (inputStream == null) {
return "Empty error stream";
} else {
}
}
try {
} catch (IOException e) {
throw new TokenCreationException(500, "Could not map the response from the rest sts instance at url " + restSTSInstanceTranslateUrl +
}
throw new TokenCreationException(500, "The json response returned from the rest-sts instance at url " + restSTSInstanceTranslateUrl +
" did not have a non-null string element for the " + AMSTSConstants.ISSUED_TOKEN + " key. The json: "
+ responseContent.toString());
}
return assertionJson.asString();
}
try {
} catch (IOException e) {
"Could not map the response from the rest sts instance at url " + restSTSInstanceValidateUrl +
}
"The json response returned from the rest-sts instance at url " + restSTSInstanceValidateUrl +
" did not have a non-null string element for the " + AMSTSConstants.TOKEN_VALID + " key. The json: "
+ responseContent.toString());
}
return assertionJson.asBoolean();
}
}