RestSTSModelImpl.java revision 35579419d6433dcf5ed882de02c6eb1739749733
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmek/*
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmek * The contents of this file are subject to the terms of the Common Development and
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmek * Distribution License (the License). You may not use this file except in compliance with the
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmek * License.
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmek *
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmek * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmek * specific language governing permission and limitations under the License.
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmek *
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmek * When distributing Covered Software, include this CDDL Header Notice in each file and include
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmek * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmek * Header, with the fields enclosed by brackets [] replaced by your own identifying
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmek * information: "Portions Copyrighted [year] [name of copyright owner]".
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmek *
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmek * Copyright 2014 ForgeRock AS. All rights reserved.
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmek */
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmek
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekpackage com.sun.identity.console.reststs.model;
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmek
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekimport com.iplanet.sso.SSOException;
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekimport com.sun.identity.common.HttpURLConnectionManager;
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekimport com.sun.identity.console.base.model.AMAdminConstants;
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekimport com.sun.identity.console.base.model.AMConsoleException;
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekimport com.sun.identity.console.base.model.AMServiceProfileModelImpl;
07630cea1f3a845c09309f197ac7c4f11edd3b62Lennart Poetteringimport com.sun.identity.console.base.model.AMSystemConfig;
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekimport com.sun.identity.shared.Constants;
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekimport com.sun.identity.shared.configuration.SystemPropertiesManager;
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekimport com.sun.identity.shared.datastruct.CollectionHelper;
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekimport com.sun.identity.sm.SMSException;
7fd1b19bc9e9f5574f2877936b8ac267c7706947Harald Hoyerimport com.sun.identity.sm.ServiceConfig;
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekimport com.sun.identity.sm.ServiceConfigManager;
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekimport org.forgerock.json.fluent.JsonValue;
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekimport org.forgerock.openam.shared.sts.SharedSTSConstants;
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekimport org.forgerock.openam.utils.CollectionUtils;
787784c4c1b24a13207d18b415d60483cfbdeaa3Ronny Chevalierimport org.forgerock.openam.utils.IOUtils;
787784c4c1b24a13207d18b415d60483cfbdeaa3Ronny Chevalier
787784c4c1b24a13207d18b415d60483cfbdeaa3Ronny Chevalierimport javax.servlet.http.HttpServletRequest;
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekimport java.io.IOException;
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekimport java.io.InputStream;
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekimport java.io.OutputStreamWriter;
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekimport java.net.HttpURLConnection;
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekimport java.net.URL;
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekimport java.util.ArrayList;
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekimport java.util.Collections;
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekimport java.util.HashMap;
e88baee88fad8bc59d33b55a7a2d640ef9e16cd6Zbigniew Jędrzejewski-Szmekimport java.util.List;
import java.util.Map;
import java.util.Set;
import static org.forgerock.json.fluent.JsonValue.field;
import static org.forgerock.json.fluent.JsonValue.json;
import static org.forgerock.json.fluent.JsonValue.object;
/**
* @see com.sun.identity.console.reststs.model.RestSTSModel
* This class extends the AMServiceProfileModelImpl because this class provides functionality for reading values corresponding
* to propertySheets.
*
*/
public class RestSTSModelImpl extends AMServiceProfileModelImpl implements RestSTSModel {
private static final String COOKIE = "Cookie";
private static final String EQUALS = "=";
public RestSTSModelImpl(HttpServletRequest req, Map map) throws AMConsoleException {
super(req, AMAdminConstants.REST_STS_SERVICE, map);
}
public Set<String> getPublishedInstances(String realm) throws AMConsoleException {
try {
ServiceConfig baseService = new ServiceConfigManager(AMAdminConstants.REST_STS_SERVICE,
getUserSSOToken()).getOrganizationConfig(realm, null);
if (baseService != null) {
return baseService.getSubConfigNames();
} else {
return Collections.EMPTY_SET;
}
} catch (SMSException e) {
throw new AMConsoleException(e);
} catch (SSOException e) {
throw new AMConsoleException(e);
}
}
public void deleteInstances(Set<String> instanceNames) throws AMConsoleException {
for (String instanceName : instanceNames) {
try {
RestSTSModelResponse response = deleteInstance(instanceName);
if (!response.isSuccessful()) {
throw new AMConsoleException(response.getMessage());
}
} catch (IOException e) {
throw new AMConsoleException(e);
}
}
}
public RestSTSModelResponse createInstance(Map<String, Set<String>> configurationState, String realm) throws AMConsoleException {
addProgrammaticConfigurationState(configurationState, realm);
JsonValue invocationJson = createInstanceInvocationState(configurationState);
try {
return invokeRestSTSInstancePublish(invocationJson.toString());
} catch (IOException e) {
throw new AMConsoleException(e);
}
}
public RestSTSModelResponse updateInstance(Map<String, Set<String>> configurationState, String realm, String instanceName) throws AMConsoleException {
addProgrammaticConfigurationState(configurationState, realm);
JsonValue invocationJson = createInstanceInvocationState(configurationState);
try {
return invokeRestSTSInstanceUpdate(invocationJson.toString(), instanceName);
} catch (IOException e) {
throw new AMConsoleException(e);
}
}
public Map<String, Set<String>> getInstanceState(String realm, String instanceName) throws AMConsoleException {
try {
ServiceConfig baseService = new ServiceConfigManager(AMAdminConstants.REST_STS_SERVICE,
getUserSSOToken()).getOrganizationConfig(realm, null);
if (baseService != null) {
ServiceConfig serviceConfig = baseService.getSubConfig(instanceName);
if (serviceConfig != null) {
return serviceConfig.getAttributes();
} else {
return Collections.EMPTY_MAP;
}
} else {
return Collections.EMPTY_MAP;
}
} catch (SMSException e) {
throw new AMConsoleException(e);
} catch (SSOException e) {
throw new AMConsoleException(e);
}
}
public RestSTSModelResponse validateConfigurationState(Map<String, Set<String>> configurationState) {
if (isNullOrEmpty(configurationState.get(SharedSTSConstants.SAML2_TOKEN_LIFETIME))) {
return RestSTSModelResponse.failure(getLocalizedString("rest.sts.validation.token.lifetime.message"));
}
if (isNullOrEmpty(configurationState.get(SharedSTSConstants.DEPLOYMENT_URL_ELEMENT))) {
return RestSTSModelResponse.failure(getLocalizedString("rest.sts.validation.deployment.url.message"));
} else {
String urlElement = configurationState.get(SharedSTSConstants.DEPLOYMENT_URL_ELEMENT).iterator().next();
if (urlElement.contains(SharedSTSConstants.FORWARD_SLASH)) {
return RestSTSModelResponse.failure(getLocalizedString("rest.sts.validation.deployment.url.content.message"));
}
}
if (isNullOrEmpty(configurationState.get(SharedSTSConstants.ISSUER_NAME))) {
return RestSTSModelResponse.failure(getLocalizedString("rest.sts.validation.issuername.message"));
}
if (CollectionHelper.getBooleanMapAttr(configurationState, SharedSTSConstants.SAML2_SIGN_ASSERTION, false)
|| CollectionHelper.getBooleanMapAttr(configurationState, SharedSTSConstants.SAML2_ENCRYPT_ASSERTION, false)
|| CollectionHelper.getBooleanMapAttr(configurationState, SharedSTSConstants.SAML2_ENCRYPT_ATTRIBUTES, false)
|| CollectionHelper.getBooleanMapAttr(configurationState, SharedSTSConstants.SAML2_ENCRYPT_NAME_ID, false)) {
if (isNullOrEmpty(configurationState.get(SharedSTSConstants.SAML2_KEYSTORE_FILE_NAME))) {
return RestSTSModelResponse.failure(getLocalizedString("rest.sts.validation.saml2.keystore.filename.message"));
}
if (isNullOrEmpty(configurationState.get(SharedSTSConstants.SAML2_KEYSTORE_PASSWORD))) {
return RestSTSModelResponse.failure(getLocalizedString("rest.sts.validation.saml2.keystore.password.message"));
}
}
if (CollectionHelper.getBooleanMapAttr(configurationState, SharedSTSConstants.SAML2_SIGN_ASSERTION, false)) {
if (isNullOrEmpty(configurationState.get(SharedSTSConstants.SAML2_SIGNATURE_KEY_ALIAS))) {
return RestSTSModelResponse.failure(getLocalizedString("rest.sts.validation.saml2.keystore.signature.keyalias.message"));
}
if (isNullOrEmpty(configurationState.get(SharedSTSConstants.SAML2_SIGNATURE_KEY_PASSWORD))) {
return RestSTSModelResponse.failure(getLocalizedString("rest.sts.validation.saml2.keystore.signature.keypassword.message"));
}
}
if (CollectionHelper.getBooleanMapAttr(configurationState, SharedSTSConstants.SAML2_ENCRYPT_ASSERTION, false)
|| CollectionHelper.getBooleanMapAttr(configurationState, SharedSTSConstants.SAML2_ENCRYPT_ATTRIBUTES, false)
|| CollectionHelper.getBooleanMapAttr(configurationState, SharedSTSConstants.SAML2_ENCRYPT_NAME_ID, false)) {
if (isNullOrEmpty(configurationState.get(SharedSTSConstants.SAML2_ENCRYPTION_KEY_ALIAS))) {
return RestSTSModelResponse.failure(getLocalizedString("rest.sts.validation.saml2.keystore.encryption.keyalias.message"));
}
}
if (CollectionHelper.getBooleanMapAttr(configurationState, SharedSTSConstants.SAML2_ENCRYPT_ASSERTION, false)
&& (CollectionHelper.getBooleanMapAttr(configurationState, SharedSTSConstants.SAML2_ENCRYPT_ATTRIBUTES, false)
|| CollectionHelper.getBooleanMapAttr(configurationState, SharedSTSConstants.SAML2_ENCRYPT_NAME_ID, false))) {
return RestSTSModelResponse.failure(getLocalizedString("rest.sts.saml2.encryptioncombinations.message"));
}
if (isNullOrEmpty(configurationState.get(SharedSTSConstants.SUPPORTED_TOKEN_TRANSFORMS))) {
return RestSTSModelResponse.failure(getLocalizedString("rest.sts.validation.tokentransforms.message"));
}
return RestSTSModelResponse.success();
}
/*
Add the realm, as this information does not have to be solicited from the user. Also add the encryption strength
parameter, as this value is hard-coded based upon the encryption algorithm type, and necessary only if the
FMEncProvider is over-ridden. See comment in SAML2Config.SAML2ConfigBuilder#encryptionAlgorithmStrength for details.
*/
private void addProgrammaticConfigurationState(Map<String, Set<String>> configurationState, String realm) {
configurationState.put(SharedSTSConstants.DEPLOYMENT_REALM, CollectionUtils.asSet(realm));
final String encryptionAlgorithmStrength =
getEncryptionStrengthFromEncryptionAlgorithm(CollectionHelper.getMapAttr(configurationState, SharedSTSConstants.SAML2_ENCRYPTION_ALGORITHM));
configurationState.put(SharedSTSConstants.SAML2_ENCRYPTION_ALGORITHM_STRENGTH,
CollectionUtils.asSet(encryptionAlgorithmStrength));
}
private String getEncryptionStrengthFromEncryptionAlgorithm(String encryptionAlgorithm) {
if ("http://www.w3.org/2001/04/xmlenc#aes128-cbc".equals(encryptionAlgorithm)) {
return "128";
} else if ("http://www.w3.org/2001/04/xmlenc#aes192-cbc".equals(encryptionAlgorithm)) {
return "192";
} else if ("http://www.w3.org/2001/04/xmlenc#aes256-cbc".equals(encryptionAlgorithm)) {
return "256";
}
//safety case, should not be triggered because possible values specified in properties file
return "128";
}
private JsonValue createInstanceInvocationState(Map<String, Set<String>> configurationState) {
JsonValue propertiesMap = new JsonValue(marshalSetValuesToListValues(configurationState));
return json(object(
field(SharedSTSConstants.REST_STS_PUBLISH_INVOCATION_CONTEXT, SharedSTSConstants.REST_STS_PUBLISH_INVOCATION_CONTEXT_VIEW_BEAN),
field(SharedSTSConstants.REST_STS_PUBLISH_INSTANCE_STATE, propertiesMap)));
}
private String getSuccessMessage(HttpURLConnection connection) throws IOException {
return readInputStream(connection.getInputStream());
}
private String getErrorMessage(HttpURLConnection connection) throws IOException {
if (connection.getErrorStream() != null) {
return readInputStream(connection.getErrorStream());
} else {
return readInputStream(connection.getInputStream());
}
}
private String readInputStream(InputStream inputStream) throws IOException {
if (inputStream == null) {
return "Empty error stream";
} else {
return IOUtils.readStream(inputStream);
}
}
private String getRestSTSInstanceDeletionUrl(String instanceId) {
String processedInstanceId = instanceId;
if (processedInstanceId.startsWith(SharedSTSConstants.FORWARD_SLASH)) {
processedInstanceId = processedInstanceId.substring(1);
}
return getRestSTSPublishEndpointUrl() + SharedSTSConstants.FORWARD_SLASH + processedInstanceId;
}
private String getRestSTSInstanceUpdateUrl(String instanceId) {
return getRestSTSInstanceDeletionUrl(instanceId);
}
private String getRestSTSInstanceCreationUrl() {
return getRestSTSPublishEndpointUrl() + SharedSTSConstants.REST_PUBLISH_SERVICE_CREATE_ACTION_URL_ELEMENT;
}
private String getRestSTSPublishEndpointUrl() {
return getAMDeploymentUrl() + SharedSTSConstants.REST_PUBLISH_SERVICE_URL_ELEMENT;
}
private String getAMDeploymentUrl() {
return AMSystemConfig.serverURL + AMSystemConfig.serverDeploymentURI;
}
/*
Currently, JsonValue#toString will only create a json array for elements which are lists. If I want the
Map<String, Set<String>> returned by this.getValues() to marshal to json correctly using JsonValue#toString(), I
need to transform the Map<String, Set<String>> to a Map<String, List<String>>.
*/
private Map<String, List<String>> marshalSetValuesToListValues(Map<String, Set<String>> smsMap) {
Map<String, List<String>> listMap = new HashMap<String, List<String>>();
for (Map.Entry<String, Set<String>> entry : smsMap.entrySet()) {
List<String> list = new ArrayList<String>(entry.getValue().size());
list.addAll(entry.getValue());
listMap.put(entry.getKey(), list);
}
return listMap;
}
private boolean isNullOrEmpty(Set<String> set) {
return ((set == null) || set.isEmpty());
}
private RestSTSModelResponse deleteInstance(String instanceId) throws IOException {
return invokeRestSTSInstanceDeletion(getRestSTSInstanceDeletionUrl(instanceId));
}
private RestSTSModelResponse invokeRestSTSInstancePublish(String invocationPayload) throws IOException {
URL url = new URL(getRestSTSInstanceCreationUrl());
HttpURLConnection connection = HttpURLConnectionManager.getConnection(url);
connection.setDoOutput(true);
connection.setRequestMethod("POST");
connection.setRequestProperty(SharedSTSConstants.CONTENT_TYPE, SharedSTSConstants.APPLICATION_JSON);
connection.setRequestProperty(COOKIE, getAdminSessionTokenCookie());
OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream());
writer.write(invocationPayload);
writer.close();
if (connection.getResponseCode() == HttpURLConnection.HTTP_CREATED) {
return RestSTSModelResponse.success(getSuccessMessage(connection));
} else {
return RestSTSModelResponse.failure(getErrorMessage(connection));
}
}
private RestSTSModelResponse invokeRestSTSInstanceDeletion(String deletionUrl) throws IOException {
URL url = new URL(deletionUrl);
HttpURLConnection connection = HttpURLConnectionManager.getConnection(url);
connection.setDoOutput(true);
connection.setRequestMethod("DELETE");
connection.setRequestProperty(SharedSTSConstants.CONTENT_TYPE, SharedSTSConstants.APPLICATION_JSON);
connection.setRequestProperty(COOKIE, getAdminSessionTokenCookie());
connection.connect();
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
return RestSTSModelResponse.success(getSuccessMessage(connection));
} else {
return RestSTSModelResponse.failure(getErrorMessage(connection));
}
}
private RestSTSModelResponse invokeRestSTSInstanceUpdate(String invocationPayload, String instanceId) throws IOException {
URL url = new URL(getRestSTSInstanceUpdateUrl(instanceId));
HttpURLConnection connection = HttpURLConnectionManager.getConnection(url);
connection.setDoOutput(true);
connection.setRequestMethod("PUT");
connection.setRequestProperty(SharedSTSConstants.CONTENT_TYPE, SharedSTSConstants.APPLICATION_JSON);
connection.setRequestProperty(COOKIE, getAdminSessionTokenCookie());
OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream());
writer.write(invocationPayload);
writer.close();
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
return RestSTSModelResponse.success(getSuccessMessage(connection));
} else {
return RestSTSModelResponse.failure(getErrorMessage(connection));
}
}
private String getAdminSessionTokenCookie() {
return SystemPropertiesManager.get(Constants.AM_COOKIE_NAME) + EQUALS + getUserSSOToken().getTokenID().toString();
}
}