Created
March 9, 2017 17:47
-
-
Save mefarazath/8b3d4fb7600024e9ffd85326263ff0e1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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