From ecda000395511e0c49f1ac53a28a439635ad2899 Mon Sep 17 00:00:00 2001 From: ayyoob Date: Sat, 4 Feb 2017 20:52:24 +0530 Subject: [PATCH] used jwt grant type instead of saml --- .../distribution/src/assembly/bin.xml | 21 +- .../src/repository/conf/etc/jwt.properties | 57 -- .../modules/oauth/token-handler-utils.js | 598 ++++++++++++++++++ .../portal/modules/oauth/token-handlers.js | 192 ++++++ .../distribution/src/ues/designer.json | 7 +- .../distribution/identity_config_change.xml | 2 +- modules/core/distribution/pom.xml | 2 +- .../core/distribution/src/assembly/bin.xml | 32 +- .../src/repository/conf/api-manager.xml | 6 +- .../src/repository/conf/cdm-config.xml | 63 -- .../modules/oauth/token-handler-utils.js | 598 ++++++++++++++++++ .../portal/modules/oauth/token-handlers.js | 192 ++++++ .../modules/sso/scripts/sso.client.js | 5 +- modules/core/p2-profile-gen/pom.xml | 31 + pom.xml | 4 +- 15 files changed, 1661 insertions(+), 149 deletions(-) delete mode 100644 modules/analytics/distribution/src/repository/conf/etc/jwt.properties create mode 100644 modules/analytics/distribution/src/repository/jaggeryapps/portal/modules/oauth/token-handler-utils.js create mode 100644 modules/analytics/distribution/src/repository/jaggeryapps/portal/modules/oauth/token-handlers.js delete mode 100644 modules/core/distribution/src/repository/conf/cdm-config.xml create mode 100644 modules/core/distribution/src/repository/jaggeryapps/portal/modules/oauth/token-handler-utils.js create mode 100644 modules/core/distribution/src/repository/jaggeryapps/portal/modules/oauth/token-handlers.js diff --git a/modules/analytics/distribution/src/assembly/bin.xml b/modules/analytics/distribution/src/assembly/bin.xml index 71f18629..55cda758 100644 --- a/modules/analytics/distribution/src/assembly/bin.xml +++ b/modules/analytics/distribution/src/assembly/bin.xml @@ -47,6 +47,8 @@ **/repository/conf/security/cipher-text.properties **/repository/conf/security/Owasp.CsrfGuard.Carbon.properties **/repository/conf/security/cipher-tool.properties + **/repository/deployment/server/jaggeryapps/portal/modules/oauth/plugins/token-handler-utils.js + **/repository/deployment/server/jaggeryapps/portal/modules/oauth/plugins/token-handlers.js @@ -723,10 +725,6 @@ src/repository/conf/security/Owasp.CsrfGuard.Carbon.properties ${pom.artifactId}-${pom.version}/repository/conf/security - - src/repository/conf/etc/jwt.properties - ${pom.artifactId}-${pom.version}/repository/conf/etc - src/repository/conf/analytics/spark/spark-udf-config.xml ${pom.artifactId}-${pom.version}/repository/conf/analytics/spark @@ -798,6 +796,21 @@ true 644 + + src/repository/jaggeryapps/portal/modules/oauth/token-handler-utils.js + + ${pom.artifactId}-${pom.version}/repository/deployment/server/jaggeryapps/portal/modules/oauth + + 755 + + + src/repository/jaggeryapps/portal/modules/oauth/token-handlers.js + + ${pom.artifactId}-${pom.version}/repository/deployment/server/jaggeryapps/portal/modules/oauth + + 755 + + diff --git a/modules/analytics/distribution/src/repository/conf/etc/jwt.properties b/modules/analytics/distribution/src/repository/conf/etc/jwt.properties deleted file mode 100644 index 3c384655..00000000 --- a/modules/analytics/distribution/src/repository/conf/etc/jwt.properties +++ /dev/null @@ -1,57 +0,0 @@ -# -# 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. -# - -#issuer of the JWT -iss=wso2.org/products/iot - -TokenEndpoint=https://${iot.keymanager.host}:${iot.keymanager.https.port}/oauth2/token - -#audience of JWT claim -#comma seperated values -aud=devicemgt - -#expiration time of JWT (number of minutes from the current time) -exp=1000 - -#issued at time of JWT (number of minutes from the current time) -iat=0 - -#nbf time of JWT (number of minutes from current time) -nbf=0 - -#skew between IDP and issuer(seconds) -skew=0 - -# JWT Id -#jti=token123 - -#KeyStore to cryptographic credentials -#KeyStore=repository/resources/security/wso2carbon.jks - -#Password of the KeyStore -#KeyStorePassword=wso2carbon - -#Alias of the SP's private key -#PrivateKeyAlias=wso2carbon - -#Private key password to retrieve the private key used to sign -#AuthnRequest and LogoutRequest messages -#PrivateKeyPassword=wso2carbon - -#this will be used as the default IDP config if there isn't any config available for tenants. -default-jwt-client=true diff --git a/modules/analytics/distribution/src/repository/jaggeryapps/portal/modules/oauth/token-handler-utils.js b/modules/analytics/distribution/src/repository/jaggeryapps/portal/modules/oauth/token-handler-utils.js new file mode 100644 index 00000000..c6a8aab5 --- /dev/null +++ b/modules/analytics/distribution/src/repository/jaggeryapps/portal/modules/oauth/token-handler-utils.js @@ -0,0 +1,598 @@ +/* + * 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. + */ + +var utils = function () { + var log = new Log("/modules/oauth/token-handler-utils.js"); + + var configs = require('/configs/portal.js').config(); + var constants = require("/modules/constants.js"); + var carbon = require("carbon"); + + //noinspection JSUnresolvedVariable + var Base64 = Packages.org.apache.commons.codec.binary.Base64; + //noinspection JSUnresolvedVariable + var String = Packages.java.lang.String; + + var publicMethods = {}; + var privateMethods = {}; + + publicMethods["encode"] = function (payload) { + return String(Base64.encodeBase64(String(payload).getBytes())); + }; + + publicMethods["decode"] = function (payload) { + return String(Base64.decodeBase64(String(payload).getBytes())); + }; + + /** + * Check whether this application is oauth enable or not + * @returns boolean if oauth enable + */ + publicMethods["checkOAuthEnabled"] = function () { + if (constants.AUTHORIZATION_TYPE_OAUTH === configs["authorization"]["activeMethod"]) { + return true; + } + return false; + }; + + /** + * Set access token into xml http request header + * @param xhr xml http request + * @returns {*} xhr which has access token it's header + */ + publicMethods["setAccessToken"] = function (xhr, callback) { + var accessToken; + if (publicMethods.checkOAuthEnabled()) { + try { + accessToken = parse(session.get(constants.ACCESS_TOKEN_PAIR_IDENTIFIER_FOR_PORTAL))["accessToken"]; + xhr.setRequestHeader(constants.AUTHORIZATION_HEADER, constants.BEARER_PREFIX + accessToken); + } catch (exception) { + log.error("Access token hasn't been set yet, " + exception); + } finally { + callback(xhr); + } + } + callback(xhr); + }; + + /** + * Get access token of current logged user + * @param callBack response with access token + */ + publicMethods["getAccessToken"] = function (callBack) { + var accessToken = null; + if (publicMethods.checkOAuthEnabled()) { + try { + accessToken = parse(session.get(constants.ACCESS_TOKEN_PAIR_IDENTIFIER_FOR_PORTAL))["accessToken"]; + } catch (exception) { + log.error("Access token hasn't been set yet, " + exception); + } finally { + callBack(accessToken); + } + } + callBack(accessToken); + }; + + /** + * Create error message which adhere to xml http response object + * @param statusCode response status code + * @param status response status + * @param responseText response message + * @returns {{statusCode: *, status: *, responseText: *}} + */ + publicMethods["createXHRObject"] = function (statusCode, status, responseText) { + return {"statusCode": statusCode, "status": status, "responseText": responseText}; + }; + + /** + * check whether user already logged to system before invoking any apis + * @param callBack + */ + publicMethods["isUserAuthorized"] = function (callBack) { + if (session.get("Loged") !== constants.LOGIN_MESSAGE) { + callBack(false); + } else { + callBack(true); + } + }; + + /** + * Get identity provider uir + * @returns {*} + */ + publicMethods["getIdPServerURL"] = function () { + return configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"]["tokenServiceURL"]; + }; + + /** + * Get an Access token pair based on client secret + * @param encodedClientKeys {{clientId:"", clientSecret:""}} + * @param scope eg: PRODUCTION + * @param idPServer identity provider url + * @returns {{accessToken: *, refreshToken: *}} + */ + publicMethods["getTokenWithClientSecretType"] = function (encodedClientKeys, scope, idPServer) { + var xhr = new XMLHttpRequest(); + var tokenEndpoint = idPServer; + xhr.open(constants.HTTP_POST, tokenEndpoint, false); + xhr.setRequestHeader(constants.CONTENT_TYPE_IDENTIFIER, constants.APPLICATION_X_WWW_FOR_URLENCODED); + xhr.setRequestHeader(constants.AUTHORIZATION_HEADER, constants.BASIC_PREFIX + encodedClientKeys); + xhr.send("grant_type=client_credentials&scope=" + scope); + var tokenPair = {}; + if (xhr.status == constants.HTTP_ACCEPTED) { + var data = parse(xhr.responseText); + tokenPair.refreshToken = data.refresh_token; + tokenPair.accessToken = data.access_token; + } else if (xhr.status == constants.HTTP_USER_NOT_AUTHENTICATED) { + log.error("Error in obtaining token with client secret grant type, You are not authenticated yet"); + return null; + } else { + log.error("Error in obtaining token with client secret grant type, This might be a problem with client meta " + + "data which required for client secret grant type"); + return null; + } + return tokenPair; + }; + + + /** + * This will create client id and client secret for a given application + * @param properties "callbackUrl": "", + * "clientName": "", + * "owner": "", + * "applicationType": "", + * "grantType": "", + * "saasApp" :"", + * "dynamicClientRegistrationEndPoint" : "" + * + * @returns {{clientId:*, clientSecret:*}} + */ + publicMethods["getDynamicClientAppCredentials"] = function (username) { + // setting up dynamic client application properties + var dcAppProperties = { + "applicationType": configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"]["appRegistration"]["appType"], + "clientName": configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"]["appRegistration"]["clientName"], + "owner": configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"]["appRegistration"]["owner"], + "tokenScope": configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"]["appRegistration"]["tokenScope"], + "grantType": configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"]["appRegistration"]["grantType"], + "callbackUrl": configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"]["appRegistration"]["callbackUrl"], + "saasApp" : configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"]["appRegistration"]["saasApp"] + }; + + var tenantDomain = carbon.server.tenantDomain({username: username}); + if (!tenantDomain) { + log.error("{/modules/oauth/token-handler-utils.js} Error in retrieving tenant " + + "based client application credentials. Unable to obtain a valid tenant domain for provided username "+ + username +"- getDynamicClientAppCredentials(x)"); + return null; + } else { + var cachedTenantBasedClientAppCredentials = privateMethods. + getCachedTenantBasedClientAppCredentials(tenantDomain); + if (cachedTenantBasedClientAppCredentials) { + return cachedTenantBasedClientAppCredentials; + } else { + // calling dynamic client app registration service endpoint + var requestURL = configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"]["appRegistration"] + ["dynamicClientAppRegistrationServiceURL"]; + var requestPayload = dcAppProperties; + var token = publicMethods.encode(configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"] + ["appRegistration"]["owner"] + ":" + configs["authorization"]["methods"]["oauth"]["attributes"] + ["oauthProvider"]["appRegistration"]["password"]); + var xhr = new XMLHttpRequest(); + xhr.open("POST", requestURL, false); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.setRequestHeader("Authorization", "Basic "+ token); + xhr.send(stringify(requestPayload)); + var dynamicClientAppCredentials = {}; + if (xhr["status"] == 201 || xhr["status"] == 200 && xhr["responseText"]) { + var responsePayload = parse(xhr["responseText"]); + var clientId = responsePayload["client_id"]; + var clientSecret = responsePayload["client_secret"]; + if(typeof clientId == "undefined"){ + clientId = responsePayload["clientId"]; + } + if(typeof clientSecret == "undefined"){ + clientSecret = responsePayload["clientSecret"]; + } + dynamicClientAppCredentials["clientId"] = clientId; + dynamicClientAppCredentials["clientSecret"] = clientSecret; + privateMethods. + setCachedTenantBasedClientAppCredentials(tenantDomain, dynamicClientAppCredentials); + } else if (xhr["status"] == 400) { + log.error("{/modules/oauth/token-handler-utils.js - getDynamicClientAppCredentials()} " + + "Bad request. Invalid data provided as dynamic client application properties."); + dynamicClientAppCredentials = null; + } else { + log.error("{/modules/oauth/token-handler-utils.js - getDynamicClientAppCredentials()} " + + "Error in retrieving dynamic client credentials."); + dynamicClientAppCredentials = null; + } + // returning dynamic client credentials + return dynamicClientAppCredentials; + } + } + }; + + /** + * If gateway is enable, apiManagerClientAppRegistrationServiceURL is used to create oauth application + * @param username username of current logged user + * @returns {{clientId:*, clientSecret:*}} + */ + publicMethods["getTenantBasedClientAppCredentials"] = function (username) { + if (!username) { + log.error("{/modules/oauth/token-handler-utils.js} Error in retrieving tenant " + + "based client app credentials. No username " + + "as input - getTenantBasedClientAppCredentials(x)"); + return null; + } else { + //noinspection JSUnresolvedFunction, JSUnresolvedVariable + var tenantDomain = carbon.server.tenantDomain({username: username}); + + if (!tenantDomain) { + log.error("{/modules/oauth/token-handler-utils.js} Error in retrieving tenant " + + "based client application credentials. Unable to obtain a valid tenant domain for provided " + + "username - getTenantBasedClientAppCredentials(x, y)"); + return null; + } else { + var cachedTenantBasedClientAppCredentials = privateMethods. + getCachedTenantBasedClientAppCredentials(tenantDomain); + if (cachedTenantBasedClientAppCredentials) { + return cachedTenantBasedClientAppCredentials; + } else { + var adminUsername = configs["authorization"]["methods"]["oauth"]["attributes"]["adminUser"]; + var adminUserTenantId = configs["authorization"]["methods"]["oauth"]["attributes"] + ["adminUserTenantId"]; + //claims required for jwtAuthenticator. + var claims = {"http://wso2.org/claims/enduserTenantId": adminUserTenantId, + "http://wso2.org/claims/enduser": adminUsername}; + var jwtToken = publicMethods.getJwtToken(adminUsername, claims); + // register a tenant based client app at API Manager + var applicationName = configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"] + ["appRegistration"]["clientName"] + "_" + tenantDomain; + var requestURL = configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"] + ["appRegistration"]["apiManagerClientAppRegistrationServiceURL"] + + "?tenantDomain=" + tenantDomain + "&applicationName=" + applicationName; + var xhr = new XMLHttpRequest(); + xhr.open("POST", requestURL, false); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.setRequestHeader("X-JWT-Assertion", "" + jwtToken); + xhr.send(); + if ((xhr["status"] == 201 || xhr["status"] == 200) && xhr["responseText"]) { + var responsePayload = parse(xhr["responseText"]); + var tenantBasedClientAppCredentials = {}; + var clientId = responsePayload["client_id"]; + var clientSecret = responsePayload["client_secret"]; + if(typeof clientId == "undefined"){ + clientId = responsePayload["clientId"]; + } + if(typeof clientSecret == "undefined"){ + clientSecret = responsePayload["clientSecret"]; + } + tenantBasedClientAppCredentials["clientId"] = clientId; + tenantBasedClientAppCredentials["clientSecret"] = clientSecret; + privateMethods. + setCachedTenantBasedClientAppCredentials(tenantDomain, tenantBasedClientAppCredentials); + return tenantBasedClientAppCredentials; + } else { + log.error("{/modules/oauth/token-handler-utils.js} Error in retrieving tenant " + + "based client application credentials from API " + + "Manager - getTenantBasedClientAppCredentials(x, y)"); + return null; + } + } + } + } + }; + + /** + * Caching oauth application credentials + * @param tenantDomain tenant domain where application is been created + * @param clientAppCredentials {{clientId:*, clientSecret:*}} + */ + privateMethods["setCachedTenantBasedClientAppCredentials"] = function (tenantDomain, clientAppCredentials) { + var cachedTenantBasedClientAppCredentialsMap = application.get(constants["CACHED_CREDENTIALS_PORTAL_APP"]); + if (!cachedTenantBasedClientAppCredentialsMap) { + cachedTenantBasedClientAppCredentialsMap = {}; + cachedTenantBasedClientAppCredentialsMap[tenantDomain] = clientAppCredentials; + application.put(constants["CACHED_CREDENTIALS_PORTAL_APP"], cachedTenantBasedClientAppCredentialsMap); + } else if (!cachedTenantBasedClientAppCredentialsMap[tenantDomain]) { + cachedTenantBasedClientAppCredentialsMap[tenantDomain] = clientAppCredentials; + } + }; + + /** + * Get oauth application credentials from cache + * @param tenantDomain tenant domain where application is been created + * @returns {{clientId:*, clientSecret:*}} + */ + privateMethods["getCachedTenantBasedClientAppCredentials"] = function (tenantDomain) { + var cachedTenantBasedClientAppCredentialsMap = application.get(constants["CACHED_CREDENTIALS_PORTAL_APP"]); + if (!cachedTenantBasedClientAppCredentialsMap || + !cachedTenantBasedClientAppCredentialsMap[tenantDomain]) { + return null; + } else { + return cachedTenantBasedClientAppCredentialsMap[tenantDomain]; + } + }; + + /** + * Get access token and refresh token using password grant type + * @param username username of the logged user + * @param password password of the logged user + * @param encodedClientAppCredentials {{clientId:*, clientSecret:*}} + * @param scopes scopes list + * @returns {{accessToken: *, refreshToken: *}} + */ + publicMethods["getTokenPairAndScopesByPasswordGrantType"] = function (username, password + , encodedClientAppCredentials, scopes) { + if (!username || !password || !encodedClientAppCredentials || !scopes) { + log.error("{/modules/oauth/token-handler-utils.js} Error in retrieving access token by password " + + "grant type. No username, password, encoded client app credentials or scopes are " + + "found - getTokenPairAndScopesByPasswordGrantType(a, b, c, d)"); + return null; + } else { + // calling oauth provider token service endpoint + var requestURL = configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"] + ["tokenServiceURL"]; + var requestPayload = "grant_type=password&username=" + + username + "&password=" + password + "&scope=" + scopes; + + var xhr = new XMLHttpRequest(); + xhr.open("POST", requestURL, false); + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xhr.setRequestHeader("Authorization", "Basic " + encodedClientAppCredentials); + xhr.send(requestPayload); + + if (xhr["status"] == 200 && xhr["responseText"]) { + var responsePayload = parse(xhr["responseText"]); + var tokenData = {}; + tokenData["accessToken"] = responsePayload["access_token"]; + tokenData["refreshToken"] = responsePayload["refresh_token"]; + tokenData["scopes"] = responsePayload["scope"]; + return tokenData; + } else { + log.error("{/modules/oauth/token-handler-utils.js} Error in retrieving access token " + + "by password grant type - getTokenPairAndScopesByPasswordGrantType(a, b, c, d)"); + return null; + } + } + }; + + /** + * Get access token and refresh token using SAML grant type + * @param assertion + * @param encodedClientAppCredentials + * @param scopes + * @returns {{accessToken: *, refreshToken: *}} + */ + publicMethods["getTokenPairAndScopesByJWTGrantType"] = function (username, encodedClientAppCredentials, scopes) { + if (!username || !encodedClientAppCredentials || !scopes) { + log.error("{/app/modules/oauth/token-handler-utils.js} Error in retrieving access token by jwt " + + "grant type. No assertion, encoded client app credentials or scopes are " + + "found - getTokenPairAndScopesByJWTGrantType(x, y, z)"); + return null; + } else { + var JWTClientManagerServicePackagePath = + "org.wso2.carbon.identity.jwt.client.extension.service.JWTClientManagerService"; + //noinspection JSUnresolvedFunction, JSUnresolvedVariable + var JWTClientManagerService = carbon.server.osgiService(JWTClientManagerServicePackagePath); + //noinspection JSUnresolvedFunction + var jwtClient = JWTClientManagerService.getJWTClient(); + // returning access token by JWT grant type + var tokenInfo = jwtClient.getAccessToken(encodedClientAppCredentials, + username, scopes); + var tokenData = {}; + tokenData["accessToken"] = tokenInfo.getAccessToken(); + tokenData["refreshToken"] = tokenInfo.getRefreshToken(); + tokenData["scopes"] = tokenInfo.getScopes(); + return tokenData; + } + }; + + /** + * Get access token and refresh token using SAML grant type + * @param assertion + * @param encodedClientAppCredentials + * @param scopes + * @returns {{accessToken: *, refreshToken: *}} + */ + publicMethods["getTokenPairAndScopesBySAMLGrantType"] = function (assertion, encodedClientAppCredentials, scopes) { + if (!assertion || !encodedClientAppCredentials || !scopes) { + log.error("{/modules/oauth/token-handler-utils.js} Error in retrieving access token by saml " + + "grant type. No assertion, encoded client app credentials or scopes are " + + "found - getTokenPairAndScopesBySAMLGrantType(x, y, z)"); + return null; + } else { + + var assertionXML = publicMethods.decode(assertion); + /* + TODO: make assertion extraction with proper parsing. + Since Jaggery XML parser seem to add formatting which causes signature verification to fail. + */ + var assertionStartMarker = " urn:ietf:params:oauth:grant-type:jwt-bearer - org.wso2.carbon.identity.oauth2.grant.jwt.JWTBearerGrantHandler + org.wso2.carbon.device.mgt.oauth.extensions.handlers.grant.ExtendedJWTGrantHandler org.wso2.carbon.identity.oauth2.grant.jwt.JWTGrantValidator ]]> diff --git a/modules/core/distribution/pom.xml b/modules/core/distribution/pom.xml index 910fa08b..6b74d4c3 100644 --- a/modules/core/distribution/pom.xml +++ b/modules/core/distribution/pom.xml @@ -128,7 +128,7 @@ /Server/OAuth/SupportedGrantTypes/SupportedGrantType (org.wso2.carbon.identity.oauth2.token.handlers.grant.saml.SAML2BearerGrantHandler) - org.wso2.carbon.device.mgt.oauth.extensions.handlers.grant.ExtendedSAML2BearerGrantHandler + org.wso2.carbon.apimgt.keymgt.handlers.ExtendedSAML2BearerGrantHandler /Server/OAuth/SupportedGrantTypes/SupportedGrantType diff --git a/modules/core/distribution/src/assembly/bin.xml b/modules/core/distribution/src/assembly/bin.xml index 0849ebbb..c7ec011b 100644 --- a/modules/core/distribution/src/assembly/bin.xml +++ b/modules/core/distribution/src/assembly/bin.xml @@ -116,6 +116,10 @@ **/repository/components/plugins/httpclient_4.3.2.wso2v1.jar **/conf/tomcat/carbon/WEB-INF/web.xml **/repository/components/plugins/org.wso2.carbon.hostobjects.sso_4.5.4.jar + **/bin/wso2server.sh + **/bin/wso2server.bat + **/repository/deployment/server/jaggeryapps/portal/modules/oauth/plugins/token-handler-utils.js + **/repository/deployment/server/jaggeryapps/portal/modules/oauth/plugins/token-handlers.js @@ -129,18 +133,6 @@ */** - - target/wso2carbon-core-${carbon.kernel.version} - ${pom.artifactId}-${pom.version} - - **/*.sh - - - bin/wso2server.sh - bin/wso2server.bat - - 755 - @@ -798,6 +790,20 @@ 755 + + src/repository/jaggeryapps/portal/modules/oauth/token-handler-utils.js + + ${pom.artifactId}-${pom.version}/repository/deployment/server/jaggeryapps/portal/modules/oauth + + 755 + + + src/repository/jaggeryapps/portal/modules/oauth/token-handlers.js + + ${pom.artifactId}-${pom.version}/repository/deployment/server/jaggeryapps/portal/modules/oauth + + 755 + @@ -995,7 +1001,7 @@ --> - src/repository/conf/cdm-config.xml + ../p2-profile-gen/target/wso2carbon-core-${carbon.kernel.version}/repository/conf/cdm-config.xml ${pom.artifactId}-${pom.version}/repository/conf true diff --git a/modules/core/distribution/src/repository/conf/api-manager.xml b/modules/core/distribution/src/repository/conf/api-manager.xml index 81cca534..1341e29f 100755 --- a/modules/core/distribution/src/repository/conf/api-manager.xml +++ b/modules/core/distribution/src/repository/conf/api-manager.xml @@ -253,10 +253,10 @@ am_application_scope - + + /oauth2/token - - - - - - jdbc/DM_DS - - - - - org.wso2.carbon.device.mgt.extensions.push.notification.provider.mqtt.MQTTBasedPushNotificationProvider - org.wso2.carbon.device.mgt.extensions.push.notification.provider.xmpp.XMPPBasedPushNotificationProvider - - - - - https://localhost:9443 - admin - admin - - - org.wso2.carbon.policy.mgt - false - 60000 - 5 - 8 - 20 - - - - Simple - - - true - 60000 - org.wso2.carbon.device.mgt.core.task.impl.DeviceDetailsRetrieverTask - - - - 20 - 20 - 20 - 20 - - \ No newline at end of file diff --git a/modules/core/distribution/src/repository/jaggeryapps/portal/modules/oauth/token-handler-utils.js b/modules/core/distribution/src/repository/jaggeryapps/portal/modules/oauth/token-handler-utils.js new file mode 100644 index 00000000..c6a8aab5 --- /dev/null +++ b/modules/core/distribution/src/repository/jaggeryapps/portal/modules/oauth/token-handler-utils.js @@ -0,0 +1,598 @@ +/* + * 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. + */ + +var utils = function () { + var log = new Log("/modules/oauth/token-handler-utils.js"); + + var configs = require('/configs/portal.js').config(); + var constants = require("/modules/constants.js"); + var carbon = require("carbon"); + + //noinspection JSUnresolvedVariable + var Base64 = Packages.org.apache.commons.codec.binary.Base64; + //noinspection JSUnresolvedVariable + var String = Packages.java.lang.String; + + var publicMethods = {}; + var privateMethods = {}; + + publicMethods["encode"] = function (payload) { + return String(Base64.encodeBase64(String(payload).getBytes())); + }; + + publicMethods["decode"] = function (payload) { + return String(Base64.decodeBase64(String(payload).getBytes())); + }; + + /** + * Check whether this application is oauth enable or not + * @returns boolean if oauth enable + */ + publicMethods["checkOAuthEnabled"] = function () { + if (constants.AUTHORIZATION_TYPE_OAUTH === configs["authorization"]["activeMethod"]) { + return true; + } + return false; + }; + + /** + * Set access token into xml http request header + * @param xhr xml http request + * @returns {*} xhr which has access token it's header + */ + publicMethods["setAccessToken"] = function (xhr, callback) { + var accessToken; + if (publicMethods.checkOAuthEnabled()) { + try { + accessToken = parse(session.get(constants.ACCESS_TOKEN_PAIR_IDENTIFIER_FOR_PORTAL))["accessToken"]; + xhr.setRequestHeader(constants.AUTHORIZATION_HEADER, constants.BEARER_PREFIX + accessToken); + } catch (exception) { + log.error("Access token hasn't been set yet, " + exception); + } finally { + callback(xhr); + } + } + callback(xhr); + }; + + /** + * Get access token of current logged user + * @param callBack response with access token + */ + publicMethods["getAccessToken"] = function (callBack) { + var accessToken = null; + if (publicMethods.checkOAuthEnabled()) { + try { + accessToken = parse(session.get(constants.ACCESS_TOKEN_PAIR_IDENTIFIER_FOR_PORTAL))["accessToken"]; + } catch (exception) { + log.error("Access token hasn't been set yet, " + exception); + } finally { + callBack(accessToken); + } + } + callBack(accessToken); + }; + + /** + * Create error message which adhere to xml http response object + * @param statusCode response status code + * @param status response status + * @param responseText response message + * @returns {{statusCode: *, status: *, responseText: *}} + */ + publicMethods["createXHRObject"] = function (statusCode, status, responseText) { + return {"statusCode": statusCode, "status": status, "responseText": responseText}; + }; + + /** + * check whether user already logged to system before invoking any apis + * @param callBack + */ + publicMethods["isUserAuthorized"] = function (callBack) { + if (session.get("Loged") !== constants.LOGIN_MESSAGE) { + callBack(false); + } else { + callBack(true); + } + }; + + /** + * Get identity provider uir + * @returns {*} + */ + publicMethods["getIdPServerURL"] = function () { + return configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"]["tokenServiceURL"]; + }; + + /** + * Get an Access token pair based on client secret + * @param encodedClientKeys {{clientId:"", clientSecret:""}} + * @param scope eg: PRODUCTION + * @param idPServer identity provider url + * @returns {{accessToken: *, refreshToken: *}} + */ + publicMethods["getTokenWithClientSecretType"] = function (encodedClientKeys, scope, idPServer) { + var xhr = new XMLHttpRequest(); + var tokenEndpoint = idPServer; + xhr.open(constants.HTTP_POST, tokenEndpoint, false); + xhr.setRequestHeader(constants.CONTENT_TYPE_IDENTIFIER, constants.APPLICATION_X_WWW_FOR_URLENCODED); + xhr.setRequestHeader(constants.AUTHORIZATION_HEADER, constants.BASIC_PREFIX + encodedClientKeys); + xhr.send("grant_type=client_credentials&scope=" + scope); + var tokenPair = {}; + if (xhr.status == constants.HTTP_ACCEPTED) { + var data = parse(xhr.responseText); + tokenPair.refreshToken = data.refresh_token; + tokenPair.accessToken = data.access_token; + } else if (xhr.status == constants.HTTP_USER_NOT_AUTHENTICATED) { + log.error("Error in obtaining token with client secret grant type, You are not authenticated yet"); + return null; + } else { + log.error("Error in obtaining token with client secret grant type, This might be a problem with client meta " + + "data which required for client secret grant type"); + return null; + } + return tokenPair; + }; + + + /** + * This will create client id and client secret for a given application + * @param properties "callbackUrl": "", + * "clientName": "", + * "owner": "", + * "applicationType": "", + * "grantType": "", + * "saasApp" :"", + * "dynamicClientRegistrationEndPoint" : "" + * + * @returns {{clientId:*, clientSecret:*}} + */ + publicMethods["getDynamicClientAppCredentials"] = function (username) { + // setting up dynamic client application properties + var dcAppProperties = { + "applicationType": configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"]["appRegistration"]["appType"], + "clientName": configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"]["appRegistration"]["clientName"], + "owner": configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"]["appRegistration"]["owner"], + "tokenScope": configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"]["appRegistration"]["tokenScope"], + "grantType": configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"]["appRegistration"]["grantType"], + "callbackUrl": configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"]["appRegistration"]["callbackUrl"], + "saasApp" : configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"]["appRegistration"]["saasApp"] + }; + + var tenantDomain = carbon.server.tenantDomain({username: username}); + if (!tenantDomain) { + log.error("{/modules/oauth/token-handler-utils.js} Error in retrieving tenant " + + "based client application credentials. Unable to obtain a valid tenant domain for provided username "+ + username +"- getDynamicClientAppCredentials(x)"); + return null; + } else { + var cachedTenantBasedClientAppCredentials = privateMethods. + getCachedTenantBasedClientAppCredentials(tenantDomain); + if (cachedTenantBasedClientAppCredentials) { + return cachedTenantBasedClientAppCredentials; + } else { + // calling dynamic client app registration service endpoint + var requestURL = configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"]["appRegistration"] + ["dynamicClientAppRegistrationServiceURL"]; + var requestPayload = dcAppProperties; + var token = publicMethods.encode(configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"] + ["appRegistration"]["owner"] + ":" + configs["authorization"]["methods"]["oauth"]["attributes"] + ["oauthProvider"]["appRegistration"]["password"]); + var xhr = new XMLHttpRequest(); + xhr.open("POST", requestURL, false); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.setRequestHeader("Authorization", "Basic "+ token); + xhr.send(stringify(requestPayload)); + var dynamicClientAppCredentials = {}; + if (xhr["status"] == 201 || xhr["status"] == 200 && xhr["responseText"]) { + var responsePayload = parse(xhr["responseText"]); + var clientId = responsePayload["client_id"]; + var clientSecret = responsePayload["client_secret"]; + if(typeof clientId == "undefined"){ + clientId = responsePayload["clientId"]; + } + if(typeof clientSecret == "undefined"){ + clientSecret = responsePayload["clientSecret"]; + } + dynamicClientAppCredentials["clientId"] = clientId; + dynamicClientAppCredentials["clientSecret"] = clientSecret; + privateMethods. + setCachedTenantBasedClientAppCredentials(tenantDomain, dynamicClientAppCredentials); + } else if (xhr["status"] == 400) { + log.error("{/modules/oauth/token-handler-utils.js - getDynamicClientAppCredentials()} " + + "Bad request. Invalid data provided as dynamic client application properties."); + dynamicClientAppCredentials = null; + } else { + log.error("{/modules/oauth/token-handler-utils.js - getDynamicClientAppCredentials()} " + + "Error in retrieving dynamic client credentials."); + dynamicClientAppCredentials = null; + } + // returning dynamic client credentials + return dynamicClientAppCredentials; + } + } + }; + + /** + * If gateway is enable, apiManagerClientAppRegistrationServiceURL is used to create oauth application + * @param username username of current logged user + * @returns {{clientId:*, clientSecret:*}} + */ + publicMethods["getTenantBasedClientAppCredentials"] = function (username) { + if (!username) { + log.error("{/modules/oauth/token-handler-utils.js} Error in retrieving tenant " + + "based client app credentials. No username " + + "as input - getTenantBasedClientAppCredentials(x)"); + return null; + } else { + //noinspection JSUnresolvedFunction, JSUnresolvedVariable + var tenantDomain = carbon.server.tenantDomain({username: username}); + + if (!tenantDomain) { + log.error("{/modules/oauth/token-handler-utils.js} Error in retrieving tenant " + + "based client application credentials. Unable to obtain a valid tenant domain for provided " + + "username - getTenantBasedClientAppCredentials(x, y)"); + return null; + } else { + var cachedTenantBasedClientAppCredentials = privateMethods. + getCachedTenantBasedClientAppCredentials(tenantDomain); + if (cachedTenantBasedClientAppCredentials) { + return cachedTenantBasedClientAppCredentials; + } else { + var adminUsername = configs["authorization"]["methods"]["oauth"]["attributes"]["adminUser"]; + var adminUserTenantId = configs["authorization"]["methods"]["oauth"]["attributes"] + ["adminUserTenantId"]; + //claims required for jwtAuthenticator. + var claims = {"http://wso2.org/claims/enduserTenantId": adminUserTenantId, + "http://wso2.org/claims/enduser": adminUsername}; + var jwtToken = publicMethods.getJwtToken(adminUsername, claims); + // register a tenant based client app at API Manager + var applicationName = configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"] + ["appRegistration"]["clientName"] + "_" + tenantDomain; + var requestURL = configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"] + ["appRegistration"]["apiManagerClientAppRegistrationServiceURL"] + + "?tenantDomain=" + tenantDomain + "&applicationName=" + applicationName; + var xhr = new XMLHttpRequest(); + xhr.open("POST", requestURL, false); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.setRequestHeader("X-JWT-Assertion", "" + jwtToken); + xhr.send(); + if ((xhr["status"] == 201 || xhr["status"] == 200) && xhr["responseText"]) { + var responsePayload = parse(xhr["responseText"]); + var tenantBasedClientAppCredentials = {}; + var clientId = responsePayload["client_id"]; + var clientSecret = responsePayload["client_secret"]; + if(typeof clientId == "undefined"){ + clientId = responsePayload["clientId"]; + } + if(typeof clientSecret == "undefined"){ + clientSecret = responsePayload["clientSecret"]; + } + tenantBasedClientAppCredentials["clientId"] = clientId; + tenantBasedClientAppCredentials["clientSecret"] = clientSecret; + privateMethods. + setCachedTenantBasedClientAppCredentials(tenantDomain, tenantBasedClientAppCredentials); + return tenantBasedClientAppCredentials; + } else { + log.error("{/modules/oauth/token-handler-utils.js} Error in retrieving tenant " + + "based client application credentials from API " + + "Manager - getTenantBasedClientAppCredentials(x, y)"); + return null; + } + } + } + } + }; + + /** + * Caching oauth application credentials + * @param tenantDomain tenant domain where application is been created + * @param clientAppCredentials {{clientId:*, clientSecret:*}} + */ + privateMethods["setCachedTenantBasedClientAppCredentials"] = function (tenantDomain, clientAppCredentials) { + var cachedTenantBasedClientAppCredentialsMap = application.get(constants["CACHED_CREDENTIALS_PORTAL_APP"]); + if (!cachedTenantBasedClientAppCredentialsMap) { + cachedTenantBasedClientAppCredentialsMap = {}; + cachedTenantBasedClientAppCredentialsMap[tenantDomain] = clientAppCredentials; + application.put(constants["CACHED_CREDENTIALS_PORTAL_APP"], cachedTenantBasedClientAppCredentialsMap); + } else if (!cachedTenantBasedClientAppCredentialsMap[tenantDomain]) { + cachedTenantBasedClientAppCredentialsMap[tenantDomain] = clientAppCredentials; + } + }; + + /** + * Get oauth application credentials from cache + * @param tenantDomain tenant domain where application is been created + * @returns {{clientId:*, clientSecret:*}} + */ + privateMethods["getCachedTenantBasedClientAppCredentials"] = function (tenantDomain) { + var cachedTenantBasedClientAppCredentialsMap = application.get(constants["CACHED_CREDENTIALS_PORTAL_APP"]); + if (!cachedTenantBasedClientAppCredentialsMap || + !cachedTenantBasedClientAppCredentialsMap[tenantDomain]) { + return null; + } else { + return cachedTenantBasedClientAppCredentialsMap[tenantDomain]; + } + }; + + /** + * Get access token and refresh token using password grant type + * @param username username of the logged user + * @param password password of the logged user + * @param encodedClientAppCredentials {{clientId:*, clientSecret:*}} + * @param scopes scopes list + * @returns {{accessToken: *, refreshToken: *}} + */ + publicMethods["getTokenPairAndScopesByPasswordGrantType"] = function (username, password + , encodedClientAppCredentials, scopes) { + if (!username || !password || !encodedClientAppCredentials || !scopes) { + log.error("{/modules/oauth/token-handler-utils.js} Error in retrieving access token by password " + + "grant type. No username, password, encoded client app credentials or scopes are " + + "found - getTokenPairAndScopesByPasswordGrantType(a, b, c, d)"); + return null; + } else { + // calling oauth provider token service endpoint + var requestURL = configs["authorization"]["methods"]["oauth"]["attributes"]["oauthProvider"] + ["tokenServiceURL"]; + var requestPayload = "grant_type=password&username=" + + username + "&password=" + password + "&scope=" + scopes; + + var xhr = new XMLHttpRequest(); + xhr.open("POST", requestURL, false); + xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + xhr.setRequestHeader("Authorization", "Basic " + encodedClientAppCredentials); + xhr.send(requestPayload); + + if (xhr["status"] == 200 && xhr["responseText"]) { + var responsePayload = parse(xhr["responseText"]); + var tokenData = {}; + tokenData["accessToken"] = responsePayload["access_token"]; + tokenData["refreshToken"] = responsePayload["refresh_token"]; + tokenData["scopes"] = responsePayload["scope"]; + return tokenData; + } else { + log.error("{/modules/oauth/token-handler-utils.js} Error in retrieving access token " + + "by password grant type - getTokenPairAndScopesByPasswordGrantType(a, b, c, d)"); + return null; + } + } + }; + + /** + * Get access token and refresh token using SAML grant type + * @param assertion + * @param encodedClientAppCredentials + * @param scopes + * @returns {{accessToken: *, refreshToken: *}} + */ + publicMethods["getTokenPairAndScopesByJWTGrantType"] = function (username, encodedClientAppCredentials, scopes) { + if (!username || !encodedClientAppCredentials || !scopes) { + log.error("{/app/modules/oauth/token-handler-utils.js} Error in retrieving access token by jwt " + + "grant type. No assertion, encoded client app credentials or scopes are " + + "found - getTokenPairAndScopesByJWTGrantType(x, y, z)"); + return null; + } else { + var JWTClientManagerServicePackagePath = + "org.wso2.carbon.identity.jwt.client.extension.service.JWTClientManagerService"; + //noinspection JSUnresolvedFunction, JSUnresolvedVariable + var JWTClientManagerService = carbon.server.osgiService(JWTClientManagerServicePackagePath); + //noinspection JSUnresolvedFunction + var jwtClient = JWTClientManagerService.getJWTClient(); + // returning access token by JWT grant type + var tokenInfo = jwtClient.getAccessToken(encodedClientAppCredentials, + username, scopes); + var tokenData = {}; + tokenData["accessToken"] = tokenInfo.getAccessToken(); + tokenData["refreshToken"] = tokenInfo.getRefreshToken(); + tokenData["scopes"] = tokenInfo.getScopes(); + return tokenData; + } + }; + + /** + * Get access token and refresh token using SAML grant type + * @param assertion + * @param encodedClientAppCredentials + * @param scopes + * @returns {{accessToken: *, refreshToken: *}} + */ + publicMethods["getTokenPairAndScopesBySAMLGrantType"] = function (assertion, encodedClientAppCredentials, scopes) { + if (!assertion || !encodedClientAppCredentials || !scopes) { + log.error("{/modules/oauth/token-handler-utils.js} Error in retrieving access token by saml " + + "grant type. No assertion, encoded client app credentials or scopes are " + + "found - getTokenPairAndScopesBySAMLGrantType(x, y, z)"); + return null; + } else { + + var assertionXML = publicMethods.decode(assertion); + /* + TODO: make assertion extraction with proper parsing. + Since Jaggery XML parser seem to add formatting which causes signature verification to fail. + */ + var assertionStartMarker = " org.wso2.carbon.devicemgt:org.wso2.carbon.device.mgt.extensions.push.notification.provider.mqtt.feature:${carbon.device.mgt.version} + + org.wso2.carbon.devicemgt:org.wso2.carbon.device.mgt.extensions.push.notification.provider.gcm.feature:${carbon.device.mgt.version} + org.wso2.carbon.devicemgt:org.wso2.carbon.device.mgt.extensions.push.notification.provider.xmpp.feature:${carbon.device.mgt.version} @@ -958,6 +961,10 @@ org.wso2.carbon.device.mgt.extensions.push.notification.provider.mqtt.feature.group ${carbon.device.mgt.version} + + org.wso2.carbon.device.mgt.extensions.push.notification.provider.gcm.feature.group + ${carbon.device.mgt.version} + org.wso2.carbon.device.mgt.extensions.push.notification.provider.xmpp.feature.group ${carbon.device.mgt.version} @@ -1904,6 +1911,10 @@ org.wso2.carbon.device.mgt.extensions.push.notification.provider.mqtt.feature.group ${carbon.device.mgt.version} + + org.wso2.carbon.device.mgt.extensions.push.notification.provider.gcm.feature.group + ${carbon.device.mgt.version} + org.wso2.carbon.device.mgt.extensions.push.notification.provider.xmpp.feature.group ${carbon.device.mgt.version} @@ -2395,6 +2406,10 @@ org.wso2.carbon.device.mgt.extensions.push.notification.provider.mqtt.feature.group ${carbon.device.mgt.version} + + org.wso2.carbon.device.mgt.extensions.push.notification.provider.gcm.feature.group + ${carbon.device.mgt.version} + org.wso2.carbon.device.mgt.extensions.push.notification.provider.xmpp.feature.group ${carbon.device.mgt.version} @@ -2605,6 +2620,10 @@ org.wso2.carbon.device.mgt.extensions.push.notification.provider.mqtt.feature.group ${carbon.device.mgt.version} + + org.wso2.carbon.device.mgt.extensions.push.notification.provider.gcm.feature.group + ${carbon.device.mgt.version} + org.wso2.carbon.device.mgt.extensions.push.notification.provider.xmpp.feature.group ${carbon.device.mgt.version} @@ -3046,6 +3065,10 @@ org.wso2.carbon.device.mgt.extensions.push.notification.provider.mqtt.feature.group ${carbon.device.mgt.version} + + org.wso2.carbon.device.mgt.extensions.push.notification.provider.gcm.feature.group + ${carbon.device.mgt.version} + org.wso2.carbon.device.mgt.extensions.push.notification.provider.xmpp.feature.group ${carbon.device.mgt.version} @@ -3255,6 +3278,10 @@ org.wso2.carbon.device.mgt.extensions.push.notification.provider.mqtt.feature.group ${carbon.device.mgt.version} + + org.wso2.carbon.device.mgt.extensions.push.notification.provider.gcm.feature.group + ${carbon.device.mgt.version} + org.wso2.carbon.device.mgt.extensions.push.notification.provider.xmpp.feature.group ${carbon.device.mgt.version} @@ -3677,6 +3704,10 @@ org.wso2.carbon.device.mgt.extensions.push.notification.provider.mqtt.feature.group ${carbon.device.mgt.version} + + org.wso2.carbon.device.mgt.extensions.push.notification.provider.gcm.feature.group + ${carbon.device.mgt.version} + org.wso2.carbon.device.mgt.extensions.push.notification.provider.xmpp.feature.group ${carbon.device.mgt.version} diff --git a/pom.xml b/pom.xml index 335779a1..f0142183 100644 --- a/pom.xml +++ b/pom.xml @@ -1529,14 +1529,14 @@ 4.7.0 - 2.0.14-SNAPSHOT + 2.0.16-SNAPSHOT [2.0.0, 3.0.0) 3.1.0-SNAPSHOT - 3.0.11-SNAPSHOT + 3.0.12-SNAPSHOT 6.1.65