Skip to content

Instantly share code, notes, and snippets.

@mefarazath
Created March 9, 2017 17:47
Show Gist options
  • Save mefarazath/8b3d4fb7600024e9ffd85326263ff0e1 to your computer and use it in GitHub Desktop.
Save mefarazath/8b3d4fb7600024e9ffd85326263ff0e1 to your computer and use it in GitHub Desktop.
/*
* Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.west.ci.identity.custom.inbound.processor;
import com.west.ci.identity.custom.inbound.WestInboundConstants;
import com.west.ci.identity.custom.inbound.message.WestIdentityResponse.WestIdentityResponseBuilder;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.owasp.encoder.Encode;
import org.wso2.carbon.identity.application.authentication.framework.exception.FrameworkException;
import org.wso2.carbon.identity.application.authentication.framework.inbound.IdentityContextCache;
import org.wso2.carbon.identity.application.authentication.framework.inbound.IdentityMessageContext;
import org.wso2.carbon.identity.application.authentication.framework.inbound.IdentityProcessor;
import org.wso2.carbon.identity.application.authentication.framework.inbound.IdentityRequest;
import org.wso2.carbon.identity.application.authentication.framework.inbound.IdentityResponse;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkUtils;
import org.wso2.carbon.identity.application.common.IdentityApplicationManagementException;
import org.wso2.carbon.identity.application.common.model.InboundAuthenticationRequestConfig;
import org.wso2.carbon.identity.application.common.model.Property;
import org.wso2.carbon.identity.application.common.model.ServiceProvider;
import org.wso2.carbon.identity.application.mgt.ApplicationManagementService;
import org.wso2.carbon.identity.core.util.IdentityUtil;
import org.wso2.carbon.utils.CarbonUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.UUID;
import static com.west.ci.identity.custom.inbound.WestInboundConstants.APP_NAME_PARAM;
import static com.west.ci.identity.custom.inbound.WestInboundConstants.CALLBACK_PARAM;
import static com.west.ci.identity.custom.inbound.WestInboundConstants.IDENTITY_MESSAGE_CONTEXT_KEY_PARAM;
import static com.west.ci.identity.custom.inbound.WestInboundConstants.INBOUND_AUTH_KEY;
import static com.west.ci.identity.custom.inbound.WestInboundConstants.INBOUND_AUTH_TYPE_OAUTH2;
import static com.west.ci.identity.custom.inbound.WestInboundConstants.INBOUND_AUTH_TYPE_SAML;
import static com.west.ci.identity.custom.inbound.WestInboundConstants.INBOUND_CONFIG_PROTOCOL;
import static com.west.ci.identity.custom.inbound.WestInboundConstants.OIDC_SCOPE;
import static com.west.ci.identity.custom.inbound.WestInboundConstants.PROTOCOL_OIDC;
import static com.west.ci.identity.custom.inbound.WestInboundConstants.PROTOCOL_SAML;
import static com.west.ci.identity.custom.inbound.WestInboundConstants.SAML_RESPONSE_PARAM;
import static com.west.ci.identity.custom.inbound.WestInboundConstants.SAML_SP_ENTITY_ID;
import static com.west.ci.identity.custom.inbound.WestInboundConstants.WEST_CUSTOM_INBOUND;
import static java.nio.charset.StandardCharsets.UTF_8;
public class WestInboundRequestProcessor extends IdentityProcessor {
private static Log log = LogFactory.getLog(WestInboundRequestProcessor.class);
private String serviceProviderName;
@Override
public IdentityResponse.IdentityResponseBuilder process(IdentityRequest identityRequest) throws FrameworkException {
// for now we simply send to authentication framework
IdentityMessageContext<String, String> messageContext = new IdentityMessageContext<>(identityRequest);
WestIdentityResponseBuilder responseBuilder = new WestIdentityResponseBuilder(messageContext);
String tenantDomain = identityRequest.getTenantDomain();
// TODO : should we validate here?
String base64EncodedSAMLResponse = identityRequest.getParameter(SAML_RESPONSE_PARAM);
if (base64EncodedSAMLResponse == null) {
throw new FrameworkException("Cannot proceed without the SAML Response");
}
// using the relying party key of the custom inbound figure out the response protocol ie. SAML or OIDC
Map<String, Property> inboundAuthProperties =
getInboundAuthProperties(serviceProviderName, tenantDomain, WEST_CUSTOM_INBOUND);
Property protocolProperty = inboundAuthProperties.get(INBOUND_CONFIG_PROTOCOL);
String protocol = null;
if (protocolProperty != null) {
protocol = protocolProperty.getValue();
}
// here we are trying to figure out the response protocol based on which we will send the response to the app.
String inboundAuthType = getResponseProtocol(protocol);
if (StringUtils.isBlank(inboundAuthType)) {
String msg = "Cannot handle request, since configured response protocol '%s' is not supported.";
throw new FrameworkException(String.format(msg, protocol));
}
Map<String, Property> inboundAuthProp = getInboundAuthProperties(serviceProviderName, tenantDomain, inboundAuthType);
Property inboundAuthKeyProperty = inboundAuthProp.get(INBOUND_AUTH_KEY);
if (inboundAuthKeyProperty == null) {
String msg = "Cannot Proceed since unique identifier of '%s' inbound authenticator is missing.";
throw new FrameworkException(String.format(msg, inboundAuthType));
}
if (INBOUND_AUTH_TYPE_SAML.equalsIgnoreCase(inboundAuthType)) {
buildSAMLInboundRequest(base64EncodedSAMLResponse, inboundAuthKeyProperty, responseBuilder);
} else if (INBOUND_AUTH_TYPE_OAUTH2.equalsIgnoreCase(inboundAuthType)) {
String callbackUrl = identityRequest.getParameter(CALLBACK_PARAM);
String oidcScope = identityRequest.getParameter(OIDC_SCOPE);
buildOIDCInboundRequest(inboundAuthKeyProperty, callbackUrl, oidcScope, responseBuilder);
// we need to send the SAMLResponse from federated IDP to the framework. We are going to put it in cache
// and send the key
messageContext.addParameter(WestInboundConstants.SAML_RESPONSE_PARAM, base64EncodedSAMLResponse);
String samlResponseCacheDataKey = UUID.randomUUID().toString();
IdentityContextCache.getInstance().addToCache(samlResponseCacheDataKey, messageContext);
// add cache key as a parameter to send to framework
responseBuilder.addParameter(IDENTITY_MESSAGE_CONTEXT_KEY_PARAM, samlResponseCacheDataKey);
}
return responseBuilder;
}
@Override
public String getCallbackPath(IdentityMessageContext identityMessageContext) {
return IdentityUtil.getServerURL("identity", false, false);
}
@Override
public String getRelyingPartyId() {
return serviceProviderName;
}
@Override
public String getRelyingPartyId(IdentityMessageContext identityMessageContext) {
return serviceProviderName;
}
@Override
public boolean canHandle(IdentityRequest identityRequest) {
serviceProviderName = identityRequest.getParameter(APP_NAME_PARAM);
return serviceProviderName != null;
}
private Map<String, Property> getInboundAuthProperties(String serviceProviderName,
String tenantDomain,
String inboundAuthType) {
try {
ApplicationManagementService applicationMgtService = ApplicationManagementService.getInstance();
ServiceProvider application = applicationMgtService.getServiceProvider(serviceProviderName, tenantDomain);
Map<String, Property> properties = new HashMap<>();
for (InboundAuthenticationRequestConfig authenticationRequestConfig : application
.getInboundAuthenticationConfig().getInboundAuthenticationRequestConfigs()) {
if (StringUtils.equals(authenticationRequestConfig.getInboundAuthType(), inboundAuthType)) {
// add all the inbound auth properties
for (Property property : authenticationRequestConfig.getProperties()) {
properties.put(property.getName(), property);
}
// add inbound auth key as a property
Property inboundAuthKey = new Property();
inboundAuthKey.setName(INBOUND_AUTH_KEY);
inboundAuthKey.setValue(authenticationRequestConfig.getInboundAuthKey());
properties.put(INBOUND_AUTH_KEY, inboundAuthKey);
}
}
return properties;
} catch (IdentityApplicationManagementException e) {
throw new RuntimeException("Error while reading inbound authenticator properties");
}
}
private String getResponseProtocol(String protocol) {
if (PROTOCOL_SAML.equalsIgnoreCase(protocol)) {
return INBOUND_AUTH_TYPE_SAML;
} else if (PROTOCOL_OIDC.equalsIgnoreCase(protocol)) {
return INBOUND_AUTH_TYPE_OAUTH2;
} else {
// this means either the protocol value was null or not supported
log.error("Protocol '" + protocol + "' is not recognized or supported.");
return null;
}
}
private String createAutoPostForm(String acsUrl, String samlReponse) {
String redirectHtmlPath = Paths.get(CarbonUtils.getCarbonHome(), "repository", "resources", "identity",
"pages", "samlsso_response.html").toAbsolutePath().toString();
FileInputStream fis = null;
String pageWithAcsResponse = null;
try {
fis = new FileInputStream(new File(redirectHtmlPath));
String ssoRedirectPage = new Scanner(fis, "UTF-8").useDelimiter("\\A").next();
String pageWithAcs = ssoRedirectPage.replace("$acUrl", acsUrl);
pageWithAcsResponse = pageWithAcs.replace("<!--$params-->", "<!--$params-->\n" + "<input type='hidden'" +
" name='SAMLResponse' value='" + Encode.forHtmlAttribute(samlReponse) + "'>");
} catch (FileNotFoundException e) {
log.error(e);
}
return pageWithAcsResponse;
}
private void buildSAMLInboundRequest(String base64EncodedSAMLResponse,
Property inboundAuthKeyProperty,
WestIdentityResponseBuilder responseBuilder) {
String samlIssuer = inboundAuthKeyProperty.getValue();
String samlssoUrl = IdentityUtil.getServerURL("samlsso", false, false);
String queryParamString = null;
try {
queryParamString = SAML_SP_ENTITY_ID + "=" + URLEncoder.encode(samlIssuer, UTF_8.name());
} catch (UnsupportedEncodingException e) {
log.error(e);
}
samlssoUrl = FrameworkUtils.appendQueryParamsStringToUrl(samlssoUrl, queryParamString);
responseBuilder.setBody(createAutoPostForm(samlssoUrl, base64EncodedSAMLResponse));
responseBuilder.setStatus(200);
}
private void buildOIDCInboundRequest(Property inboundAuthKeyProperty,
String callbackUrl,
String scope,
WestIdentityResponseBuilder responseBuilder) throws FrameworkException {
String clientID = inboundAuthKeyProperty.getValue();
responseBuilder.setRedirectUrl(IdentityUtil.getServerURL("oauth2/authorize", false, false));
// OIDC authorization request parameters.
responseBuilder.addParameter("response_type", "code");
responseBuilder.addParameter("client_id", clientID);
if (StringUtils.isBlank(callbackUrl)) {
throw new FrameworkException("Callback parameter cannot not be empty/null.");
}
responseBuilder.addParameter("redirect_uri", callbackUrl);
// if scope is not sent from federated idp, we go for the default openid scope.
if (StringUtils.isBlank(scope)) {
scope = "openid";
}
responseBuilder.addParameter("scope", scope);
responseBuilder.addParameter("state", UUID.randomUUID().toString());
responseBuilder.setStatus(302);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment