/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2012-2016 ForgeRock AS.
*
* 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
* http://forgerock.org/license/CDDLv1.0.html
* 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 http://forgerock.org/license/CDDLv1.0.html
* 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]".
*/
package com.sun.identity.workflow;
import static java.text.MessageFormat.format;
import static java.util.Collections.singleton;
import static org.forgerock.openam.entitlement.utils.EntitlementUtils.getApplicationService;
import static org.forgerock.openam.oauth2.OAuth2Constants.AuthorizationEndpoint.*;
import static org.forgerock.openam.oauth2.OAuth2Constants.OAuth2ProviderService.*;
import static org.forgerock.openam.utils.CollectionUtils.asSet;
import static org.forgerock.util.query.QueryFilter.equalTo;
import java.security.AccessController;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.UUID;
import javax.security.auth.Subject;
import org.forgerock.guava.common.collect.ImmutableMap;
import org.forgerock.guava.common.collect.ImmutableSet;
import org.forgerock.guice.core.InjectorHolder;
import org.forgerock.oauth2.core.AuthorizationCodeResponseTypeHandler;
import org.forgerock.oauth2.core.TokenResponseTypeHandler;
import org.forgerock.openam.entitlement.ResourceType;
import org.forgerock.openam.entitlement.conditions.subject.AuthenticatedUsers;
import org.forgerock.openam.entitlement.configuration.SmsAttribute;
import org.forgerock.openam.entitlement.rest.PolicyStore;
import org.forgerock.openam.entitlement.rest.PolicyStoreProvider;
import org.forgerock.openam.entitlement.rest.PrivilegePolicyStoreProvider;
import org.forgerock.openam.entitlement.rest.query.QueryAttribute;
import org.forgerock.openam.entitlement.service.ApplicationService;
import org.forgerock.openam.entitlement.service.DefaultPrivilegeManagerFactory;
import org.forgerock.openam.entitlement.service.PrivilegeManagerFactory;
import org.forgerock.openam.entitlement.service.ResourceTypeService;
import org.forgerock.openam.entitlement.utils.EntitlementUtils;
import org.forgerock.openam.oauth2.OAuth2Constants;
import org.forgerock.openam.utils.StringUtils;
import org.forgerock.openidconnect.IdTokenResponseTypeHandler;
import org.forgerock.util.query.QueryFilter;
import com.iplanet.sso.SSOException;
import com.iplanet.sso.SSOToken;
import com.sun.identity.common.ISLocaleContext;
import com.sun.identity.entitlement.Application;
import com.sun.identity.entitlement.ApplicationType;
import com.sun.identity.entitlement.ApplicationTypeManager;
import com.sun.identity.entitlement.DenyOverride;
import com.sun.identity.entitlement.Entitlement;
import com.sun.identity.entitlement.EntitlementException;
import com.sun.identity.entitlement.Privilege;
import com.sun.identity.entitlement.opensso.SubjectUtils;
import com.sun.identity.security.AdminTokenAction;
import com.sun.identity.shared.debug.Debug;
import com.sun.identity.sm.SMSException;
import com.sun.identity.sm.SMSUtils;
import com.sun.identity.sm.ServiceConfigManager;
import com.sun.identity.sm.ServiceSchema;
import com.sun.identity.sm.ServiceSchemaManager;
public class ConfigureOAuth2 extends Task {
private static final Map<String, String> OIDC_SCOPES = new ImmutableMap.Builder<String, String>()
.put("openid", "")
.put(OAuth2Constants.Scopes.EMAIL, "openidconnect.scopes.email")
.put(OAuth2Constants.Scopes.ADDRESS, "openidconnect.scopes.address")
.put(OAuth2Constants.Scopes.PHONE, "openidconnect.scopes.phone")
.put(OAuth2Constants.Scopes.PROFILE, "openidconnect.scopes.profile")
.build();
private static final Map<String, String> OIDC_CLAIMS = new ImmutableMap.Builder<String, String>()
.put("email", "openidconnect.claims.email")
.put("address", "openidconnect.claims.address")
.put("phone_number", "openidconnect.claims.phonenumber")
.put("given_name", "openidconnect.claims.givenname")
.put("zoneinfo", "openidconnect.claims.zoneinfo")
.put("family_name", "openidconnect.claims.familyname")
.put("locale", "openidconnect.claims.locale")
.put("name", "openidconnect.claims.name")
.put("profile", "openidconnect.scopes.profile")
.build();
private static final Set<String> OIDC_DEFAULT_SCOPES = asSet("openid", "profile", "email", "address", "phone");
private static final Map<String, Set<String>> COMMON_OIDC_UMA_ATTRIBUTES =
new ImmutableMap.Builder<String, Set<String>>()
.put(SUBJECT_TYPES_SUPPORTED, singleton("public"))
.put(ID_TOKEN_SIGNING_ALGORITHMS, asSet("HS256", "HS384", "HS512", "RS256", "ES256", "ES384",
"ES512"))
.put(RESPONSE_TYPE_LIST, asSet(
TOKEN + "|" + TokenResponseTypeHandler.class.getName(),
CODE + "|" + AuthorizationCodeResponseTypeHandler.class.getName(),
ID_TOKEN + "|" + IdTokenResponseTypeHandler.class.getName()
))
.build();
private static final Map<String, Set<String>> OIDC_ATTRIBUTES = new ImmutableMap.Builder<String, Set<String>>()
.putAll(COMMON_OIDC_UMA_ATTRIBUTES)
.put(DEFAULT_SCOPES, OIDC_DEFAULT_SCOPES)
.build();
private static final Map<String, Set<String>> MOBILE_CONNECT_ATTRIBUTES =
new ImmutableMap.Builder<String, Set<String>>()
.putAll(OIDC_ATTRIBUTES)
.put(CREATED_TIMESTAMP_ATTRIBUTE_NAME, singleton("createTimestamp"))
.put(MODIFIED_TIMESTAMP_ATTRIBUTE_NAME, singleton("modifyTimestamp"))
.build();
private static final Map<String, Set<String>> UMA_ATTRIBUTES =
new ImmutableMap.Builder<String, Set<String>>()
.putAll(COMMON_OIDC_UMA_ATTRIBUTES)
.put(DEFAULT_SCOPES, new ImmutableSet.Builder<String>()
.addAll(OIDC_DEFAULT_SCOPES).add("uma_protection").add("uma_authorization").build())
.build();
private static final Map<String, Map<String, Set<String>>> PROFILE_SETTINGS =
new ImmutableMap.Builder<String, Map<String, Set<String>>>()
.put("oauth2", Collections.<String, Set<String>>emptyMap())
.put("oidc", OIDC_ATTRIBUTES)
.put("mobileconnect", MOBILE_CONNECT_ATTRIBUTES)
.put("uma", UMA_ATTRIBUTES)
.build();
private static final Map<String, Map<String, String>> SUPPORTED_SCOPE_KEYS =
new ImmutableMap.Builder<String, Map<String, String>>()
.put("oauth2", Collections.<String, String>emptyMap())
.put("oidc", OIDC_SCOPES)
.put("mobileconnect", OIDC_SCOPES)
.put("uma", new ImmutableMap.Builder<String, String>()
.putAll(OIDC_SCOPES)
.put("uma_protection", "uma.scopes.umaprotection")
.put("uma_authorization", "uma.scopes.umaauthorization")
.build())
.build();
private static final Map<String, Map<String, String>> SUPPORTED_CLAIM_KEYS =
new ImmutableMap.Builder<String, Map<String, String>>()
.put("oauth2", Collections.<String, String>emptyMap())
.put("oidc", OIDC_CLAIMS)
.put("mobileconnect", OIDC_CLAIMS)
.put("uma", OIDC_CLAIMS)
.build();
private static final Debug DEBUG = Debug.getInstance("workflow");
private static final String OAUTH2_SERVICE_NAME = "OAuth2Provider";
private static final String UMA_SERVICE_NAME = "UmaProvider";
//params
private static final String TYPE = "type";
private static final String REALM = "realm";
private static final String ROOT = "/";
//service params
private static final String RTL = "rtl";
private static final String ACL = "acl";
private static final String ATL = "atl";
private static final String IRT = "irt";
private static final String IRTR = "irtr";
private static final String SIC = "sic";
//policy params
private static final String POLICY_NAME = "OAuth2ProviderPolicy";
private static final String POLICY_RESOURCE_TYPE_NAME = "OAuth2";
private static final String POLICY_APPLICATION_NAME = "OAuth2";
private static final String OAUTH2_AUTHORIZE_ENDPOINT = "/oauth2{0}/authorize?*";
public static final String MESSAGE = "oauth2.provider.configured";
public static final String POLICY_CREATED = "oauth2.provider.policy.created";
public static final String POLICY_EXISTS = "oauth2.provider.policy.exists";
public static final String UMA_SERVICE_CREATED = "oauth2.provider.created.uma";
private final PolicyStoreProvider storeProvider;
public ConfigureOAuth2(){
PrivilegeManagerFactory factory = new DefaultPrivilegeManagerFactory();
storeProvider = new PrivilegePolicyStoreProvider(factory, Collections.<String, QueryAttribute>emptyMap());
}
public String execute(Locale locale, Map params) throws WorkflowException {
final String type = getString(params, TYPE);
final String realm = getString(params, REALM);
final SSOToken token = AccessController.doPrivileged(AdminTokenAction.getInstance());
if (StringUtils.isEmpty(type)) {
throw new WorkflowException("type parameter is required");
}
//replace service attributes
final Map<String, Set<String>> attrValues = getDefaultOAuth2ProviderAttributes(token);
attrValues.putAll(PROFILE_SETTINGS.get(type));
attrValues.put(SUPPORTED_SCOPES, translate(realm, SUPPORTED_SCOPE_KEYS.get(type)));
attrValues.put(SUPPORTED_CLAIMS, translate(realm, SUPPORTED_CLAIM_KEYS.get(type)));
attrValues.put(REFRESH_TOKEN_LIFETIME_NAME, singleton(getString(params, RTL)));
attrValues.put(AUTHZ_CODE_LIFETIME_NAME, singleton(getString(params, ACL)));
attrValues.put(ACCESS_TOKEN_LIFETIME_NAME, singleton(getString(params, ATL)));
attrValues.put(ISSUE_REFRESH_TOKEN, singleton(getString(params, IRT)));
attrValues.put(ISSUE_REFRESH_TOKEN_ON_REFRESHING_TOKEN, singleton(getString(params, IRTR)));
attrValues.put(SCOPE_PLUGIN_CLASS, singleton(getString(params, SIC)));
createProvider(OAUTH2_SERVICE_NAME, token, realm, attrValues);
final boolean createUmaService = "uma".equals(type);
if (createUmaService) {
createProvider(UMA_SERVICE_NAME, token, realm, Collections.<String, Set<String>>emptyMap());
}
String policyURL = getRequestURL(params) + format(OAUTH2_AUTHORIZE_ENDPOINT, "/".equals(realm) ? "" : realm);
//check if policy exists
boolean createPolicy = false;
try {
Subject adminSubject = SubjectUtils.createSuperAdminSubject();
PolicyStore policyStore = storeProvider.getPolicyStore(adminSubject, realm);
try {
if (policyStore.read(POLICY_NAME) == null) {
createPolicy = true;
}
} catch (Exception e){
createPolicy = true;
}
if (createPolicy){
Privilege toStore = Privilege.getNewInstance();
Map<String, Boolean> actions = new HashMap<>();
actions.put("POST", true);
actions.put("GET", true);
Entitlement entitlement = new Entitlement();
entitlement.setActionValues(actions);
entitlement.setResourceName(policyURL);
entitlement.setApplicationName(POLICY_APPLICATION_NAME);
toStore.setResourceTypeUuid(getUrlResourceTypeId(adminSubject, realm));
toStore.setSubject(new AuthenticatedUsers());
toStore.setName(POLICY_NAME);
toStore.setEntitlement(entitlement);
policyStore.create(toStore);
}
} catch (EntitlementException e) {
DEBUG.error("ConfigureOAuth2.execute() : Unable to create policy", e);
throw new WorkflowException("oauth2.provider.policy.failed");
}
String messageTemplate = getMessage(MESSAGE, locale);
return format(messageTemplate, createUmaService ? getMessage(UMA_SERVICE_CREATED, locale) : "", realm,
format(getMessage(createPolicy ? POLICY_CREATED : POLICY_EXISTS, locale), POLICY_NAME));
}
private Set<String> translate(String realm, Map<String, String> values) {
Set<String> result = new HashSet<>();
ISLocaleContext localeContext = new ISLocaleContext(realm);
ResourceBundle bundle = ResourceBundle.getBundle("oauth2-default-user-descriptions", localeContext.getLocale());
for (Map.Entry<String, String> value : values.entrySet()) {
if (StringUtils.isNotEmpty(value.getValue())) {
result.add(value.getKey() + "|" + bundle.getString(value.getValue()));
} else {
result.add(value.getKey() + "|");
}
}
return result;
}
private String getUrlResourceTypeId(Subject adminSubject, String realm)
throws EntitlementException, WorkflowException {
ApplicationService applicationService = getApplicationService(adminSubject, realm);
Application application = applicationService.getApplication(POLICY_APPLICATION_NAME);
if (application == null) {
ApplicationType applicationType = ApplicationTypeManager.getAppplicationType(adminSubject,
ApplicationTypeManager.URL_APPLICATION_TYPE_NAME);
application = EntitlementUtils.newApplication(POLICY_APPLICATION_NAME, applicationType);
}
Set<String> resourceTypeIds = application.getResourceTypeUuids();
ResourceTypeService resourceTypeService = InjectorHolder.getInstance(ResourceTypeService.class);
for (String id : resourceTypeIds) {
ResourceType resourceType = resourceTypeService.getResourceType(adminSubject, realm, id);
if (POLICY_RESOURCE_TYPE_NAME.equalsIgnoreCase(resourceType.getName())) {
return id;
}
}
QueryFilter<SmsAttribute> name = equalTo(SmsAttribute.newSearchableInstance("name"), POLICY_RESOURCE_TYPE_NAME);
Set<ResourceType> types = resourceTypeService.getResourceTypes(name, adminSubject, realm);
ResourceType resourceType;
if (types == null || types.isEmpty()) {
resourceType = ResourceType.builder()
.addPatterns(asSet("*://*:*/*/authorize?*"))
.addActions(new ImmutableMap.Builder<String, Boolean>().put("GET", true).put("POST", true).build())
.setName(POLICY_RESOURCE_TYPE_NAME)
.setUUID(UUID.randomUUID().toString())
.build();
resourceType = resourceTypeService.saveResourceType(adminSubject, realm, resourceType);
} else {
resourceType = types.iterator().next();
}
application.addAllResourceTypeUuids(asSet(resourceType.getUUID()));
application.setEntitlementCombiner(DenyOverride.class);
application.setSubjects(EntitlementUtils.getSubjectsShortNames());
application.setConditions(EntitlementUtils.getConditionsShortNames());
applicationService.saveApplication(application);
return resourceType.getUUID();
}
private Map<String, Set<String>> getDefaultOAuth2ProviderAttributes(SSOToken token) throws WorkflowException {
try {
final ServiceSchema serviceSchema = new ServiceSchemaManager(OAUTH2_SERVICE_NAME, token).getOrganizationSchema();
return SMSUtils.removeValidators(serviceSchema.getReadOnlyAttributeDefaults(), serviceSchema);
} catch (SMSException e) {
DEBUG.error("An error occurred while trying to read the default OAuth2 Provider settings.", e);
throw new WorkflowException("oauth2.provider.read.error", null);
} catch (SSOException e) {
DEBUG.error("An error occurred while trying to read the default OAuth2 Provider settings.", e);
throw new WorkflowException("oauth2.provider.read.error", null);
}
}
private void createProvider(String serviceName, SSOToken token, String realm, Map<String, Set<String>> attrValues)
throws WorkflowException {
try {
new ServiceConfigManager(serviceName, token).createOrganizationConfig(realm, attrValues);
} catch (SMSException e) {
DEBUG.error("An error occurred while trying to create the OAuth2 Provider.", e);
throw new WorkflowException("oauth2.provider.create.error", null);
} catch (SSOException e) {
DEBUG.error("An error occurred while trying to create the OAuth2 Provider.", e);
throw new WorkflowException("oauth2.provider.create.error", null);
}
}
}