From 2a538ff9590b1df4d3f20cf638a0188a36f315c6 Mon Sep 17 00:00:00 2001 From: ayyoob Date: Mon, 16 Jan 2017 16:07:36 +0530 Subject: [PATCH] fixed multi tenant login issue --- .../APIManagementProviderServiceImpl.java | 19 + .../src/main/webapp/WEB-INF/web.xml | 4 + .../src/main/webapp/WEB-INF/web.xml | 4 + .../devicemgt/app/conf/config.json | 2 +- .../app/modules/oauth/token-handler-utils.js | 2 +- .../pom.xml | 23 +- .../ExtendedSAML2BearerGrantHandler.java | 570 ++++++++++++++++++ .../pom.xml | 4 - 8 files changed, 614 insertions(+), 14 deletions(-) create mode 100644 components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/src/main/java/org/wso2/carbon/device/mgt/oauth/extensions/handlers/grant/ExtendedSAML2BearerGrantHandler.java diff --git a/components/apimgt-extensions/org.wso2.carbon.apimgt.application.extension/src/main/java/org/wso2/carbon/apimgt/application/extension/APIManagementProviderServiceImpl.java b/components/apimgt-extensions/org.wso2.carbon.apimgt.application.extension/src/main/java/org/wso2/carbon/apimgt/application/extension/APIManagementProviderServiceImpl.java index 087a6ee4a2..daedca4271 100644 --- a/components/apimgt-extensions/org.wso2.carbon.apimgt.application.extension/src/main/java/org/wso2/carbon/apimgt/application/extension/APIManagementProviderServiceImpl.java +++ b/components/apimgt-extensions/org.wso2.carbon.apimgt.application.extension/src/main/java/org/wso2/carbon/apimgt/application/extension/APIManagementProviderServiceImpl.java @@ -30,6 +30,8 @@ import org.wso2.carbon.apimgt.application.extension.exception.APIManagerExceptio import org.wso2.carbon.apimgt.application.extension.util.APIManagerUtil; import org.wso2.carbon.apimgt.impl.APIConstants; import org.wso2.carbon.apimgt.impl.APIManagerFactory; +import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.user.api.UserStoreException; import org.wso2.carbon.utils.multitenancy.MultitenantConstants; import org.wso2.carbon.utils.multitenancy.MultitenantUtils; @@ -276,6 +278,23 @@ public class APIManagementProviderServiceImpl implements APIManagementProviderSe Set userVisibleAPIs = null; for (String tag : tags) { Set tagAPIs = apiConsumer.getAPIsWithTag(tag, APIManagerUtil.getTenantDomain()); + if (tagAPIs == null || tagAPIs.size() == 0) { + try { + PrivilegedCarbonContext.startTenantFlow(); + PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain( + MultitenantConstants.SUPER_TENANT_DOMAIN_NAME, true); + PrivilegedCarbonContext.getThreadLocalCarbonContext().setUsername( + PrivilegedCarbonContext.getThreadLocalCarbonContext().getUserRealm() + .getRealmConfiguration().getAdminUserName()); + APIConsumer anonymousConsumer = APIManagerFactory.getInstance().getAPIConsumer(username); + tagAPIs = anonymousConsumer.getAPIsWithTag(tag, MultitenantConstants.SUPER_TENANT_DOMAIN_NAME); + + } catch (UserStoreException e) { + log.error("failed to initialized super tenant flow", e); + } finally { + PrivilegedCarbonContext.endTenantFlow(); + } + } if (userVisibleAPIs == null) { userVisibleAPIs = tagAPIs; } else { diff --git a/components/certificate-mgt/org.wso2.carbon.certificate.mgt.api/src/main/webapp/WEB-INF/web.xml b/components/certificate-mgt/org.wso2.carbon.certificate.mgt.api/src/main/webapp/WEB-INF/web.xml index 62a814568e..58aa02917c 100644 --- a/components/certificate-mgt/org.wso2.carbon.certificate.mgt.api/src/main/webapp/WEB-INF/web.xml +++ b/components/certificate-mgt/org.wso2.carbon.certificate.mgt.api/src/main/webapp/WEB-INF/web.xml @@ -47,5 +47,9 @@ managed-api-owner admin + + isSharedWithAllTenants + true + diff --git a/components/certificate-mgt/org.wso2.carbon.certificate.mgt.cert.admin.api/src/main/webapp/WEB-INF/web.xml b/components/certificate-mgt/org.wso2.carbon.certificate.mgt.cert.admin.api/src/main/webapp/WEB-INF/web.xml index 72020e147e..1d59c04b17 100644 --- a/components/certificate-mgt/org.wso2.carbon.certificate.mgt.cert.admin.api/src/main/webapp/WEB-INF/web.xml +++ b/components/certificate-mgt/org.wso2.carbon.certificate.mgt.cert.admin.api/src/main/webapp/WEB-INF/web.xml @@ -52,6 +52,10 @@ managed-api-owner admin + + isSharedWithAllTenants + true + diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/conf/config.json b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/conf/config.json index 573d973b3a..d87ca3d0a7 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/conf/config.json +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/conf/config.json @@ -20,7 +20,7 @@ "owner": "admin@carbon.super", "dynamicClientAppRegistrationServiceURL": "%https.ip%/dynamic-client-web/register", "apiManagerClientAppRegistrationServiceURL": "%https.ip%/api-application-registration/register/tenants", - "grantType": "password refresh_token urn:ietf:params:oauth:grant-type:saml2-bearer urn:ietf:params:oauth:grant-type:jwt-bearer", + "grantType": "password refresh_token urn:ietf:params:oauth:grant-type:saml2-carbon urn:ietf:params:oauth:grant-type:jwt-bearer", "tokenScope": "admin", "callbackUrl": "%https.ip%/api/device-mgt/v1.0" }, diff --git a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/modules/oauth/token-handler-utils.js b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/modules/oauth/token-handler-utils.js index 81cdc1729b..c60cd9c80d 100644 --- a/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/modules/oauth/token-handler-utils.js +++ b/components/device-mgt/org.wso2.carbon.device.mgt.ui/src/main/resources/jaggeryapps/devicemgt/app/modules/oauth/token-handler-utils.js @@ -303,7 +303,7 @@ var utils = function () { // calling oauth provider token service endpoint var requestURL = deviceMgtProps["oauthProvider"]["tokenServiceURL"]; - var requestPayload = "grant_type=urn:ietf:params:oauth:grant-type:saml2-bearer&" + + var requestPayload = "grant_type=urn:ietf:params:oauth:grant-type:saml2-carbon&" + "assertion=" + encodeURIComponent(encodedAssertion) + "&scope=" + scopes; var xhr = new XMLHttpRequest(); diff --git a/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/pom.xml b/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/pom.xml index dc80a4ff55..2a01d519b0 100644 --- a/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/pom.xml +++ b/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/pom.xml @@ -100,24 +100,31 @@ org.wso2.carbon.identity.core.util, org.wso2.carbon.identity.oauth2.dto, org.wso2.carbon.identity.oauth2.token, - org.apache.oltu.oauth2.common.validators, org.wso2.carbon.utils, org.wso2.carbon.context, org.wso2.carbon.identity.oauth.cache, org.wso2.carbon.identity.oauth.config, org.wso2.carbon.identity.oauth2.dao, org.wso2.carbon.utils.multitenancy, - org.wso2.carbon.identity.oauth2.grant.jwt.*, - org.wso2.carbon.device.mgt.core.*, - org.wso2.carbon.apimgt.keymgt, org.wso2.carbon.apimgt.keymgt.handlers, - com.google.gson, org.apache.commons.codec.binary, org.wso2.carbon.identity.application.authentication.framework.model, - org.apache.oltu.oauth2.common, org.wso2.carbon.base, - org.apache.xerces.impl; resolution:=optional, - org.apache.xerces.util; resolution:=optional + org.apache.commons.collections, + org.apache.commons.lang, + org.joda.time, + org.opensaml.saml2.core, + org.opensaml.security, + org.opensaml.xml.*, + org.w3c.dom, + org.wso2.carbon.identity.application.common.util, + org.wso2.carbon.identity.base, + org.wso2.carbon.identity.oauth2.token.handlers.grant.*, + org.wso2.carbon.identity.oauth2.util, + org.wso2.carbon.idp.mgt, + org.opensaml.common.xml, + org.wso2.carbon.identity.oauth.common, + org.opensaml diff --git a/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/src/main/java/org/wso2/carbon/device/mgt/oauth/extensions/handlers/grant/ExtendedSAML2BearerGrantHandler.java b/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/src/main/java/org/wso2/carbon/device/mgt/oauth/extensions/handlers/grant/ExtendedSAML2BearerGrantHandler.java new file mode 100644 index 0000000000..67fbe41ba4 --- /dev/null +++ b/components/identity-extensions/org.wso2.carbon.device.mgt.oauth.extensions/src/main/java/org/wso2/carbon/device/mgt/oauth/extensions/handlers/grant/ExtendedSAML2BearerGrantHandler.java @@ -0,0 +1,570 @@ +/* + * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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 org.wso2.carbon.device.mgt.oauth.extensions.handlers.grant; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.joda.time.DateTime; +import org.opensaml.DefaultBootstrap; +import org.opensaml.common.xml.SAMLConstants; +import org.opensaml.saml2.core.Assertion; +import org.opensaml.saml2.core.Audience; +import org.opensaml.saml2.core.AudienceRestriction; +import org.opensaml.saml2.core.Conditions; +import org.opensaml.saml2.core.SubjectConfirmation; +import org.opensaml.security.SAMLSignatureProfileValidator; +import org.opensaml.xml.ConfigurationException; +import org.opensaml.xml.XMLObject; +import org.opensaml.xml.security.x509.X509Credential; +import org.opensaml.xml.signature.SignatureValidator; +import org.opensaml.xml.validation.ValidationException; +import org.w3c.dom.NodeList; +import org.wso2.carbon.base.MultitenantConstants; +import org.wso2.carbon.device.mgt.oauth.extensions.OAuthExtUtils; +import org.wso2.carbon.identity.application.authentication.framework.model.AuthenticatedUser; +import org.wso2.carbon.identity.application.common.model.FederatedAuthenticatorConfig; +import org.wso2.carbon.identity.application.common.model.IdentityProvider; +import org.wso2.carbon.identity.application.common.model.Property; +import org.wso2.carbon.identity.application.common.util.IdentityApplicationConstants; +import org.wso2.carbon.identity.application.common.util.IdentityApplicationManagementUtil; +import org.wso2.carbon.identity.base.IdentityConstants; +import org.wso2.carbon.identity.base.IdentityException; +import org.wso2.carbon.identity.core.util.IdentityUtil; +import org.wso2.carbon.identity.oauth.common.OAuthConstants; +import org.wso2.carbon.identity.oauth.config.OAuthServerConfiguration; +import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; +import org.wso2.carbon.identity.oauth2.model.RequestParameter; +import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext; +import org.wso2.carbon.identity.oauth2.token.handlers.grant.AbstractAuthorizationGrantHandler; +import org.wso2.carbon.identity.oauth2.token.handlers.grant.saml.SAML2TokenCallbackHandler; +import org.wso2.carbon.identity.oauth2.util.X509CredentialImpl; +import org.wso2.carbon.idp.mgt.IdentityProviderManagementException; +import org.wso2.carbon.idp.mgt.IdentityProviderManager; +import org.wso2.carbon.utils.multitenancy.MultitenantUtils; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This looks up for idp in supertenant space. + */ +@SuppressWarnings("unused") +public class ExtendedSAML2BearerGrantHandler extends AbstractAuthorizationGrantHandler { + private static Log log = LogFactory.getLog(ExtendedSAML2BearerGrantHandler.class); + SAMLSignatureProfileValidator profileValidator = null; + private static final String ASSERTION_IDENTIFIER = "assertion"; + + @Override + public void init() throws IdentityOAuth2Exception { + + super.init(); + + Thread thread = Thread.currentThread(); + ClassLoader loader = thread.getContextClassLoader(); + thread.setContextClassLoader(this.getClass().getClassLoader()); + + try { + DefaultBootstrap.bootstrap(); + } catch (ConfigurationException e) { + log.error("Error in bootstrapping the OpenSAML2 library", e); + throw new IdentityOAuth2Exception("Error in bootstrapping the OpenSAML2 library"); + } finally { + thread.setContextClassLoader(loader); + } + + profileValidator = new SAMLSignatureProfileValidator(); + } + + @Override + public boolean validateScope(OAuthTokenReqMessageContext tokReqMsgCtx) { + return OAuthExtUtils.setScopes(tokReqMsgCtx); + } + + /** + * We're validating the SAML token that we receive from the request. Through the assertion parameter is the POST + * request. A request format that we handle here looks like, + *

+ * POST /token.oauth2 HTTP/1.1 + * Host: as.example.com + * Content-Type: application/x-www-form-urlencoded + *

+ * grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Asaml2-bearer& + * assertion=PHNhbWxwOl...[omitted for brevity]...ZT4 + * + * @param tokReqMsgCtx Token message request context + * @return true if validation is successful, false otherwise + * @throws org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception + */ + @Override + public boolean validateGrant(OAuthTokenReqMessageContext tokReqMsgCtx) throws IdentityOAuth2Exception { + RequestParameter[] parameters = tokReqMsgCtx.getOauth2AccessTokenReqDTO().getRequestParameters(); + for (RequestParameter parameter : parameters) { + if (ASSERTION_IDENTIFIER.equals(parameter.getKey())) { + tokReqMsgCtx.getOauth2AccessTokenReqDTO().setAssertion(parameter.getValue()[0]); + } + } + + if(!super.validateGrant(tokReqMsgCtx)){ + return false; + } + + Assertion assertion = null; + IdentityProvider identityProvider = null; + String tokenEndpointAlias = null; + String tenantDomain = tokReqMsgCtx.getOauth2AccessTokenReqDTO().getTenantDomain(); + if (tenantDomain == null || "".equals(tenantDomain)) { + tenantDomain = MultitenantConstants.SUPER_TENANT_DOMAIN_NAME; + } + + // Logging the SAML token + if (log.isDebugEnabled() && IdentityUtil.isTokenLoggable(IdentityConstants.IdentityTokens.SAML_ASSERTION)) { + log.debug("Received SAML assertion : " + + new String(Base64.decodeBase64(tokReqMsgCtx.getOauth2AccessTokenReqDTO().getAssertion())) + ); + } + + try { + XMLObject samlObject = IdentityUtil.unmarshall(new String(Base64.decodeBase64( + tokReqMsgCtx.getOauth2AccessTokenReqDTO().getAssertion()))); + // Validating for multiple assertions + NodeList assertionList = samlObject.getDOM().getElementsByTagNameNS(SAMLConstants.SAML20_NS, "Assertion"); + if (assertionList.getLength() > 0) { + log.error("Invalid schema for SAML2 Assertion. Nested assertions detected."); + return false; + } + + if (samlObject instanceof Assertion) { + assertion = (Assertion) samlObject; + } else { + log.error("Only Assertion objects are validated in SAML2Bearer Grant Type"); + return false; + } + } catch (IdentityException e) { + // fault in the saml2 assertion + if(log.isDebugEnabled()){ + log.debug("Error while unmashalling the assertion", e); + } + return false; + } + + if (assertion == null) { + log.debug("Assertion is null, cannot continue"); + return false; + } + + /* + The Assertion MUST contain a element. The subject MAY identify the resource owner for whom + the access token is being requested. For client authentication, the Subject MUST be the "client_id" + of the OAuth client. When using an Assertion as an authorization grant, the Subject SHOULD identify + an authorized accessor for whom the access token is being requested (typically the resource owner, or + an authorized delegate). Additional information identifying the subject/principal of the transaction + MAY be included in an . + */ + if (assertion.getSubject() != null) { + String subjectIdentifier = assertion.getSubject().getNameID().getValue(); + if (StringUtils.isBlank(subjectIdentifier)) { + if (log.isDebugEnabled()){ + log.debug("NameID in Assertion cannot be empty"); + } + return false; + } + AuthenticatedUser user = + AuthenticatedUser.createFederateAuthenticatedUserFromSubjectIdentifier(subjectIdentifier); + user.setUserName(MultitenantUtils.getTenantAwareUsername(subjectIdentifier)); + // we take a tenant domain of the authorized user to be the tenant domain of the OAuth app + user.setTenantDomain(tenantDomain); + tokReqMsgCtx.setAuthorizedUser(user); + tenantDomain = MultitenantConstants.SUPER_TENANT_DOMAIN_NAME; + } else { + if (log.isDebugEnabled()) { + log.debug("Cannot find a Subject in the Assertion"); + } + return false; + } + + if (assertion.getIssuer() == null || "".equals(assertion.getIssuer().getValue())) { + if (log.isDebugEnabled()) { + log.debug("Issuer is empty in the SAML assertion"); + } + return false; + } else { + try { + identityProvider = IdentityProviderManager.getInstance(). + getIdPByAuthenticatorPropertyValue("IdPEntityId", + assertion.getIssuer().getValue(), tenantDomain, false); + // IF Federated IDP not found get the resident IDP and check, + // resident IDP entityID == issuer + if (identityProvider != null) { + if (IdentityApplicationConstants.RESIDENT_IDP_RESERVED_NAME.equals( + identityProvider.getIdentityProviderName())) { + identityProvider = IdentityProviderManager.getInstance().getResidentIdP(tenantDomain); + + FederatedAuthenticatorConfig[] fedAuthnConfigs = + identityProvider.getFederatedAuthenticatorConfigs(); + String idpEntityId = null; + + // Get SAML authenticator + FederatedAuthenticatorConfig samlAuthenticatorConfig = + IdentityApplicationManagementUtil.getFederatedAuthenticator(fedAuthnConfigs, + IdentityApplicationConstants.Authenticator.SAML2SSO.NAME); + // Get Entity ID from SAML authenticator + Property samlProperty = IdentityApplicationManagementUtil.getProperty( + samlAuthenticatorConfig.getProperties(), + IdentityApplicationConstants.Authenticator.SAML2SSO.IDP_ENTITY_ID); + if (samlProperty != null) { + idpEntityId = samlProperty.getValue(); + } + + if (idpEntityId == null || !assertion.getIssuer().getValue().equals(idpEntityId)) { + if(log.isDebugEnabled()) { + log.debug("SAML Token Issuer verification failed against resident Identity Provider " + + "in tenant : " + tenantDomain + ". Received : " + + assertion.getIssuer().getValue() + ", Expected : " + idpEntityId); + } + return false; + } + + // Get OpenIDConnect authenticator == OAuth + // authenticator + FederatedAuthenticatorConfig oauthAuthenticatorConfig = + IdentityApplicationManagementUtil.getFederatedAuthenticator(fedAuthnConfigs, + IdentityApplicationConstants.Authenticator.OIDC.NAME); + // Get OAuth token endpoint + Property oauthProperty = IdentityApplicationManagementUtil.getProperty( + oauthAuthenticatorConfig.getProperties(), + IdentityApplicationConstants.Authenticator.OIDC.OAUTH2_TOKEN_URL); + if (oauthProperty != null) { + tokenEndpointAlias = oauthProperty.getValue(); + } + } else { + // Get Alias from Federated IDP + tokenEndpointAlias = identityProvider.getAlias(); + } + } else { + if(log.isDebugEnabled()) { + log.debug("SAML Token Issuer : " + assertion.getIssuer().getValue() + + " not registered as a local Identity Provider in tenant : " + tenantDomain); + } + return false; + } + } catch (IdentityProviderManagementException e) { + throw new IdentityOAuth2Exception("Error while getting an Identity Provider for issuer value : " + + assertion.getIssuer().getValue(), e); + } + } + + /* + The Assertion MUST contain element with an element with an + element containing a URI reference that identifies the authorization server, or the service provider + SAML entity of its controlling domain, as an intended audience. The token endpoint URL of the + authorization server MAY be used as an acceptable value for an element. The authorization + server MUST verify that it is an intended audience for the Assertion. + */ + + if (StringUtils.isBlank(tokenEndpointAlias)) { + if (log.isDebugEnabled()){ + String errorMsg = "Token Endpoint alias has not been configured in the Identity Provider : " + + identityProvider.getIdentityProviderName() + " in tenant : " + tenantDomain; + log.debug(errorMsg); + } + return false; + } + + Conditions conditions = assertion.getConditions(); + if (conditions != null) { + //Set validity period extracted from SAML Assertion + long curTimeInMillis = Calendar.getInstance().getTimeInMillis(); + tokReqMsgCtx.setValidityPeriod(conditions.getNotOnOrAfter().getMillis() - curTimeInMillis); + List audienceRestrictions = conditions.getAudienceRestrictions(); + if (audienceRestrictions != null && !audienceRestrictions.isEmpty()) { + boolean audienceFound = false; + for (AudienceRestriction audienceRestriction : audienceRestrictions) { + if (CollectionUtils.isNotEmpty(audienceRestriction.getAudiences())) { + for (Audience audience : audienceRestriction.getAudiences()) { + if (audience.getAudienceURI().equals(tokenEndpointAlias)) { + audienceFound = true; + break; + } + } + } + if (audienceFound) { + break; + } + } + if (!audienceFound) { + if (log.isDebugEnabled()) { + log.debug("SAML Assertion Audience Restriction validation failed against the Audience : " + + tokenEndpointAlias + " of Identity Provider : " + + identityProvider.getIdentityProviderName() + " in tenant : " + tenantDomain); + } + return false; + } + } else { + if (log.isDebugEnabled()) { + String message = "SAML Assertion doesn't contain AudienceRestrictions"; + log.debug(message); + } + return false; + } + } else { + if (log.isDebugEnabled()) { + String message = "SAML Assertion doesn't contain Conditions"; + log.debug(message); + } + return false; + } + + + /* + The Assertion MUST have an expiry that limits the time window during which it can be used. The expiry + can be expressed either as the NotOnOrAfter attribute of the element or as the NotOnOrAfter + attribute of a suitable element. + */ + + /* + The element MUST contain at least one element that allows the + authorization server to confirm it as a Bearer Assertion. Such a element MUST + have a Method attribute with a value of "urn:oasis:names:tc:SAML:2.0:cm:bearer". The + element MUST contain a element, unless the Assertion + has a suitable NotOnOrAfter attribute on the element, in which case the + element MAY be omitted. When present, the element + MUST have a Recipient attribute with a value indicating the token endpoint URL of the authorization + server (or an acceptable alias). The authorization server MUST verify that the value of the Recipient + attribute matches the token endpoint URL (or an acceptable alias) to which the Assertion was delivered. + The element MUST have a NotOnOrAfter attribute that limits the window during + which the Assertion can be confirmed. The element MAY also contain an Address + attribute limiting the client address from which the Assertion can be delivered. Verification of the + Address is at the discretion of the authorization server. + */ + + DateTime notOnOrAfterFromConditions = null; + Map notOnOrAfterFromAndNotBeforeSubjectConfirmations = new HashMap<>(); + boolean bearerFound = false; + List recipientURLS = new ArrayList<>(); + + if (assertion.getConditions() != null && assertion.getConditions().getNotOnOrAfter() != null) { + notOnOrAfterFromConditions = assertion.getConditions().getNotOnOrAfter(); + } + + DateTime notBeforeConditions = null; + if (assertion.getConditions() != null && assertion.getConditions().getNotBefore() != null) { + notBeforeConditions = assertion.getConditions().getNotBefore(); + } + + List subjectConfirmations = assertion.getSubject().getSubjectConfirmations(); + if (subjectConfirmations != null && !subjectConfirmations.isEmpty()) { + for (SubjectConfirmation s : subjectConfirmations) { + if (s.getMethod() != null) { + if (s.getMethod().equals(OAuthConstants.OAUTH_SAML2_BEARER_METHOD)) { + bearerFound = true; + } + } else { + if (log.isDebugEnabled()){ + log.debug("Cannot find Method attribute in SubjectConfirmation " + s.toString()); + } + return false; + } + + if (s.getSubjectConfirmationData() != null) { + if (s.getSubjectConfirmationData().getRecipient() != null) { + recipientURLS.add(s.getSubjectConfirmationData().getRecipient()); + } + if (s.getSubjectConfirmationData().getNotOnOrAfter() != null || s.getSubjectConfirmationData() + .getNotBefore() != null) { + notOnOrAfterFromAndNotBeforeSubjectConfirmations.put(s.getSubjectConfirmationData().getNotOnOrAfter(), + s.getSubjectConfirmationData().getNotBefore()); + } else { + if (log.isDebugEnabled()){ + log.debug("Cannot find NotOnOrAfter and NotBefore attributes in " + + "SubjectConfirmationData " + + s.getSubjectConfirmationData().toString()); + } + } + } else { + if (s.getSubjectConfirmationData() == null && notOnOrAfterFromConditions == null) { + if (log.isDebugEnabled()){ + log.debug("Neither can find NotOnOrAfter attribute in Conditions nor SubjectConfirmationData" + + "in SubjectConfirmation " + s.toString()); + } + return false; + } + + if (s.getSubjectConfirmationData() == null && notBeforeConditions == null) { + if (log.isDebugEnabled()){ + log.debug("Neither can find NotBefore attribute in Conditions nor SubjectConfirmationData" + + "in SubjectConfirmation " + s.toString()); + } + return false; + } + } + } + } else { + if (log.isDebugEnabled()){ + log.debug("No SubjectConfirmation exist in Assertion"); + } + return false; + } + + if (!bearerFound) { + if (log.isDebugEnabled()){ + log.debug("Failed to find a SubjectConfirmation with a Method attribute having : " + + OAuthConstants.OAUTH_SAML2_BEARER_METHOD); + } + return false; + } + + if (CollectionUtils.isNotEmpty(recipientURLS) && !recipientURLS.contains(tokenEndpointAlias)) { + if (log.isDebugEnabled()){ + log.debug("None of the recipient URLs match against the token endpoint alias : " + tokenEndpointAlias + + " of Identity Provider " + identityProvider.getIdentityProviderName() + " in tenant : " + + tenantDomain); + } + return false; + } + + /* + The authorization server MUST verify that the NotOnOrAfter instant has not passed, subject to allowable + clock skew between systems. An invalid NotOnOrAfter instant on the element invalidates + the entire Assertion. An invalid NotOnOrAfter instant on a element only + invalidates the individual . The authorization server MAY reject Assertions with + a NotOnOrAfter instant that is unreasonably far in the future. The authorization server MAY ensure + that Bearer Assertions are not replayed, by maintaining the set of used ID values for the length of + time for which the Assertion would be considered valid based on the applicable NotOnOrAfter instant. + */ + if (notOnOrAfterFromConditions != null && notOnOrAfterFromConditions.compareTo(new DateTime()) < 1) { + // notOnOrAfter is an expired timestamp + if (log.isDebugEnabled()){ + log.debug("NotOnOrAfter is having an expired timestamp in Conditions element"); + } + return false; + } + + if (notBeforeConditions != null && notBeforeConditions.compareTo(new DateTime()) >= 1) { + // notBefore is an early timestamp + if (log.isDebugEnabled()){ + log.debug("NotBefore is having an early timestamp in Conditions element"); + } + return false; + } + + boolean validSubjectConfirmationDataExists = false; + if (!notOnOrAfterFromAndNotBeforeSubjectConfirmations.isEmpty()) { + for (Map.Entry entry : notOnOrAfterFromAndNotBeforeSubjectConfirmations.entrySet()) { + if (entry.getKey() != null) { + if (entry.getKey().compareTo(new DateTime()) >= 1) { + validSubjectConfirmationDataExists = true; + } + } + if (entry.getValue() != null) { + if (entry.getValue().compareTo(new DateTime()) < 1) { + validSubjectConfirmationDataExists = true; + } + } + } + } + + if (notOnOrAfterFromConditions == null && !validSubjectConfirmationDataExists) { + if (log.isDebugEnabled()){ + log.debug("No valid NotOnOrAfter element found in SubjectConfirmations"); + } + return false; + } + + if (notBeforeConditions == null && !validSubjectConfirmationDataExists) { + if (log.isDebugEnabled()){ + log.debug("No valid NotBefore element found in SubjectConfirmations"); + } + return false; + } + + /* + The Assertion MUST be digitally signed by the issuer and the authorization server MUST verify the + signature. + */ + + try { + profileValidator.validate(assertion.getSignature()); + } catch (ValidationException e) { + // Indicates signature did not conform to SAML Signature profile + log.error("Signature do not confirm to SAML signature profile.", e); + + return false; + } + + X509Certificate x509Certificate = null; + try { + x509Certificate = (X509Certificate) IdentityApplicationManagementUtil + .decodeCertificate(identityProvider.getCertificate()); + } catch (CertificateException e) { + throw new IdentityOAuth2Exception("Error occurred while decoding public certificate of Identity Provider " + + identityProvider.getIdentityProviderName() + " for tenant domain " + tenantDomain, e); + } + + try { + X509Credential x509Credential = new X509CredentialImpl(x509Certificate); + SignatureValidator signatureValidator = new SignatureValidator(x509Credential); + signatureValidator.validate(assertion.getSignature()); + if (log.isDebugEnabled()){ + log.debug("Signature validation successful"); + } + } catch (ValidationException e) { + log.error("Error while validating the signature.", e); + return false; + } + + + /* + The authorization server MUST verify that the Assertion is valid in all other respects per + [OASIS.saml-core-2.0-os], such as (but not limited to) evaluating all content within the Conditions + element including the NotOnOrAfter and NotBefore attributes, rejecting unknown condition types, etc. + + [OASIS.saml-core-2.0-os] - http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf + */ + + + // TODO: Throw the SAML request through the general SAML2 validation routines + + tokReqMsgCtx.setScope(tokReqMsgCtx.getOauth2AccessTokenReqDTO().getScope()); + + // Storing the Assertion. This will be used in OpenID Connect for example + tokReqMsgCtx.addProperty(OAuthConstants.OAUTH_SAML2_ASSERTION, assertion); + + // Invoking extension + SAML2TokenCallbackHandler callback = + OAuthServerConfiguration.getInstance() + .getSAML2TokenCallbackHandler(); + if (callback != null) { + if (log.isDebugEnabled()){ + log.debug("Invoking the SAML2 Token callback handler "); + } + callback.handleSAML2Token(tokReqMsgCtx); + } + + return true; + } + +} diff --git a/components/identity-extensions/org.wso2.carbon.identity.jwt.client.extension/pom.xml b/components/identity-extensions/org.wso2.carbon.identity.jwt.client.extension/pom.xml index b39b20638d..f5b348b6c9 100644 --- a/components/identity-extensions/org.wso2.carbon.identity.jwt.client.extension/pom.xml +++ b/components/identity-extensions/org.wso2.carbon.identity.jwt.client.extension/pom.xml @@ -133,7 +133,6 @@ org.osgi.framework, org.osgi.service.component, - org.wso2.carbon.governance.api.*, org.wso2.carbon.context, org.wso2.carbon.registry.core, org.wso2.carbon.registry.core.exceptions, @@ -141,18 +140,15 @@ org.wso2.carbon.utils, org.apache.commons.logging, org.wso2.carbon.registry.core.*;resolution:=optional, - org.wso2.carbon.registry.common.*;version="${carbon.registry.imp.pkg.version.range}", org.wso2.carbon.registry.indexing.*; version="${carbon.registry.imp.pkg.version.range}", com.nimbusds.jwt.*;version="${nimbus.orbit.version.range}", com.nimbusds.jose.*;version="${nimbus.orbit.version.range}", javax.net.ssl, org.apache.commons.codec.binary, - org.apache.commons.io, org.apache.http;version="${httpclient.version.range}", org.apache.http.client;version="${httpclient.version.range}", org.apache.http.message;version="${httpclient.version.range}", org.apache.http.client;version="${httpclient.version.range}", - org.apache.http.impl;version="${httpclient.version.range}", org.apache.http.conn.*;version="${httpclient.version.range}", org.apache.http.util;version="${httpclient.version.range}", org.apache.http.client.entity;version="${httpclient.version.range}",