From 8e95812f1171d271061c1f2e99d2cb9ef8d480b0 Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Tue, 5 Feb 2019 10:24:50 +0530 Subject: [PATCH 01/26] Moving JWT issuer and validator to auth --- stdlib/auth/pom.xml | 5 ++ .../main/ballerina/auth/auth_constants.bal | 18 +++++ .../main/ballerina/auth/jwt_auth_provider.bal | 29 ++++--- .../src/main/ballerina/auth}/jwt_common.bal | 0 .../src/main/ballerina/auth}/jwt_issuer.bal | 41 ++++------ .../src/main/ballerina/auth}/jwt_native.bal | 30 ++------ .../auth/jwt_supported_auth_provider_util.bal | 26 ++----- .../main/ballerina/auth}/jwt_validator.bal | 72 +++++++++--------- .../auth/ldap_auth_store_provider.bal | 13 +--- .../auth/ldap}/jwt/crypto/JWSAlgorithm.java | 2 +- .../auth/ldap}/jwt/crypto/JWSException.java | 2 +- .../auth/ldap}/jwt/crypto/JWSSigner.java | 2 +- .../auth/ldap}/jwt/crypto/JWSVerifier.java | 2 +- .../auth/ldap}/jwt/crypto/KeyStoreHolder.java | 2 +- .../auth/ldap}/jwt/crypto/RSASSAProvider.java | 2 +- .../auth/ldap}/jwt/crypto/RSASigner.java | 2 +- .../auth/ldap}/jwt/crypto/RSAVerifier.java | 2 +- .../ldap}/jwt/crypto/TrustStoreHolder.java | 2 +- .../ldap}/jwt/signature/PathResolver.java | 2 +- .../auth/ldap}/jwt/signature/Sign.java | 26 ++++--- .../ldap}/jwt/signature/VerifySignature.java | 27 +++---- .../stdlib/auth/jwt/JWTAuthenticatorTest.java | 4 +- .../stdlib/auth}/jwt/JwtTest.java | 6 +- .../auth}/jwt/crypto/JWTSignerTest.java | 8 +- .../auth}/jwt/crypto/JWTVerifierTest.java | 8 +- .../test-src/jwt-authenticator-test.bal | 25 +++--- .../test/resources/test-src/jwt/jwt-test.bal | 38 +++++++++ .../stdlib/auth/JWTAuthnHandlerTest.java | 4 +- .../src/main/ballerina/internal/file.bal | 2 + .../stdlib/internal/ParseJson.java | 56 -------------- .../datafiles/keyStore/ballerinaKeystore.p12 | Bin 2594 -> 0 bytes .../keyStore/ballerinaTruststore.p12 | Bin 109346 -> 0 bytes .../test/resources/test-src/jwt/jwt-test.bal | 39 ---------- .../test/nativeimpl/functions/UtilTest.java | 60 --------------- .../test/types/json/JSONTest.java | 2 +- .../nativeimpl/functions/util-test.bal | 5 -- .../test-src/types/jsontype/json-test.bal | 6 +- 37 files changed, 213 insertions(+), 357 deletions(-) create mode 100644 stdlib/auth/src/main/ballerina/auth/auth_constants.bal rename stdlib/{internal/src/main/ballerina/internal => auth/src/main/ballerina/auth}/jwt_common.bal (100%) rename stdlib/{internal/src/main/ballerina/internal => auth/src/main/ballerina/auth}/jwt_issuer.bal (74%) rename stdlib/{internal/src/main/ballerina/internal => auth/src/main/ballerina/auth}/jwt_native.bal (63%) rename stdlib/{internal/src/main/ballerina/internal => auth/src/main/ballerina/auth}/jwt_validator.bal (81%) rename stdlib/{internal/src/main/java/org/ballerinalang/stdlib/internal => auth/src/main/java/org/ballerinalang/auth/ldap}/jwt/crypto/JWSAlgorithm.java (95%) rename stdlib/{internal/src/main/java/org/ballerinalang/stdlib/internal => auth/src/main/java/org/ballerinalang/auth/ldap}/jwt/crypto/JWSException.java (96%) rename stdlib/{internal/src/main/java/org/ballerinalang/stdlib/internal => auth/src/main/java/org/ballerinalang/auth/ldap}/jwt/crypto/JWSSigner.java (96%) rename stdlib/{internal/src/main/java/org/ballerinalang/stdlib/internal => auth/src/main/java/org/ballerinalang/auth/ldap}/jwt/crypto/JWSVerifier.java (96%) rename stdlib/{internal/src/main/java/org/ballerinalang/stdlib/internal => auth/src/main/java/org/ballerinalang/auth/ldap}/jwt/crypto/KeyStoreHolder.java (98%) rename stdlib/{internal/src/main/java/org/ballerinalang/stdlib/internal => auth/src/main/java/org/ballerinalang/auth/ldap}/jwt/crypto/RSASSAProvider.java (96%) rename stdlib/{internal/src/main/java/org/ballerinalang/stdlib/internal => auth/src/main/java/org/ballerinalang/auth/ldap}/jwt/crypto/RSASigner.java (97%) rename stdlib/{internal/src/main/java/org/ballerinalang/stdlib/internal => auth/src/main/java/org/ballerinalang/auth/ldap}/jwt/crypto/RSAVerifier.java (97%) rename stdlib/{internal/src/main/java/org/ballerinalang/stdlib/internal => auth/src/main/java/org/ballerinalang/auth/ldap}/jwt/crypto/TrustStoreHolder.java (98%) rename stdlib/{internal/src/main/java/org/ballerinalang/stdlib/internal => auth/src/main/java/org/ballerinalang/auth/ldap}/jwt/signature/PathResolver.java (97%) rename stdlib/{internal/src/main/java/org/ballerinalang/stdlib/internal => auth/src/main/java/org/ballerinalang/auth/ldap}/jwt/signature/Sign.java (78%) rename stdlib/{internal/src/main/java/org/ballerinalang/stdlib/internal => auth/src/main/java/org/ballerinalang/auth/ldap}/jwt/signature/VerifySignature.java (79%) rename stdlib/{internal/src/test/java/org/ballerinalang/internal => auth/src/test/java/org/ballerinalang/stdlib/auth}/jwt/JwtTest.java (94%) rename stdlib/{internal/src/test/java/org/ballerinalang/internal => auth/src/test/java/org/ballerinalang/stdlib/auth}/jwt/crypto/JWTSignerTest.java (91%) rename stdlib/{internal/src/test/java/org/ballerinalang/internal => auth/src/test/java/org/ballerinalang/stdlib/auth}/jwt/crypto/JWTVerifierTest.java (91%) create mode 100644 stdlib/auth/src/test/resources/test-src/jwt/jwt-test.bal delete mode 100644 stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/ParseJson.java delete mode 100644 stdlib/internal/src/test/resources/datafiles/keyStore/ballerinaKeystore.p12 delete mode 100644 stdlib/internal/src/test/resources/datafiles/keyStore/ballerinaTruststore.p12 delete mode 100644 stdlib/internal/src/test/resources/test-src/jwt/jwt-test.bal delete mode 100644 tests/ballerina-unit-test/src/test/java/org/ballerinalang/test/nativeimpl/functions/UtilTest.java delete mode 100644 tests/ballerina-unit-test/src/test/resources/test-src/nativeimpl/functions/util-test.bal diff --git a/stdlib/auth/pom.xml b/stdlib/auth/pom.xml index c2fa78338032..346dcb9a09ef 100644 --- a/stdlib/auth/pom.xml +++ b/stdlib/auth/pom.xml @@ -132,6 +132,11 @@ ballerina-task test + + org.ballerinalang + ballerina-crypto + test + diff --git a/stdlib/auth/src/main/ballerina/auth/auth_constants.bal b/stdlib/auth/src/main/ballerina/auth/auth_constants.bal new file mode 100644 index 000000000000..28f533a1abe2 --- /dev/null +++ b/stdlib/auth/src/main/ballerina/auth/auth_constants.bal @@ -0,0 +1,18 @@ +// Copyright (c) 2019 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. + +# Constant for the auth error code +public final string AUTH_ERROR_CODE = "{ballerina/auth}AuthError"; diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_auth_provider.bal b/stdlib/auth/src/main/ballerina/auth/jwt_auth_provider.bal index 3dce654222d9..b88f5f6ef1b8 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_auth_provider.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_auth_provider.bal @@ -22,7 +22,6 @@ import ballerina/time; # Represents a JWT Authenticator # -# + jwtAuthProviderConfig - JWT authentication provider configurations public type JWTAuthProvider object { public JWTAuthProviderConfig jwtAuthProviderConfig; @@ -44,7 +43,7 @@ public type JWTAuthProvider object { public function authenticate(string jwtToken) returns boolean|error { if (self.authCache.hasKey(jwtToken)) { var payload = self.authenticateFromCache(jwtToken); - if (payload is internal:JwtPayload) { + if (payload is JwtPayload) { self.setAuthContext(payload, jwtToken); return true; } else { @@ -52,8 +51,8 @@ public type JWTAuthProvider object { } } - var payload = internal:validate(jwtToken, self.jwtAuthProviderConfig); - if (payload is internal:JwtPayload) { + var payload = validateJwt(jwtToken, self.jwtAuthProviderConfig); + if (payload is JwtPayload) { self.setAuthContext(payload, jwtToken); self.addToAuthenticationCache(jwtToken, payload.exp, payload); return true; @@ -62,12 +61,12 @@ public type JWTAuthProvider object { } } - function authenticateFromCache(string jwtToken) returns internal:JwtPayload|() { + function authenticateFromCache(string jwtToken) returns JwtPayload|() { var context = trap self.authCache.get(jwtToken); if (context is CachedJWTAuthContext) { // convert to current time and check the expiry time if (context.expiryTime > (time:currentTime().time / 1000)) { - internal:JwtPayload payload = context.jwtPayload; + JwtPayload payload = context.jwtPayload; log:printDebug(function() returns string { return "Authenticate user :" + payload.sub + " from cache"; }); @@ -77,7 +76,7 @@ public type JWTAuthProvider object { return (); } - function addToAuthenticationCache(string jwtToken, int exp, internal:JwtPayload payload) { + function addToAuthenticationCache(string jwtToken, int exp, JwtPayload payload) { CachedJWTAuthContext cachedContext = {jwtPayload : payload, expiryTime : exp}; self.authCache.put(jwtToken, cachedContext); log:printDebug(function() returns string { @@ -85,7 +84,7 @@ public type JWTAuthProvider object { }); } - function setAuthContext(internal:JwtPayload jwtPayload, string jwtToken) { + function setAuthContext(JwtPayload jwtPayload, string jwtToken) { runtime:UserPrincipal userPrincipal = runtime:getInvocationContext().userPrincipal; userPrincipal.userId = jwtPayload.iss + ":" + jwtPayload.sub; // By default set sub as username. @@ -120,21 +119,19 @@ const string AUTH_TYPE_JWT = "jwt"; # + issuer - Identifier of the token issuer # + audience - Identifier of the token recipients # + clockSkew - Time in seconds to mitigate clock skew +# + trustStore - Trust store used for signature verification # + certificateAlias - Token signed key alias -# + trustStoreFilePath - Path to the trust store file -# + trustStorePassword - Trust store password public type JWTAuthProviderConfig record { - string issuer = ""; - string audience = ""; + string issuer; + string audience; int clockSkew = 0; - string certificateAlias = ""; - string trustStoreFilePath = ""; - string trustStorePassword = ""; + crypto:TrustStore trustStore; + string certificateAlias; !...; }; type CachedJWTAuthContext record { - internal:JwtPayload jwtPayload; + JwtPayload jwtPayload; int expiryTime; !...; }; diff --git a/stdlib/internal/src/main/ballerina/internal/jwt_common.bal b/stdlib/auth/src/main/ballerina/auth/jwt_common.bal similarity index 100% rename from stdlib/internal/src/main/ballerina/internal/jwt_common.bal rename to stdlib/auth/src/main/ballerina/auth/jwt_common.bal diff --git a/stdlib/internal/src/main/ballerina/internal/jwt_issuer.bal b/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal similarity index 74% rename from stdlib/internal/src/main/ballerina/internal/jwt_issuer.bal rename to stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal index 5da819430dcf..9d4d84f4e375 100644 --- a/stdlib/internal/src/main/ballerina/internal/jwt_issuer.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal @@ -15,44 +15,29 @@ // under the License. import ballerina/encoding; - -# Represents JWT issuer configurations. -# + keyAlias - Key alias used for signing -# + keyPassword - Key password used for signing -# + keyStoreFilePath - Key store file path -# + keyStorePassword - Key store password -public type JWTIssuerConfig record { - string keyAlias = ""; - string keyPassword = ""; - string keyStoreFilePath = ""; - string keyStorePassword = ""; - !...; -}; +import ballerina/crypto; # Issue a JWT token. # # + header - JwtHeader object # + payload - JwtPayload object -# + config - JWTIssuerConfig object +# + keyStore - Keystore to be used in JWT signing +# + keyAlias - Signing key alias +# + keyPassword - Signing key password # + return - JWT token string or an error if token validation fails -public function issue(JwtHeader header, JwtPayload payload, JWTIssuerConfig config) returns (string|error) { - string jwtHeader = check createHeader(header); - string jwtPayload = check createPayload(payload); +public function issueJwt(JwtHeader header, JwtPayload payload, crypto:KeyStore keyStore, string keyAlias, + string keyPassword) returns (string|error) { + string jwtHeader = check buildHeaderString(header); + string jwtPayload = check buildPayloadString(payload); string jwtAssertion = jwtHeader + "." + jwtPayload; - KeyStore keyStore = { - keyAlias : config.keyAlias, - keyPassword : config.keyPassword, - keyStoreFilePath : config.keyStoreFilePath, - keyStorePassword : config.keyStorePassword - }; - string signature = sign(jwtAssertion, header.alg, keyStore); + string signature = sign(jwtAssertion, header.alg, keyStore, keyAlias, keyPassword); return (jwtAssertion + "." + signature); } -function createHeader(JwtHeader header) returns (string|error) { +function buildHeaderString(JwtHeader header) returns (string|error) { json headerJson = {}; if (!validateMandatoryJwtHeaderFields(header)) { - error jwtError = error(INTERNAL_ERROR_CODE, { message : "Mandatory field signing algorithm(alg) is empty." }); + error jwtError = error(AUTH_ERROR_CODE, { message : "Mandatory field signing algorithm(alg) is empty." }); return jwtError; } headerJson[ALG] = header.alg; @@ -66,10 +51,10 @@ function createHeader(JwtHeader header) returns (string|error) { return encodedPayload; } -function createPayload(JwtPayload payload) returns (string|error) { +function buildPayloadString(JwtPayload payload) returns (string|error) { json payloadJson = {}; if (!validateMandatoryFields(payload)) { - error jwtError = error(INTERNAL_ERROR_CODE, + error jwtError = error(AUTH_ERROR_CODE, { message : "Mandatory fields(Issuer, Subject, Expiration time or Audience) are empty." }); return jwtError; } diff --git a/stdlib/internal/src/main/ballerina/internal/jwt_native.bal b/stdlib/auth/src/main/ballerina/auth/jwt_native.bal similarity index 63% rename from stdlib/internal/src/main/ballerina/internal/jwt_native.bal rename to stdlib/auth/src/main/ballerina/auth/jwt_native.bal index abbd8b099570..247a1a851514 100644 --- a/stdlib/internal/src/main/ballerina/internal/jwt_native.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_native.bal @@ -20,16 +20,10 @@ # + signature - Signature string. # + algorithm - Signature algorithm. # + trustStore - Truststore. -# + return - Verified status. true or false. -extern function verifySignature(string data, string signature, string algorithm, TrustStore trustStore) - returns error?; - -type TrustStore record { - string certificateAlias; - string trustStoreFilePath; - string trustStorePassword; - !...; -}; +# + keyAlias - Key alias. +# + return - error if verification failed, nil if verification is successful. +extern function verifySignature(string data, string signature, string algorithm, crypto:TrustStore trustStore, + string keyAlias) returns error?; # Sign the given input jwt data. # @@ -37,18 +31,6 @@ type TrustStore record { # + algorithm - Signature string. # + keyStore - Keystore. # + return - Signature. Signed string. -extern function sign(string data, string algorithm, KeyStore keyStore) returns (string); - -type KeyStore record { - string keyAlias; - string keyPassword; - string keyStoreFilePath; - string keyStorePassword; - !...; -}; +extern function sign(string data, string algorithm, crypto:KeyStore keyStore, string keyAlias, string keyPassword) +returns (string); -# Parse JSON string to generate JSON object. -# -# + s - JSON string -# + return - JSON object. -public extern function parseJson(string s) returns (json|error); diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal b/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal index a500df487502..ecbc585129c6 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal @@ -46,10 +46,10 @@ public type InferredJwtAuthProviderConfig record { # + username - user name # + authConfig - authentication provider configurations that supports generating JWT for client interactions function setAuthToken(string username, InferredJwtAuthProviderConfig authConfig) { - internal:JwtHeader header = createHeader(authConfig); - internal:JwtPayload payload = createPayload(username, authConfig); - internal:JWTIssuerConfig config = createJWTIssueConfig(authConfig); - var token = internal:issue(header, payload, config); + JwtHeader header = createHeader(authConfig); + JwtPayload payload = createPayload(username, authConfig); + crypto:KeyStore keyStore = { path: authConfig.keyStoreFilePath, password: authConfig.keyStorePassword }; + var token = issueJwt(header, payload, keyStore, authConfig.keyAlias, authConfig.keyPassword); if (token is string) { runtime:AuthContext authContext = runtime:getInvocationContext().authContext; authContext.scheme = "jwt"; @@ -57,15 +57,15 @@ function setAuthToken(string username, InferredJwtAuthProviderConfig authConfig) } } -function createHeader(InferredJwtAuthProviderConfig authConfig) returns (internal:JwtHeader) { - internal:JwtHeader header = { alg: authConfig.signingAlg, typ: "JWT" }; +function createHeader(InferredJwtAuthProviderConfig authConfig) returns (JwtHeader) { + JwtHeader header = { alg: authConfig.signingAlg, typ: "JWT" }; return header; } -function createPayload(string username, InferredJwtAuthProviderConfig authConfig) returns (internal:JwtPayload) { +function createPayload(string username, InferredJwtAuthProviderConfig authConfig) returns (JwtPayload) { string audList = authConfig.audience; string[] audience = audList.split(" "); - internal:JwtPayload payload = { + JwtPayload payload = { sub: username, iss: authConfig.issuer, exp: time:currentTime().time / 1000 + authConfig.expTime, @@ -76,13 +76,3 @@ function createPayload(string username, InferredJwtAuthProviderConfig authConfig }; return payload; } - -function createJWTIssueConfig(InferredJwtAuthProviderConfig authConfig) returns (internal:JWTIssuerConfig) { - internal:JWTIssuerConfig config = { - keyAlias: authConfig.keyAlias, - keyPassword: authConfig.keyPassword, - keyStoreFilePath: authConfig.keyStoreFilePath, - keyStorePassword: authConfig.keyStorePassword - }; - return config; -} diff --git a/stdlib/internal/src/main/ballerina/internal/jwt_validator.bal b/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal similarity index 81% rename from stdlib/internal/src/main/ballerina/internal/jwt_validator.bal rename to stdlib/auth/src/main/ballerina/auth/jwt_validator.bal index 930eb7757ccc..6d01c85e6a51 100644 --- a/stdlib/internal/src/main/ballerina/internal/jwt_validator.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal @@ -17,30 +17,31 @@ import ballerina/log; import ballerina/time; import ballerina/encoding; +import ballerina/crypto; +import ballerina/internal; +import ballerina/io; # Represents JWT validator configurations. # + issuer - Expected issuer # + audience - Expected audience # + clockSkew - Clock skew in seconds -# + certificateAlias - Certificate alias used for validation -# + trustStoreFilePath - Trust store file path -# + trustStorePassword - Trust store password +# + trustStore - Trust store used for signature verification +# + certificateAlias - Token signed key alias public type JWTValidatorConfig record { - string issuer = ""; - string audience = ""; + string issuer; + string audience; int clockSkew = 0; - string certificateAlias = ""; - string trustStoreFilePath = ""; - string trustStorePassword = ""; + crypto:TrustStore trustStore; + string certificateAlias; }; -# Validity given JWT token. +# Validity given JWT string. # # + jwtToken - JWT token that need to validate # + config - JWTValidatorConfig object # + return - If JWT token is valied return the JWT payload. # An error if token validation fails. -public function validate(string jwtToken, JWTValidatorConfig config) returns JwtPayload|error { +public function validateJwt(string jwtToken, JWTValidatorConfig config) returns JwtPayload|error { string[] encodedJWTComponents = []; var jwtComponents = getJWTComponents(jwtToken); if (jwtComponents is string[]) { @@ -48,7 +49,7 @@ public function validate(string jwtToken, JWTValidatorConfig config) returns Jwt } else if (jwtComponents is error) { return jwtComponents; } else { - error jwtError = error(INTERNAL_ERROR_CODE, { message : "Invalid JWT token" }); + error jwtError = error(AUTH_ERROR_CODE, { message : "Invalid JWT token" }); return jwtError; } @@ -61,22 +62,22 @@ public function validate(string jwtToken, JWTValidatorConfig config) returns Jwt } else if (decodedJwt is error) { return decodedJwt; } else { - error jwtError = error(INTERNAL_ERROR_CODE, { message : "Invalid JWT token" }); + error jwtError = error(AUTH_ERROR_CODE, { message : "Invalid JWT token" }); return jwtError; } - var jwtValidity = validateJWT(encodedJWTComponents, header, payload, config); + var jwtValidity = validateJwtRecords(encodedJWTComponents, header, payload, config); if (jwtValidity is error) { return jwtValidity; } else if (jwtValidity is boolean) { if (jwtValidity) { return payload; } else { - error jwtError = error(INTERNAL_ERROR_CODE, { message : "Invalid JWT token" }); + error jwtError = error(AUTH_ERROR_CODE, { message : "Invalid JWT token" }); return jwtError; } } else { - error jwtError = error(INTERNAL_ERROR_CODE, { message : "Invalid JWT token" }); + error jwtError = error(AUTH_ERROR_CODE, { message : "Invalid JWT token" }); return jwtError; } } @@ -87,7 +88,7 @@ function getJWTComponents(string jwtToken) returns (string[])|error { log:printDebug(function() returns string { return "Invalid JWT token :" + jwtToken; }); - error jwtError = error(INTERNAL_ERROR_CODE, { message : "Invalid JWT token" }); + error jwtError = error(AUTH_ERROR_CODE, { message : "Invalid JWT token" }); return jwtError; } return jwtComponents; @@ -116,18 +117,20 @@ function getDecodedJWTComponents(string[] encodedJWTComponents) returns ((json, json jwtHeaderJson = {}; json jwtPayloadJson = {}; - var jsonHeader = parseJson(jwtHeader); + io:StringReader reader = new(jwtHeader); + var jsonHeader = reader.readJson(); if (jsonHeader is json) { jwtHeaderJson = jsonHeader; } else if (jsonHeader is error) { return jsonHeader; } - var jsonPayloaad = parseJson(jwtPayload); - if (jsonPayloaad is json) { - jwtPayloadJson = jsonPayloaad; - } else if (jsonPayloaad is error) { - return jsonPayloaad; + reader = new(jwtPayload); + var jsonPayload = reader.readJson(); + if (jsonPayload is json) { + jwtPayloadJson = jsonPayload; + } else if (jsonPayload is error) { + return jsonPayload; } return (jwtHeaderJson, jwtPayloadJson); } @@ -210,39 +213,39 @@ function parsePayload(json jwtPayloadJson) returns (JwtPayload) { return jwtPayload; } -function validateJWT(string[] encodedJWTComponents, JwtHeader jwtHeader, JwtPayload jwtPayload, JWTValidatorConfig -config) returns (boolean|error) { +function validateJwtRecords(string[] encodedJWTComponents, JwtHeader jwtHeader, JwtPayload jwtPayload, + JWTValidatorConfig config) returns (boolean|error) { if (!validateMandatoryJwtHeaderFields(jwtHeader)) { - error jwtError = error(INTERNAL_ERROR_CODE, + error jwtError = error(AUTH_ERROR_CODE, { message : "Mandatory field signing algorithm(alg) is empty in the given JSON Web Token." }); return jwtError; } if (!validateMandatoryFields(jwtPayload)) { - error jwtError = error(INTERNAL_ERROR_CODE, + error jwtError = error(AUTH_ERROR_CODE, { message : "Mandatory fields(Issuer,Subject, Expiration time or Audience) are empty in the given JSON Web Token." }); return jwtError; } var signatureValidationResult = validateSignature(encodedJWTComponents, jwtHeader, config); if (signatureValidationResult is error) { - error jwtError = error(INTERNAL_ERROR_CODE, { message : signatureValidationResult.reason() }); + error jwtError = error(AUTH_ERROR_CODE, { message : signatureValidationResult.reason() }); return jwtError; } if (!validateIssuer(jwtPayload, config)) { - error jwtError = error(INTERNAL_ERROR_CODE, { message : "JWT contained invalid issuer name : " + jwtPayload.iss }); + error jwtError = error(AUTH_ERROR_CODE, { message : "JWT contained invalid issuer name : " + jwtPayload.iss }); return jwtError; } if (!validateAudience(jwtPayload, config)) { //TODO need to set expected audience or available audience list - error jwtError = error(INTERNAL_ERROR_CODE, { message : "Invalid audience" }); + error jwtError = error(AUTH_ERROR_CODE, { message : "Invalid audience" }); return jwtError; } if (!validateExpirationTime(jwtPayload, config)) { - error jwtError = error(INTERNAL_ERROR_CODE, { message : "JWT token is expired" }); + error jwtError = error(AUTH_ERROR_CODE, { message : "JWT token is expired" }); return jwtError; } //TODO : Validate nbf field of jwtPayload availability first if (!validateNotBeforeTime(jwtPayload)) { - error jwtError = error(INTERNAL_ERROR_CODE, { message : "JWT token is used before Not_Before_Time" }); + error jwtError = error(AUTH_ERROR_CODE, { message : "JWT token is used before Not_Before_Time" }); return jwtError; } //TODO : Need to validate jwt id (jti) and custom claims. @@ -267,12 +270,7 @@ function validateSignature(string[] encodedJWTComponents, JwtHeader jwtHeader, J returns error? { string assertion = encodedJWTComponents[0] + "." + encodedJWTComponents[1]; string signPart = encodedJWTComponents[2]; - TrustStore trustStore = { - certificateAlias : config.certificateAlias, - trustStoreFilePath : config.trustStoreFilePath, - trustStorePassword : config.trustStorePassword - }; - return verifySignature(assertion, signPart, jwtHeader.alg, trustStore); + return verifySignature(assertion, signPart, jwtHeader.alg, config.trustStore, config.certificateAlias); } function validateIssuer(JwtPayload jwtPayload, JWTValidatorConfig config) returns (boolean) { diff --git a/stdlib/auth/src/main/ballerina/auth/ldap_auth_store_provider.bal b/stdlib/auth/src/main/ballerina/auth/ldap_auth_store_provider.bal index 1e7c4a4f8aeb..a58a740cc761 100644 --- a/stdlib/auth/src/main/ballerina/auth/ldap_auth_store_provider.bal +++ b/stdlib/auth/src/main/ballerina/auth/ldap_auth_store_provider.bal @@ -15,6 +15,7 @@ // under the License. import ballerina/runtime; +import ballerina/crypto; # Represents configurations that required for LDAP auth store. # @@ -70,21 +71,11 @@ public type LdapAuthProviderConfig record { # + trustStore - Configures the trust store to be used # + trustedCertFile - A file containing a list of certificates or a single certificate that the client trusts public type SecureClientSocket record { - TrustStore? trustStore = (); + crypto:TrustStore? trustStore = (); string trustedCertFile = ""; !...; }; -# A record for providing trust store related configurations. -# -# + path - Path to the trust store file -# + password - Trust store password -public type TrustStore record { - string path = ""; - string password = ""; - !...; -}; - # Represents Ballerina configuration for LDAP based auth store provider # # + ldapAuthProviderConfig - LDAP auth store configurations diff --git a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/JWSAlgorithm.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSAlgorithm.java similarity index 95% rename from stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/JWSAlgorithm.java rename to stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSAlgorithm.java index e8a7b6c2a4e4..c92d0af0854d 100644 --- a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/JWSAlgorithm.java +++ b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSAlgorithm.java @@ -16,7 +16,7 @@ * under the License. */ -package org.ballerinalang.stdlib.internal.jwt.crypto; +package org.ballerinalang.auth.ldap.jwt.crypto; /** * JSON Web Signature (JWS) algorithm name, represents the {@code alg} header diff --git a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/JWSException.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSException.java similarity index 96% rename from stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/JWSException.java rename to stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSException.java index 02a520026348..eca4a4edbf82 100644 --- a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/JWSException.java +++ b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSException.java @@ -16,7 +16,7 @@ * under the License. */ -package org.ballerinalang.stdlib.internal.jwt.crypto; +package org.ballerinalang.auth.ldap.jwt.crypto; /** * A checked exception for wrapping potential exceptions thrown during JWT signature processing. diff --git a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/JWSSigner.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSSigner.java similarity index 96% rename from stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/JWSSigner.java rename to stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSSigner.java index fcbff2c555b9..8c89208dd010 100644 --- a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/JWSSigner.java +++ b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSSigner.java @@ -16,7 +16,7 @@ * under the License. */ -package org.ballerinalang.stdlib.internal.jwt.crypto; +package org.ballerinalang.auth.ldap.jwt.crypto; /** * JSON web signature. diff --git a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/JWSVerifier.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSVerifier.java similarity index 96% rename from stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/JWSVerifier.java rename to stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSVerifier.java index 9922045eaa5d..957ac48685c3 100644 --- a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/JWSVerifier.java +++ b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSVerifier.java @@ -16,7 +16,7 @@ * under the License. */ -package org.ballerinalang.stdlib.internal.jwt.crypto; +package org.ballerinalang.auth.ldap.jwt.crypto; /** * JSON web signature verifier. diff --git a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/KeyStoreHolder.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/KeyStoreHolder.java similarity index 98% rename from stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/KeyStoreHolder.java rename to stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/KeyStoreHolder.java index 2aefe5c04564..b84214de4a70 100644 --- a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/KeyStoreHolder.java +++ b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/KeyStoreHolder.java @@ -16,7 +16,7 @@ * under the License. */ -package org.ballerinalang.stdlib.internal.jwt.crypto; +package org.ballerinalang.auth.ldap.jwt.crypto; import org.ballerinalang.util.exceptions.BallerinaException; diff --git a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/RSASSAProvider.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/RSASSAProvider.java similarity index 96% rename from stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/RSASSAProvider.java rename to stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/RSASSAProvider.java index 66f8581d2ae8..6dfb3caad086 100644 --- a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/RSASSAProvider.java +++ b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/RSASSAProvider.java @@ -16,7 +16,7 @@ * under the License. */ -package org.ballerinalang.stdlib.internal.jwt.crypto; +package org.ballerinalang.auth.ldap.jwt.crypto; /** * Provides the supported algorithms. diff --git a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/RSASigner.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/RSASigner.java similarity index 97% rename from stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/RSASigner.java rename to stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/RSASigner.java index 98b9a35412f1..6018c3ceb049 100644 --- a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/RSASigner.java +++ b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/RSASigner.java @@ -16,7 +16,7 @@ * under the License. */ -package org.ballerinalang.stdlib.internal.jwt.crypto; +package org.ballerinalang.auth.ldap.jwt.crypto; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; diff --git a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/RSAVerifier.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/RSAVerifier.java similarity index 97% rename from stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/RSAVerifier.java rename to stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/RSAVerifier.java index 1ff67828a6f0..0911c8afa4ea 100644 --- a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/RSAVerifier.java +++ b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/RSAVerifier.java @@ -16,7 +16,7 @@ * under the License. */ -package org.ballerinalang.stdlib.internal.jwt.crypto; +package org.ballerinalang.auth.ldap.jwt.crypto; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; diff --git a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/TrustStoreHolder.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/TrustStoreHolder.java similarity index 98% rename from stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/TrustStoreHolder.java rename to stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/TrustStoreHolder.java index 8c6b7eb61f48..a66f03cccb66 100644 --- a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/crypto/TrustStoreHolder.java +++ b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/TrustStoreHolder.java @@ -16,7 +16,7 @@ * under the License. */ -package org.ballerinalang.stdlib.internal.jwt.crypto; +package org.ballerinalang.auth.ldap.jwt.crypto; import org.ballerinalang.util.exceptions.BallerinaException; diff --git a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/signature/PathResolver.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/signature/PathResolver.java similarity index 97% rename from stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/signature/PathResolver.java rename to stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/signature/PathResolver.java index 52797477cbdb..ee8976ca0c04 100644 --- a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/signature/PathResolver.java +++ b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/signature/PathResolver.java @@ -16,7 +16,7 @@ * under the License. */ -package org.ballerinalang.stdlib.internal.jwt.signature; +package org.ballerinalang.auth.ldap.jwt.signature; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/signature/Sign.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/signature/Sign.java similarity index 78% rename from stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/signature/Sign.java rename to stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/signature/Sign.java index a6c309e6d5d4..996891f40955 100644 --- a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/signature/Sign.java +++ b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/signature/Sign.java @@ -16,8 +16,11 @@ * under the License. */ -package org.ballerinalang.stdlib.internal.jwt.signature; +package org.ballerinalang.auth.ldap.jwt.signature; +import org.ballerinalang.auth.ldap.jwt.crypto.JWSSigner; +import org.ballerinalang.auth.ldap.jwt.crypto.KeyStoreHolder; +import org.ballerinalang.auth.ldap.jwt.crypto.RSASigner; import org.ballerinalang.bre.Context; import org.ballerinalang.bre.bvm.BLangVMErrors; import org.ballerinalang.bre.bvm.BlockingNativeCallableUnit; @@ -28,9 +31,6 @@ import org.ballerinalang.natives.annotations.Argument; import org.ballerinalang.natives.annotations.BallerinaFunction; import org.ballerinalang.natives.annotations.ReturnType; -import org.ballerinalang.stdlib.internal.jwt.crypto.JWSSigner; -import org.ballerinalang.stdlib.internal.jwt.crypto.KeyStoreHolder; -import org.ballerinalang.stdlib.internal.jwt.crypto.RSASigner; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,35 +42,37 @@ * @since 0.964.0 */ @BallerinaFunction( - orgName = "ballerina", packageName = "internal", + orgName = "ballerina", packageName = "auth", functionName = "sign", args = { @Argument(name = "data", type = TypeKind.STRING), @Argument(name = "algorithm", type = TypeKind.STRING), @Argument(name = "keyStore", type = TypeKind.RECORD, structType = "KeyStore", - structPackage = "ballerina/internal") + structPackage = "ballerina/internal"), + @Argument(name = "keyAlias", type = TypeKind.STRING), + @Argument(name = "keyPassword", type = TypeKind.STRING) }, returnType = {@ReturnType(type = TypeKind.STRING)}, isPublic = true ) public class Sign extends BlockingNativeCallableUnit { private static final Logger log = LoggerFactory.getLogger(Sign.class); - private static final String KEY_ALIAS_FIELD = "keyAlias"; - private static final String KEY_PASSWORD_FIELD = "keyPassword"; - private static final String KEY_STORE_PATH_FIELD = "keyStoreFilePath"; - private static final String KEY_STORE_PASSWORD_FIELD = "keyStorePassword"; + private static final String KEY_STORE_PATH_FIELD = "path"; + private static final String KEY_STORE_PASSWORD_FIELD = "password"; @Override public void execute(Context context) { String data = context.getStringArgument(0); String algorithm = context.getStringArgument(1); + String keyAlias = context.getStringArgument(2); + String keyPasswordStr = context.getStringArgument(3); BMap keyStore = (BMap) context.getRefArgument(0); - char[] keyPassword = keyStore.get(KEY_PASSWORD_FIELD).stringValue().toCharArray(); + char[] keyPassword = keyPasswordStr.toCharArray(); char[] keyStorePassword = keyStore.get(KEY_STORE_PASSWORD_FIELD).stringValue().toCharArray(); String signature = null; PrivateKey privateKey; try { - privateKey = KeyStoreHolder.getInstance().getPrivateKey(keyStore.get(KEY_ALIAS_FIELD).stringValue(), + privateKey = KeyStoreHolder.getInstance().getPrivateKey(keyAlias, keyPassword, PathResolver.getResolvedPath(keyStore.get(KEY_STORE_PATH_FIELD).stringValue()), keyStorePassword); JWSSigner signer = new RSASigner(privateKey); diff --git a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/signature/VerifySignature.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/signature/VerifySignature.java similarity index 79% rename from stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/signature/VerifySignature.java rename to stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/signature/VerifySignature.java index f277c6399fa9..2c8a09df848d 100644 --- a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/jwt/signature/VerifySignature.java +++ b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/signature/VerifySignature.java @@ -16,8 +16,11 @@ * under the License. */ -package org.ballerinalang.stdlib.internal.jwt.signature; +package org.ballerinalang.auth.ldap.jwt.signature; +import org.ballerinalang.auth.ldap.jwt.crypto.JWSVerifier; +import org.ballerinalang.auth.ldap.jwt.crypto.RSAVerifier; +import org.ballerinalang.auth.ldap.jwt.crypto.TrustStoreHolder; import org.ballerinalang.bre.Context; import org.ballerinalang.bre.bvm.BLangVMErrors; import org.ballerinalang.bre.bvm.BlockingNativeCallableUnit; @@ -27,9 +30,6 @@ import org.ballerinalang.natives.annotations.Argument; import org.ballerinalang.natives.annotations.BallerinaFunction; import org.ballerinalang.natives.annotations.ReturnType; -import org.ballerinalang.stdlib.internal.jwt.crypto.JWSVerifier; -import org.ballerinalang.stdlib.internal.jwt.crypto.RSAVerifier; -import org.ballerinalang.stdlib.internal.jwt.crypto.TrustStoreHolder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -47,14 +47,15 @@ * @since 0.964.0 */ @BallerinaFunction( - orgName = "ballerina", packageName = "internal", + orgName = "ballerina", packageName = "auth", functionName = "verifySignature", args = { @Argument(name = "data", type = TypeKind.STRING), @Argument(name = "signature", type = TypeKind.STRING), @Argument(name = "algorithm", type = TypeKind.STRING), @Argument(name = "trustStore", type = TypeKind.RECORD, structType = "TrustStoreHolder", - structPackage = "ballerina/internal") + structPackage = "ballerina/internal"), + @Argument(name = "keyAlias", type = TypeKind.STRING) }, returnType = { @ReturnType(type = TypeKind.OBJECT, structType = STRUCT_GENERIC_ERROR, structPackage = @@ -64,23 +65,23 @@ ) public class VerifySignature extends BlockingNativeCallableUnit { private static final Logger log = LoggerFactory.getLogger(VerifySignature.class); - private static final String CERT_ALIAS = "certificateAlias"; - private static final String TRUST_STORE_PATH = "trustStoreFilePath"; - private static final String TRUST_STORE_PASSWORD = "trustStorePassword"; + private static final String TRUST_STORE_PATH = "path"; + private static final String TRUST_STORE_PASSWORD = "password"; @Override public void execute(Context context) { String data = context.getStringArgument(0); String signature = context.getStringArgument(1); String algorithm = context.getStringArgument(2); + String keyAlias = context.getStringArgument(3); BMap trustStore = (BMap) context.getRefArgument(0); char[] trustStorePassword = trustStore.get(TRUST_STORE_PASSWORD).stringValue().toCharArray(); RSAPublicKey publicKey; String msg = null; try { X509Certificate certificate = (X509Certificate) TrustStoreHolder.getInstance().getTrustedCertificate( - trustStore.get(CERT_ALIAS).stringValue(), - PathResolver.getResolvedPath(trustStore.get(TRUST_STORE_PATH).stringValue()), trustStorePassword); + keyAlias, PathResolver.getResolvedPath(trustStore.get(TRUST_STORE_PATH).stringValue()), + trustStorePassword); certificate.checkValidity(); publicKey = (RSAPublicKey) certificate.getPublicKey(); @@ -89,9 +90,9 @@ public void execute(Context context) { msg = "Invalid signature"; } } catch (CertificateExpiredException e) { - msg = "Certificate with alias " + trustStore.get(CERT_ALIAS) + " has expired"; + msg = "Certificate with alias " + keyAlias + " has expired"; } catch (CertificateNotYetValidException e) { - msg = "Certificate with alias " + trustStore.get(CERT_ALIAS) + " is not yet valid"; + msg = "Certificate with alias " + keyAlias + " is not yet valid"; } catch (Exception e) { msg = "Error in verifying signature"; log.error(msg, e); diff --git a/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JWTAuthenticatorTest.java b/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JWTAuthenticatorTest.java index a64d89b19445..5c138f7087a4 100644 --- a/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JWTAuthenticatorTest.java +++ b/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JWTAuthenticatorTest.java @@ -18,6 +18,8 @@ package org.ballerinalang.stdlib.auth.jwt; +import org.ballerinalang.auth.ldap.jwt.crypto.JWSSigner; +import org.ballerinalang.auth.ldap.jwt.crypto.RSASigner; import org.ballerinalang.config.ConfigRegistry; import org.ballerinalang.launcher.util.BCompileUtil; import org.ballerinalang.launcher.util.BRunUtil; @@ -26,8 +28,6 @@ import org.ballerinalang.model.values.BMap; import org.ballerinalang.model.values.BString; import org.ballerinalang.model.values.BValue; -import org.ballerinalang.stdlib.internal.jwt.crypto.JWSSigner; -import org.ballerinalang.stdlib.internal.jwt.crypto.RSASigner; import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; diff --git a/stdlib/internal/src/test/java/org/ballerinalang/internal/jwt/JwtTest.java b/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JwtTest.java similarity index 94% rename from stdlib/internal/src/test/java/org/ballerinalang/internal/jwt/JwtTest.java rename to stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JwtTest.java index 02fdedc6f8d3..f861acbdcf71 100644 --- a/stdlib/internal/src/test/java/org/ballerinalang/internal/jwt/JwtTest.java +++ b/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JwtTest.java @@ -16,7 +16,7 @@ * under the License. */ -package org.ballerinalang.internal.jwt; +package org.ballerinalang.stdlib.auth.jwt; import org.ballerinalang.launcher.util.BCompileUtil; import org.ballerinalang.launcher.util.BRunUtil; @@ -46,9 +46,9 @@ public class JwtTest { @BeforeClass public void setup() throws Exception { keyStorePath = getClass().getClassLoader().getResource( - "datafiles/keyStore/ballerinaKeystore.p12").getPath(); + "datafiles/keystore/ballerinaKeystore.p12").getPath(); trustStorePath = getClass().getClassLoader().getResource( - "datafiles/keyStore/ballerinaTruststore.p12").getPath(); + "datafiles/keystore/ballerinaTruststore.p12").getPath(); resourceRoot = new File(getClass().getProtectionDomain().getCodeSource(). getLocation().getPath()).getAbsolutePath(); Path sourceRoot = Paths.get(resourceRoot, "test-src", "jwt"); diff --git a/stdlib/internal/src/test/java/org/ballerinalang/internal/jwt/crypto/JWTSignerTest.java b/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/crypto/JWTSignerTest.java similarity index 91% rename from stdlib/internal/src/test/java/org/ballerinalang/internal/jwt/crypto/JWTSignerTest.java rename to stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/crypto/JWTSignerTest.java index 159e0740f161..30ca42675892 100644 --- a/stdlib/internal/src/test/java/org/ballerinalang/internal/jwt/crypto/JWTSignerTest.java +++ b/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/crypto/JWTSignerTest.java @@ -16,10 +16,10 @@ * under the License. */ -package org.ballerinalang.internal.jwt.crypto; +package org.ballerinalang.stdlib.auth.jwt.crypto; -import org.ballerinalang.stdlib.internal.jwt.crypto.JWSSigner; -import org.ballerinalang.stdlib.internal.jwt.crypto.RSASigner; +import org.ballerinalang.auth.ldap.jwt.crypto.JWSSigner; +import org.ballerinalang.auth.ldap.jwt.crypto.RSASigner; import org.testng.Assert; import org.testng.annotations.Test; @@ -51,7 +51,7 @@ public void testRSA256Verifier() throws Exception { private PrivateKey getPrivateKey() throws Exception { KeyStore keyStore; InputStream file = new FileInputStream(new File(getClass().getClassLoader().getResource( - "datafiles/keyStore/ballerinaKeystore.p12").getPath())); + "datafiles/keystore/ballerinaKeystore.p12").getPath())); keyStore = java.security.KeyStore.getInstance("pkcs12"); keyStore.load(file, "ballerina".toCharArray()); KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry("ballerina", new KeyStore diff --git a/stdlib/internal/src/test/java/org/ballerinalang/internal/jwt/crypto/JWTVerifierTest.java b/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/crypto/JWTVerifierTest.java similarity index 91% rename from stdlib/internal/src/test/java/org/ballerinalang/internal/jwt/crypto/JWTVerifierTest.java rename to stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/crypto/JWTVerifierTest.java index e90546fa8dd2..6243c5144a65 100644 --- a/stdlib/internal/src/test/java/org/ballerinalang/internal/jwt/crypto/JWTVerifierTest.java +++ b/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/crypto/JWTVerifierTest.java @@ -16,10 +16,10 @@ * under the License. */ -package org.ballerinalang.internal.jwt.crypto; +package org.ballerinalang.stdlib.auth.jwt.crypto; -import org.ballerinalang.stdlib.internal.jwt.crypto.JWSVerifier; -import org.ballerinalang.stdlib.internal.jwt.crypto.RSAVerifier; +import org.ballerinalang.auth.ldap.jwt.crypto.JWSVerifier; +import org.ballerinalang.auth.ldap.jwt.crypto.RSAVerifier; import org.testng.Assert; import org.testng.annotations.Test; @@ -53,7 +53,7 @@ public void testRSA256Verifier() throws Exception { private RSAPublicKey getRSAPublicKey() throws Exception { KeyStore trustStore; InputStream file = new FileInputStream(new File(getClass().getClassLoader().getResource( - "datafiles/keyStore/ballerinaTruststore.p12").getPath())); + "datafiles/keystore/ballerinaTruststore.p12").getPath())); trustStore = java.security.KeyStore.getInstance("pkcs12"); trustStore.load(file, "ballerina".toCharArray()); Certificate publicCertificate = trustStore.getCertificate("ballerina"); diff --git a/stdlib/auth/src/test/resources/test-src/jwt-authenticator-test.bal b/stdlib/auth/src/test/resources/test-src/jwt-authenticator-test.bal index e4041129b76b..ad80ddffdbe5 100644 --- a/stdlib/auth/src/test/resources/test-src/jwt-authenticator-test.bal +++ b/stdlib/auth/src/test/resources/test-src/jwt-authenticator-test.bal @@ -1,21 +1,26 @@ import ballerina/auth; +import ballerina/crypto; function testJwtAuthenticatorCreationWithCache() returns (auth:JWTAuthProvider) { - auth:JWTAuthProviderConfig jwtConfig = {}; - jwtConfig.issuer = "wso2"; - jwtConfig.audience = "ballerina"; - jwtConfig.certificateAlias = "ballerina"; + crypto:TrustStore trustStore = { path: trustStorePath, password: "ballerina" }; + auth:JWTAuthProviderConfig jwtConfig = { + issuer: "wso2", + audience: "ballerina", + certificateAlias: "ballerina", + trustStore: trustStore + }; auth:JWTAuthProvider jwtAuthProvider = new(jwtConfig); return jwtAuthProvider; } function testAuthenticationSuccess(string jwtToken, string trustStorePath) returns (boolean|error) { - auth:JWTAuthProviderConfig jwtConfig = {}; - jwtConfig.issuer = "wso2"; - jwtConfig.audience = "ballerina"; - jwtConfig.certificateAlias = "ballerina"; - jwtConfig.trustStoreFilePath = trustStorePath; - jwtConfig.trustStorePassword = "ballerina"; + crypto:TrustStore trustStore = { path: trustStorePath, password: "ballerina" }; + auth:JWTAuthProviderConfig jwtConfig = { + issuer: "wso2", + audience: "ballerina", + certificateAlias: "ballerina", + trustStore: trustStore + }; auth:JWTAuthProvider jwtAuthProvider = new(jwtConfig); return jwtAuthProvider.authenticate(jwtToken); } diff --git a/stdlib/auth/src/test/resources/test-src/jwt/jwt-test.bal b/stdlib/auth/src/test/resources/test-src/jwt/jwt-test.bal new file mode 100644 index 000000000000..10cdc11bee42 --- /dev/null +++ b/stdlib/auth/src/test/resources/test-src/jwt/jwt-test.bal @@ -0,0 +1,38 @@ +import ballerina/internal; +import ballerina/time; +import ballerina/auth; +import ballerina/crypto; + +function testIssueJwt (string keyStorePath) returns (string)|error { + auth:JwtHeader header = {}; + header.alg = "RS256"; + header.typ = "JWT"; + + auth:JwtPayload payload = {}; + payload.sub = "John"; + payload.iss = "wso2"; + payload.jti = "100078234ba23"; + payload.aud = ["ballerina", "ballerinaSamples"]; + payload.exp = time:currentTime().time/1000 + 600; + + crypto:KeyStore keyStore = { path: keyStorePath, password: "ballerina" }; + return auth:issueJwt(header, payload, keyStore, "ballerina", "ballerina"); +} + +function testValidateJwt (string jwtToken, string trustStorePath) returns boolean|error { + crypto:TrustStore trustStore = { path: trustStorePath, password: "ballerina" }; + auth:JWTValidatorConfig config = { + issuer: "wso2", + certificateAlias: "ballerina", + audience: "ballerinaSamples", + clockSkew: 60, + trustStore: trustStore + }; + + var result = auth:validateJwt(jwtToken, config); + if (result is auth:JwtPayload) { + return true; + } else { + return result; + } +} diff --git a/stdlib/http/src/test/java/org/ballerinalang/stdlib/auth/JWTAuthnHandlerTest.java b/stdlib/http/src/test/java/org/ballerinalang/stdlib/auth/JWTAuthnHandlerTest.java index a316f4e54c33..eb28cd2146c7 100644 --- a/stdlib/http/src/test/java/org/ballerinalang/stdlib/auth/JWTAuthnHandlerTest.java +++ b/stdlib/http/src/test/java/org/ballerinalang/stdlib/auth/JWTAuthnHandlerTest.java @@ -25,8 +25,8 @@ import org.ballerinalang.model.values.BBoolean; import org.ballerinalang.model.values.BString; import org.ballerinalang.model.values.BValue; -import org.ballerinalang.stdlib.internal.jwt.crypto.JWSSigner; -import org.ballerinalang.stdlib.internal.jwt.crypto.RSASigner; +import org.ballerinalang.auth.ldap.jwt.crypto.JWSSigner; +import org.ballerinalang.auth.ldap.jwt.crypto.RSASigner; import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; diff --git a/stdlib/internal/src/main/ballerina/internal/file.bal b/stdlib/internal/src/main/ballerina/internal/file.bal index 726a941d0334..8c0d1eb2a58a 100644 --- a/stdlib/internal/src/main/ballerina/internal/file.bal +++ b/stdlib/internal/src/main/ballerina/internal/file.bal @@ -14,6 +14,8 @@ // specific language governing permissions and limitations // under the License. +import ballerina/time; + # Reference to the file location. public type Path object { private string root; diff --git a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/ParseJson.java b/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/ParseJson.java deleted file mode 100644 index 4ae90d0304e6..000000000000 --- a/stdlib/internal/src/main/java/org/ballerinalang/stdlib/internal/ParseJson.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2018, 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.ballerinalang.stdlib.internal; - -import org.ballerinalang.bre.Context; -import org.ballerinalang.bre.bvm.BlockingNativeCallableUnit; -import org.ballerinalang.model.types.TypeKind; -import org.ballerinalang.model.util.JsonParser; -import org.ballerinalang.model.values.BError; -import org.ballerinalang.model.values.BValue; -import org.ballerinalang.natives.annotations.Argument; -import org.ballerinalang.natives.annotations.BallerinaFunction; -import org.ballerinalang.natives.annotations.ReturnType; -import org.ballerinalang.stdlib.io.utils.Utils; - -/** - * Extern function ballerina/internal:parseJson. - * - * @since 0.980.0 - */ -@BallerinaFunction( - orgName = "ballerina", packageName = "internal", - functionName = "parseJson", - args = {@Argument(name = "s", type = TypeKind.STRING)}, - returnType = {@ReturnType(type = TypeKind.JSON)}, - isPublic = true) -public class ParseJson extends BlockingNativeCallableUnit { - - @Override - public void execute(Context context) { - try { - String value = context.getStringArgument(0); - BValue json = JsonParser.parse(value); - context.setReturnValues(json); - } catch (Exception e) { - BError error = Utils.createConversionError(context, "Failed to parse json string: " + e.getMessage()); - context.setReturnValues(error); - } - } -} diff --git a/stdlib/internal/src/test/resources/datafiles/keyStore/ballerinaKeystore.p12 b/stdlib/internal/src/test/resources/datafiles/keyStore/ballerinaKeystore.p12 deleted file mode 100644 index cd38805e8da34f215df21f0e8c55b1793c2273cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2594 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
    {@^Iemn_5nlD_>WCf27{%6TL9L1*|DU*{^}{- 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 ((BError) returns[0]).getDetails()).get("message").stringValue(); - Assert.assertTrue(errorMsg.contains("Failed to parse json string:")); - } -} diff --git a/tests/ballerina-unit-test/src/test/java/org/ballerinalang/test/types/json/JSONTest.java b/tests/ballerina-unit-test/src/test/java/org/ballerinalang/test/types/json/JSONTest.java index ad862d69b9cc..9dad0edcc907 100644 --- a/tests/ballerina-unit-test/src/test/java/org/ballerinalang/test/types/json/JSONTest.java +++ b/tests/ballerina-unit-test/src/test/java/org/ballerinalang/test/types/json/JSONTest.java @@ -172,7 +172,7 @@ public void testParseMalformedString() { BValue[] returns = BRunUtil.invoke(compileResult, "testParse", args); Assert.assertTrue(returns[0] instanceof BError); String errorMsg = ((BMap) ((BError) returns[0]).details).get("message").stringValue(); - Assert.assertEquals(errorMsg, "Failed to parse json string: unrecognized token 'some' at line: 1 column: 6"); + Assert.assertEquals(errorMsg, "unrecognized token 'some' at line: 1 column: 6"); } @Test(description = "Convert complex json object in to xml") diff --git a/tests/ballerina-unit-test/src/test/resources/test-src/nativeimpl/functions/util-test.bal b/tests/ballerina-unit-test/src/test/resources/test-src/nativeimpl/functions/util-test.bal deleted file mode 100644 index 96cf539283ff..000000000000 --- a/tests/ballerina-unit-test/src/test/resources/test-src/nativeimpl/functions/util-test.bal +++ /dev/null @@ -1,5 +0,0 @@ -import ballerina/internal; - -function testParseJson (string s) returns (json|error) { - return internal:parseJson(s); -} diff --git a/tests/ballerina-unit-test/src/test/resources/test-src/types/jsontype/json-test.bal b/tests/ballerina-unit-test/src/test/resources/test-src/types/jsontype/json-test.bal index c83241f0a360..72a6ac7355a0 100644 --- a/tests/ballerina-unit-test/src/test/resources/test-src/types/jsontype/json-test.bal +++ b/tests/ballerina-unit-test/src/test/resources/test-src/types/jsontype/json-test.bal @@ -12,7 +12,8 @@ function toString (json msg) returns (string?) { } function testParse (string jsonStr) returns (json | error) { - return internal:parseJson(jsonStr); + io:StringReader reader = new(jsonStr); + return reader.readJson(); } function testGetKeys () returns (string[]?, string[]?, string[]?, string[]?) { @@ -61,7 +62,8 @@ function testToXMLWithOptions (json msg) returns (xml | error?) { function testStringToJSONConversion() returns (json | error) { string s = "{\"foo\": \"bar\"}"; - return internal:parseJson(s); + io:StringReader reader = new(s); + return reader.readJson(); } function testJSONArrayToJsonAssignment() returns (json) { From 0f0df793b7a17d7c1d80f15bbfc03ad4dd7a8126 Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Tue, 5 Feb 2019 10:25:00 +0530 Subject: [PATCH 02/26] Moving JWT issuer and validator to auth --- .../ballerinalang/stdlib/auth/jwt/JWTAuthenticatorTest.java | 3 ++- .../src/test/resources/test-src/jwt-authenticator-test.bal | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JWTAuthenticatorTest.java b/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JWTAuthenticatorTest.java index 5c138f7087a4..c27de9cef94b 100644 --- a/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JWTAuthenticatorTest.java +++ b/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JWTAuthenticatorTest.java @@ -119,7 +119,8 @@ public void setup() throws Exception { @Test(description = "Test case for creating JWT authenticator with a cache") public void testCreateJwtAuthenticatorWithCache() { - BValue[] returns = BRunUtil.invoke(compileResult, "testJwtAuthenticatorCreationWithCache"); + BValue[] inputBValues = {new BString(trustStorePath)}; + BValue[] returns = BRunUtil.invoke(compileResult, "testJwtAuthenticatorCreationWithCache", inputBValues); Assert.assertNotNull(returns); Assert.assertTrue(returns[0] instanceof BMap); } diff --git a/stdlib/auth/src/test/resources/test-src/jwt-authenticator-test.bal b/stdlib/auth/src/test/resources/test-src/jwt-authenticator-test.bal index ad80ddffdbe5..c80cbe97c553 100644 --- a/stdlib/auth/src/test/resources/test-src/jwt-authenticator-test.bal +++ b/stdlib/auth/src/test/resources/test-src/jwt-authenticator-test.bal @@ -1,7 +1,7 @@ import ballerina/auth; import ballerina/crypto; -function testJwtAuthenticatorCreationWithCache() returns (auth:JWTAuthProvider) { +function testJwtAuthenticatorCreationWithCache(string trustStorePath) returns (auth:JWTAuthProvider) { crypto:TrustStore trustStore = { path: trustStorePath, password: "ballerina" }; auth:JWTAuthProviderConfig jwtConfig = { issuer: "wso2", From 21cdf4386a6447bcaa90e84cd5d1a081c9cc3c5b Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Tue, 5 Feb 2019 16:47:14 +0530 Subject: [PATCH 03/26] Correct HTTP module accordingly --- .../auth/jwt_supported_auth_provider_util.bal | 22 +++++----- .../main/ballerina/http/service_endpoint.bal | 43 ++++++++++++------- .../test-src/auth/jwt-authn-handler-test.bal | 17 +++++--- 3 files changed, 48 insertions(+), 34 deletions(-) diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal b/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal index ecbc585129c6..adbdc6c6ffe1 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal @@ -18,26 +18,25 @@ import ballerina/internal; import ballerina/runtime; import ballerina/system; import ballerina/time; +import ballerina/crypto; # Represents authentication provider configurations that supports generating JWT for client interactions. # # + issuer - Expected JWT token issuer # + audience - Expected JWT token audience # + expTime - Expiry time for newly issued JWT tokens +# + keyStore - Keystore containing the signing key # + keyAlias - Key alias for signing newly issued JWT tokens # + keyPassword - Key password for signing newly issued JWT tokens -# + keyStoreFilePath - Path to the key-store file containing signing key -# + keyStorePassword - Password of the key-store file containing signing key # + signingAlg - Signing algorithm for signing newly issued JWT tokens -public type InferredJwtAuthProviderConfig record { - string issuer = ""; - string audience = ""; + public type InferredJwtAuthProviderConfig record { + string issuer; + string audience; int expTime = 0; - string keyAlias = ""; - string keyPassword = ""; - string keyStoreFilePath = ""; - string keyStorePassword = ""; - string signingAlg = ""; + crypto:KeyStore keyStore; + string keyAlias; + string keyPassword; + string signingAlg; !...; }; @@ -48,8 +47,7 @@ public type InferredJwtAuthProviderConfig record { function setAuthToken(string username, InferredJwtAuthProviderConfig authConfig) { JwtHeader header = createHeader(authConfig); JwtPayload payload = createPayload(username, authConfig); - crypto:KeyStore keyStore = { path: authConfig.keyStoreFilePath, password: authConfig.keyStorePassword }; - var token = issueJwt(header, payload, keyStore, authConfig.keyAlias, authConfig.keyPassword); + var token = issueJwt(header, payload, authConfig.keyStore, authConfig.keyAlias, authConfig.keyPassword); if (token is string) { runtime:AuthContext authContext = runtime:getInvocationContext().authContext; authContext.scheme = "jwt"; diff --git a/stdlib/http/src/main/ballerina/http/service_endpoint.bal b/stdlib/http/src/main/ballerina/http/service_endpoint.bal index a98bdcc728cb..f6ee70361f7f 100644 --- a/stdlib/http/src/main/ballerina/http/service_endpoint.bal +++ b/stdlib/http/src/main/ballerina/http/service_endpoint.bal @@ -17,6 +17,7 @@ import ballerina/auth; import ballerina/log; import ballerina/system; +import ballerina/crypto; ///////////////////////////// /// HTTP Listener Endpoint /// @@ -381,13 +382,19 @@ function createAuthHandler(AuthProvider authProvider, string instanceId) returns HttpBasicAuthnHandler basicAuthHandler = new(authStoreProvider); return basicAuthHandler; } else if (authProvider.scheme == AUTH_SCHEME_JWT){ - auth:JWTAuthProviderConfig jwtConfig = {}; - jwtConfig.issuer = authProvider.issuer; - jwtConfig.audience = authProvider.audience; - jwtConfig.certificateAlias = authProvider.certificateAlias; - jwtConfig.clockSkew = authProvider.clockSkew; - jwtConfig.trustStoreFilePath = authProvider.trustStore.path ?: ""; - jwtConfig.trustStorePassword = authProvider.trustStore.password ?: ""; + string trustStorePath = authProvider.trustStore.path ?: ""; + string trustStorePassword = authProvider.trustStore.password ?: ""; + crypto:TrustStore trustStore = { + path: trustStorePath, + password: trustStorePassword + }; + auth:JWTAuthProviderConfig jwtConfig = { + issuer: authProvider.issuer, + audience: authProvider.audience, + certificateAlias: authProvider.certificateAlias, + clockSkew: authProvider.clockSkew, + trustStore: trustStore + }; auth:JWTAuthProvider jwtAuthProvider = new(jwtConfig); HttpJwtAuthnHandler jwtAuthnHandler = new(jwtAuthProvider); return jwtAuthnHandler; @@ -404,15 +411,19 @@ function getInferredJwtAuthProviderConfig(AuthProvider authProvider) returns aut int defaultExpTime = 300; // in seconds string defaultSignAlg = "RS256"; - auth:InferredJwtAuthProviderConfig jwtAuthConfig = {}; - jwtAuthConfig.issuer = authProvider.issuer == "" ? defaultIssuer : authProvider.issuer; - jwtAuthConfig.expTime = authProvider.expTime == 0 ? defaultExpTime : authProvider.expTime; - jwtAuthConfig.signingAlg = authProvider.signingAlg == "" ? defaultSignAlg : authProvider.signingAlg; - jwtAuthConfig.audience = authProvider.audience == "" ? defaultAudience : authProvider.audience; - jwtAuthConfig.keyAlias = authProvider.keyAlias; - jwtAuthConfig.keyPassword = authProvider.keyPassword; - jwtAuthConfig.keyStoreFilePath = authProvider.keyStore.path ?: ""; - jwtAuthConfig.keyStorePassword = authProvider.keyStore.password ?: ""; + crypto:KeyStore keyStore = { + path: authProvider.keyStore.path ?: "", + password: authProvider.keyStore.password ?: "" + }; + auth:InferredJwtAuthProviderConfig jwtAuthConfig = { + issuer: authProvider.issuer == "" ? defaultIssuer : authProvider.issuer, + expTime: authProvider.expTime == 0 ? defaultExpTime : authProvider.expTime, + signingAlg: authProvider.signingAlg == "" ? defaultSignAlg : authProvider.signingAlg, + audience: authProvider.audience == "" ? defaultAudience : authProvider.audience, + keyAlias: authProvider.keyAlias, + keyPassword: authProvider.keyPassword, + keyStore: keyStore + }; return jwtAuthConfig; } diff --git a/stdlib/http/src/test/resources/test-src/auth/jwt-authn-handler-test.bal b/stdlib/http/src/test/resources/test-src/auth/jwt-authn-handler-test.bal index c96c439ac1d7..8a6815527a32 100644 --- a/stdlib/http/src/test/resources/test-src/auth/jwt-authn-handler-test.bal +++ b/stdlib/http/src/test/resources/test-src/auth/jwt-authn-handler-test.bal @@ -1,5 +1,6 @@ import ballerina/auth; import ballerina/http; +import ballerina/crypto; function testCanHandleHttpJwtAuthWithoutHeader() returns (boolean) { http:HttpJwtAuthnHandler handler = new(createJwtAuthProvider("ballerina/security/ballerinaTruststore.p12")); @@ -42,12 +43,16 @@ function createRequest() returns (http:Request) { } function createJwtAuthProvider(string trustStorePath) returns auth:JWTAuthProvider { - auth:JWTAuthProviderConfig jwtConfig = {}; - jwtConfig.issuer = "wso2"; - jwtConfig.audience = "ballerina"; - jwtConfig.certificateAlias = "ballerina"; - jwtConfig.trustStoreFilePath = trustStorePath; - jwtConfig.trustStorePassword = "ballerina"; + crypto:TrustStore trustStore = { + path: trustStorePath, + password: "ballerina" + }; + auth:JWTAuthProviderConfig jwtConfig = { + issuer: "wso2", + audience: "ballerina", + certificateAlias: "ballerina", + trustStore: trustStore + }; auth:JWTAuthProvider jwtAuthProvider = new(jwtConfig); return jwtAuthProvider; } From 2d6dfb6fd8638ab46eacbb3e662bd598da6f5d7a Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Tue, 5 Feb 2019 17:54:56 +0530 Subject: [PATCH 04/26] Add RSA signature verification support --- .../src/main/ballerina/crypto/crypto.bal | 44 +++++++++++++ .../stdlib/crypto/CryptoUtils.java | 24 +++++++ .../nativeimpl/VerifyRsaMd5Signature.java | 56 +++++++++++++++++ .../nativeimpl/VerifyRsaSha1Signature.java | 56 +++++++++++++++++ .../nativeimpl/VerifyRsaSha256Signature.java | 56 +++++++++++++++++ .../nativeimpl/VerifyRsaSha384Signature.java | 56 +++++++++++++++++ .../nativeimpl/VerifyRsaSha512Signature.java | 56 +++++++++++++++++ .../stdlib/crypto/CryptoTest.java | 41 ++++++++++++ .../resources/test-src/crypto/crypto-test.bal | 50 +++++++++++++++ .../encoding/nativeimpl/DecodeBase64Url.java | 63 +++++++++++++++++++ .../encoding/nativeimpl/EncodeBase64Url.java | 55 ++++++++++++++++ 11 files changed, 557 insertions(+) create mode 100644 stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/VerifyRsaMd5Signature.java create mode 100644 stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/VerifyRsaSha1Signature.java create mode 100644 stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/VerifyRsaSha256Signature.java create mode 100644 stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/VerifyRsaSha384Signature.java create mode 100644 stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/VerifyRsaSha512Signature.java create mode 100644 stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/DecodeBase64Url.java create mode 100644 stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/EncodeBase64Url.java diff --git a/stdlib/crypto/src/main/ballerina/crypto/crypto.bal b/stdlib/crypto/src/main/ballerina/crypto/crypto.bal index 947f872659ca..8dd8c5dcf1a1 100644 --- a/stdlib/crypto/src/main/ballerina/crypto/crypto.bal +++ b/stdlib/crypto/src/main/ballerina/crypto/crypto.bal @@ -170,6 +170,50 @@ public extern function signRsaSha384(byte[] input, PrivateKey privateKey) return # + return - The generated signature or error if private key is invalid public extern function signRsaSha512(byte[] input, PrivateKey privateKey) returns byte[]|error; +# Verify RSA-MD5 based signature. +# +# + data - The content to be verified +# + signature - Signature value +# + publicKey - Public key used for verification +# + return - Validity of the signature or error if public key is invalid +public extern function verifyRsaMd5Signature(byte[] data, byte[] signature, PublicKey publicKey) returns boolean|error; + +# Verify RSA-SHA1 based signature. +# +# + data - The content to be verified +# + signature - Signature value +# + publicKey - Public key used for verification +# + return - Validity of the signature or error if public key is invalid +public extern function verifyRsaSha1Signature(byte[] data, byte[] signature, PublicKey publicKey) +returns boolean|error; + +# Verify RSA-SHA256 based signature. +# +# + data - The content to be verified +# + signature - Signature value +# + publicKey - Public key used for verification +# + return - Validity of the signature or error if public key is invalid +public extern function verifyRsaSha256Signature(byte[] data, byte[] signature, PublicKey publicKey) +returns boolean|error; + +# Verify RSA-SHA384 based signature. +# +# + data - The content to be verified +# + signature - Signature value +# + publicKey - Public key used for verification +# + return - Validity of the signature or error if public key is invalid +public extern function verifyRsaSha384Signature(byte[] data, byte[] signature, PublicKey publicKey) +returns boolean|error; + +# Verify RSA-SHA512 based signature. +# +# + data - The content to be verified +# + signature - Signature value +# + publicKey - Public key used for verification +# + return - Validity of the signature or error if public key is invalid +public extern function verifyRsaSha512Signature(byte[] data, byte[] signature, PublicKey publicKey) +returns boolean|error; + # Read a private key from the provided PKCS#12 archive file. # # + keyStore - Key store configuration diff --git a/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/CryptoUtils.java b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/CryptoUtils.java index a7022e1f3bcd..b970945c1dd6 100644 --- a/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/CryptoUtils.java +++ b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/CryptoUtils.java @@ -31,6 +31,7 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; +import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import javax.crypto.Mac; @@ -110,6 +111,29 @@ public static byte[] sign(Context context, String algorithm, PrivateKey privateK } } + /** + * Verify signature of a byte array based on the provided signing algorithm. + * + * @param context BRE context used to raise error messages + * @param algorithm algorithm used during verification + * @param publicKey public key to be used during verification + * @param data input byte array for verification + * @param signature signature byte array for verification + * @return validity of the signature + * @throws InvalidKeyException if the publicKey is invalid + */ + public static boolean verify(Context context, String algorithm, PublicKey publicKey, byte[] data, + byte[] signature) throws InvalidKeyException { + try { + Signature sig = Signature.getInstance(algorithm); + sig.initVerify(publicKey); + sig.update(data); + return sig.verify(signature); + } catch (NoSuchAlgorithmException | SignatureException e) { + throw new BallerinaException("error occurred while calculating signature: " + e.getMessage(), context); + } + } + /** * Create crypto error. * diff --git a/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/VerifyRsaMd5Signature.java b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/VerifyRsaMd5Signature.java new file mode 100644 index 000000000000..b2071202326a --- /dev/null +++ b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/VerifyRsaMd5Signature.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019, 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.ballerinalang.stdlib.crypto.nativeimpl; + +import org.ballerinalang.bre.Context; +import org.ballerinalang.bre.bvm.BlockingNativeCallableUnit; +import org.ballerinalang.model.values.BBoolean; +import org.ballerinalang.model.values.BMap; +import org.ballerinalang.model.values.BValue; +import org.ballerinalang.model.values.BValueArray; +import org.ballerinalang.natives.annotations.BallerinaFunction; +import org.ballerinalang.stdlib.crypto.Constants; +import org.ballerinalang.stdlib.crypto.CryptoUtils; + +import java.security.InvalidKeyException; +import java.security.PublicKey; + +/** + * Extern function ballerina.crypto:verifyRsaMd5Signature. + * + * @since 0.990.4 + */ +@BallerinaFunction(orgName = "ballerina", packageName = "crypto", functionName = "verifyRsaMd5Signature") +public class VerifyRsaMd5Signature extends BlockingNativeCallableUnit { + + @Override + public void execute(Context context) { + BValue dataBValue = context.getRefArgument(0); + BValue signatureBValue = context.getRefArgument(1); + BMap publicKey = (BMap) context.getRefArgument(2); + byte[] data = ((BValueArray) dataBValue).getBytes(); + byte[] signature = ((BValueArray) signatureBValue).getBytes(); + try { + context.setReturnValues(new BBoolean(CryptoUtils.verify(context, "MD5withRSA", + (PublicKey) publicKey.getNativeData(Constants.NATIVE_DATA_PUBLIC_KEY), data, signature))); + } catch (InvalidKeyException e) { + context.setReturnValues(CryptoUtils.createCryptoError(context, "invalid uninitialized key")); + } + } +} diff --git a/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/VerifyRsaSha1Signature.java b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/VerifyRsaSha1Signature.java new file mode 100644 index 000000000000..400006cbcfe9 --- /dev/null +++ b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/VerifyRsaSha1Signature.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019, 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.ballerinalang.stdlib.crypto.nativeimpl; + +import org.ballerinalang.bre.Context; +import org.ballerinalang.bre.bvm.BlockingNativeCallableUnit; +import org.ballerinalang.model.values.BBoolean; +import org.ballerinalang.model.values.BMap; +import org.ballerinalang.model.values.BValue; +import org.ballerinalang.model.values.BValueArray; +import org.ballerinalang.natives.annotations.BallerinaFunction; +import org.ballerinalang.stdlib.crypto.Constants; +import org.ballerinalang.stdlib.crypto.CryptoUtils; + +import java.security.InvalidKeyException; +import java.security.PublicKey; + +/** + * Extern function ballerina.crypto:verifyRsaSha1Signature. + * + * @since 0.990.4 + */ +@BallerinaFunction(orgName = "ballerina", packageName = "crypto", functionName = "verifyRsaSha1Signature") +public class VerifyRsaSha1Signature extends BlockingNativeCallableUnit { + + @Override + public void execute(Context context) { + BValue dataBValue = context.getRefArgument(0); + BValue signatureBValue = context.getRefArgument(1); + BMap publicKey = (BMap) context.getRefArgument(2); + byte[] data = ((BValueArray) dataBValue).getBytes(); + byte[] signature = ((BValueArray) signatureBValue).getBytes(); + try { + context.setReturnValues(new BBoolean(CryptoUtils.verify(context, "SHA1withRSA", + (PublicKey) publicKey.getNativeData(Constants.NATIVE_DATA_PUBLIC_KEY), data, signature))); + } catch (InvalidKeyException e) { + context.setReturnValues(CryptoUtils.createCryptoError(context, "invalid uninitialized key")); + } + } +} diff --git a/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/VerifyRsaSha256Signature.java b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/VerifyRsaSha256Signature.java new file mode 100644 index 000000000000..0c2fe44c687c --- /dev/null +++ b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/VerifyRsaSha256Signature.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019, 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.ballerinalang.stdlib.crypto.nativeimpl; + +import org.ballerinalang.bre.Context; +import org.ballerinalang.bre.bvm.BlockingNativeCallableUnit; +import org.ballerinalang.model.values.BBoolean; +import org.ballerinalang.model.values.BMap; +import org.ballerinalang.model.values.BValue; +import org.ballerinalang.model.values.BValueArray; +import org.ballerinalang.natives.annotations.BallerinaFunction; +import org.ballerinalang.stdlib.crypto.Constants; +import org.ballerinalang.stdlib.crypto.CryptoUtils; + +import java.security.InvalidKeyException; +import java.security.PublicKey; + +/** + * Extern function ballerina.crypto:verifyRsaSha256Signature. + * + * @since 0.990.4 + */ +@BallerinaFunction(orgName = "ballerina", packageName = "crypto", functionName = "verifyRsaSha256Signature") +public class VerifyRsaSha256Signature extends BlockingNativeCallableUnit { + + @Override + public void execute(Context context) { + BValue dataBValue = context.getRefArgument(0); + BValue signatureBValue = context.getRefArgument(1); + BMap publicKey = (BMap) context.getRefArgument(2); + byte[] data = ((BValueArray) dataBValue).getBytes(); + byte[] signature = ((BValueArray) signatureBValue).getBytes(); + try { + context.setReturnValues(new BBoolean(CryptoUtils.verify(context, "SHA256withRSA", + (PublicKey) publicKey.getNativeData(Constants.NATIVE_DATA_PUBLIC_KEY), data, signature))); + } catch (InvalidKeyException e) { + context.setReturnValues(CryptoUtils.createCryptoError(context, "invalid uninitialized key")); + } + } +} diff --git a/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/VerifyRsaSha384Signature.java b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/VerifyRsaSha384Signature.java new file mode 100644 index 000000000000..fc76c7dc147f --- /dev/null +++ b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/VerifyRsaSha384Signature.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019, 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.ballerinalang.stdlib.crypto.nativeimpl; + +import org.ballerinalang.bre.Context; +import org.ballerinalang.bre.bvm.BlockingNativeCallableUnit; +import org.ballerinalang.model.values.BBoolean; +import org.ballerinalang.model.values.BMap; +import org.ballerinalang.model.values.BValue; +import org.ballerinalang.model.values.BValueArray; +import org.ballerinalang.natives.annotations.BallerinaFunction; +import org.ballerinalang.stdlib.crypto.Constants; +import org.ballerinalang.stdlib.crypto.CryptoUtils; + +import java.security.InvalidKeyException; +import java.security.PublicKey; + +/** + * Extern function ballerina.crypto:verifyRsaSha384Signature. + * + * @since 0.990.4 + */ +@BallerinaFunction(orgName = "ballerina", packageName = "crypto", functionName = "verifyRsaSha384Signature") +public class VerifyRsaSha384Signature extends BlockingNativeCallableUnit { + + @Override + public void execute(Context context) { + BValue dataBValue = context.getRefArgument(0); + BValue signatureBValue = context.getRefArgument(1); + BMap publicKey = (BMap) context.getRefArgument(2); + byte[] data = ((BValueArray) dataBValue).getBytes(); + byte[] signature = ((BValueArray) signatureBValue).getBytes(); + try { + context.setReturnValues(new BBoolean(CryptoUtils.verify(context, "SHA384withRSA", + (PublicKey) publicKey.getNativeData(Constants.NATIVE_DATA_PUBLIC_KEY), data, signature))); + } catch (InvalidKeyException e) { + context.setReturnValues(CryptoUtils.createCryptoError(context, "invalid uninitialized key")); + } + } +} diff --git a/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/VerifyRsaSha512Signature.java b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/VerifyRsaSha512Signature.java new file mode 100644 index 000000000000..30a00c3354fa --- /dev/null +++ b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/VerifyRsaSha512Signature.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2019, 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.ballerinalang.stdlib.crypto.nativeimpl; + +import org.ballerinalang.bre.Context; +import org.ballerinalang.bre.bvm.BlockingNativeCallableUnit; +import org.ballerinalang.model.values.BBoolean; +import org.ballerinalang.model.values.BMap; +import org.ballerinalang.model.values.BValue; +import org.ballerinalang.model.values.BValueArray; +import org.ballerinalang.natives.annotations.BallerinaFunction; +import org.ballerinalang.stdlib.crypto.Constants; +import org.ballerinalang.stdlib.crypto.CryptoUtils; + +import java.security.InvalidKeyException; +import java.security.PublicKey; + +/** + * Extern function ballerina.crypto:verifyRsaSha512Signature. + * + * @since 0.990.4 + */ +@BallerinaFunction(orgName = "ballerina", packageName = "crypto", functionName = "verifyRsaSha512Signature") +public class VerifyRsaSha512Signature extends BlockingNativeCallableUnit { + + @Override + public void execute(Context context) { + BValue dataBValue = context.getRefArgument(0); + BValue signatureBValue = context.getRefArgument(1); + BMap publicKey = (BMap) context.getRefArgument(2); + byte[] data = ((BValueArray) dataBValue).getBytes(); + byte[] signature = ((BValueArray) signatureBValue).getBytes(); + try { + context.setReturnValues(new BBoolean(CryptoUtils.verify(context, "SHA512withRSA", + (PublicKey) publicKey.getNativeData(Constants.NATIVE_DATA_PUBLIC_KEY), data, signature))); + } catch (InvalidKeyException e) { + context.setReturnValues(CryptoUtils.createCryptoError(context, "invalid uninitialized key")); + } + } +} diff --git a/stdlib/crypto/src/test/java/org/ballerinalang/stdlib/crypto/CryptoTest.java b/stdlib/crypto/src/test/java/org/ballerinalang/stdlib/crypto/CryptoTest.java index ede14df6f58b..626b35d8ac1c 100644 --- a/stdlib/crypto/src/test/java/org/ballerinalang/stdlib/crypto/CryptoTest.java +++ b/stdlib/crypto/src/test/java/org/ballerinalang/stdlib/crypto/CryptoTest.java @@ -21,6 +21,7 @@ import org.ballerinalang.launcher.util.BCompileUtil; import org.ballerinalang.launcher.util.BRunUtil; import org.ballerinalang.launcher.util.CompileResult; +import org.ballerinalang.model.values.BBoolean; import org.ballerinalang.model.values.BError; import org.ballerinalang.model.values.BMap; import org.ballerinalang.model.values.BString; @@ -163,6 +164,14 @@ public void testSignRsaSha1() throws DecoderException { new BString("ballerina"), new BString("ballerina"), new BString("ballerina")}); Assert.assertFalse(returnValues == null || returnValues.length == 0 || returnValues[0] == null); Assert.assertEquals(((BValueArray) returnValues[0]).getBytes(), expectedSignature); + + returnValues = BRunUtil.invoke(compileResult, "testVerifyRsaSha1", + new BValue[]{new BValueArray(payload), returnValues[0], + new BString("target" + File.separator + "test-classes" + File.separator + "datafiles" + + File.separator + "crypto" + File.separator + "testKeystore.p12"), + new BString("ballerina"), new BString("ballerina")}); + Assert.assertFalse(returnValues == null || returnValues.length == 0 || returnValues[0] == null); + Assert.assertEquals(((BBoolean) returnValues[0]).booleanValue(), true); } @Test(description = "Test RSA-SHA256 siging") @@ -184,6 +193,14 @@ public void testSignRsaSha256() throws DecoderException { new BString("ballerina"), new BString("ballerina"), new BString("ballerina")}); Assert.assertFalse(returnValues == null || returnValues.length == 0 || returnValues[0] == null); Assert.assertEquals(((BValueArray) returnValues[0]).getBytes(), expectedSignature); + + returnValues = BRunUtil.invoke(compileResult, "testVerifyRsaSha256", + new BValue[]{new BValueArray(payload), returnValues[0], + new BString("target" + File.separator + "test-classes" + File.separator + "datafiles" + + File.separator + "crypto" + File.separator + "testKeystore.p12"), + new BString("ballerina"), new BString("ballerina")}); + Assert.assertFalse(returnValues == null || returnValues.length == 0 || returnValues[0] == null); + Assert.assertEquals(((BBoolean) returnValues[0]).booleanValue(), true); } @Test(description = "Test RSA-384 siging") @@ -205,6 +222,14 @@ public void testSignRsaSha384() throws DecoderException { new BString("ballerina"), new BString("ballerina"), new BString("ballerina")}); Assert.assertFalse(returnValues == null || returnValues.length == 0 || returnValues[0] == null); Assert.assertEquals(((BValueArray) returnValues[0]).getBytes(), expectedSignature); + + returnValues = BRunUtil.invoke(compileResult, "testVerifyRsaSha384", + new BValue[]{new BValueArray(payload), returnValues[0], + new BString("target" + File.separator + "test-classes" + File.separator + "datafiles" + + File.separator + "crypto" + File.separator + "testKeystore.p12"), + new BString("ballerina"), new BString("ballerina")}); + Assert.assertFalse(returnValues == null || returnValues.length == 0 || returnValues[0] == null); + Assert.assertEquals(((BBoolean) returnValues[0]).booleanValue(), true); } @Test(description = "Test RSA-512 siging") @@ -226,6 +251,14 @@ public void testSignRsaSha512() throws DecoderException { new BString("ballerina"), new BString("ballerina"), new BString("ballerina")}); Assert.assertFalse(returnValues == null || returnValues.length == 0 || returnValues[0] == null); Assert.assertEquals(((BValueArray) returnValues[0]).getBytes(), expectedSignature); + + returnValues = BRunUtil.invoke(compileResult, "testVerifyRsaSha512", + new BValue[]{new BValueArray(payload), returnValues[0], + new BString("target" + File.separator + "test-classes" + File.separator + "datafiles" + + File.separator + "crypto" + File.separator + "testKeystore.p12"), + new BString("ballerina"), new BString("ballerina")}); + Assert.assertFalse(returnValues == null || returnValues.length == 0 || returnValues[0] == null); + Assert.assertEquals(((BBoolean) returnValues[0]).booleanValue(), true); } @Test(description = "Test RSA-MD5 siging") @@ -247,6 +280,14 @@ public void testSignRsaMd5() throws DecoderException { new BString("ballerina"), new BString("ballerina"), new BString("ballerina")}); Assert.assertFalse(returnValues == null || returnValues.length == 0 || returnValues[0] == null); Assert.assertEquals(((BValueArray) returnValues[0]).getBytes(), expectedSignature); + + returnValues = BRunUtil.invoke(compileResult, "testVerifyRsaMd5", + new BValue[]{new BValueArray(payload), returnValues[0], + new BString("target" + File.separator + "test-classes" + File.separator + "datafiles" + + File.separator + "crypto" + File.separator + "testKeystore.p12"), + new BString("ballerina"), new BString("ballerina")}); + Assert.assertFalse(returnValues == null || returnValues.length == 0 || returnValues[0] == null); + Assert.assertEquals(((BBoolean) returnValues[0]).booleanValue(), true); } @Test(description = "Test RSA-SHA1 siging with an invalid private key") diff --git a/stdlib/crypto/src/test/resources/test-src/crypto/crypto-test.bal b/stdlib/crypto/src/test/resources/test-src/crypto/crypto-test.bal index 09406ec2067a..97abf213c456 100644 --- a/stdlib/crypto/src/test/resources/test-src/crypto/crypto-test.bal +++ b/stdlib/crypto/src/test/resources/test-src/crypto/crypto-test.bal @@ -139,3 +139,53 @@ function testSignRsaMd5WithInvalidKey(byte[] input) returns byte[]|error { crypto:PrivateKey pk = {algorithm:"RSA"}; return crypto:signRsaMd5(input, pk); } + +function testVerifyRsaSha1(byte[] input, byte[] signature, string path, string keyStorePassword, string keyAlias) +returns boolean|error { + crypto:KeyStore keyStore = { + path: path, + password: keyStorePassword + }; + crypto:PublicKey pk = check crypto:decodePublicKey(keyStore = keyStore, keyAlias = keyAlias); + return crypto:verifyRsaSha1Signature(input, signature, pk); +} + +function testVerifyRsaSha256(byte[] input, byte[] signature, string path, string keyStorePassword, string keyAlias) +returns boolean|error { + crypto:KeyStore keyStore = { + path: path, + password: keyStorePassword + }; + crypto:PublicKey pk = check crypto:decodePublicKey(keyStore = keyStore, keyAlias = keyAlias); + return crypto:verifyRsaSha256Signature(input, signature, pk); +} + +function testVerifyRsaSha384(byte[] input, byte[] signature, string path, string keyStorePassword, string keyAlias) +returns boolean|error { + crypto:KeyStore keyStore = { + path: path, + password: keyStorePassword + }; + crypto:PublicKey pk = check crypto:decodePublicKey(keyStore = keyStore, keyAlias = keyAlias); + return crypto:verifyRsaSha384Signature(input, signature, pk); +} + +function testVerifyRsaSha512(byte[] input, byte[] signature, string path, string keyStorePassword, string keyAlias) +returns boolean|error { + crypto:KeyStore keyStore = { + path: path, + password: keyStorePassword + }; + crypto:PublicKey pk = check crypto:decodePublicKey(keyStore = keyStore, keyAlias = keyAlias); + return crypto:verifyRsaSha512Signature(input, signature, pk); +} + +function testVerifyRsaMd5(byte[] input, byte[] signature, string path, string keyStorePassword, string keyAlias) +returns boolean|error { + crypto:KeyStore keyStore = { + path: path, + password: keyStorePassword + }; + crypto:PublicKey pk = check crypto:decodePublicKey(keyStore = keyStore, keyAlias = keyAlias); + return crypto:verifyRsaMd5Signature(input, signature, pk); +} diff --git a/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/DecodeBase64Url.java b/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/DecodeBase64Url.java new file mode 100644 index 000000000000..a57b467edce7 --- /dev/null +++ b/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/DecodeBase64Url.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019, 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.ballerinalang.stdlib.encoding.nativeimpl; + +import org.ballerinalang.bre.Context; +import org.ballerinalang.bre.bvm.BlockingNativeCallableUnit; +import org.ballerinalang.model.types.TypeKind; +import org.ballerinalang.model.values.BValueArray; +import org.ballerinalang.natives.annotations.Argument; +import org.ballerinalang.natives.annotations.BallerinaFunction; +import org.ballerinalang.natives.annotations.ReturnType; +import org.ballerinalang.stdlib.encoding.Constants; +import org.ballerinalang.stdlib.encoding.EncodingUtil; + +import java.util.Base64; + +/** + * Extern function ballerina.encoding:decodeBase64. + * + * @since 0.991.0 + */ +@BallerinaFunction( + orgName = "ballerina", packageName = "encoding", + functionName = "decodeBase64", + args = { + @Argument(name = "input", type = TypeKind.STRING) + }, + returnType = { + @ReturnType(type = TypeKind.ARRAY, elementType = TypeKind.BYTE), + @ReturnType(type = TypeKind.RECORD, structType = Constants.ENCODING_ERROR, + structPackage = Constants.ENCODING_PACKAGE) + }, + isPublic = true +) +public class DecodeBase64Url extends BlockingNativeCallableUnit { + + @Override + public void execute(Context context) { + String input = context.getStringArgument(0); + try { + byte[] output = Base64.getDecoder().decode(input); + context.setReturnValues(new BValueArray(output)); + } catch (IllegalArgumentException e) { + context.setReturnValues(EncodingUtil.createEncodingError(context, "input is not a valid Base64 value")); + } + } +} diff --git a/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/EncodeBase64Url.java b/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/EncodeBase64Url.java new file mode 100644 index 000000000000..a5d2de3f672d --- /dev/null +++ b/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/EncodeBase64Url.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019, 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.ballerinalang.stdlib.encoding.nativeimpl; + +import org.ballerinalang.bre.Context; +import org.ballerinalang.bre.bvm.BlockingNativeCallableUnit; +import org.ballerinalang.model.types.TypeKind; +import org.ballerinalang.model.values.BString; +import org.ballerinalang.model.values.BValueArray; +import org.ballerinalang.natives.annotations.Argument; +import org.ballerinalang.natives.annotations.BallerinaFunction; +import org.ballerinalang.natives.annotations.ReturnType; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +/** + * Extern function ballerina.encoding:encodeBase64. + * + * @since 0.991.0 + */ +@BallerinaFunction( + orgName = "ballerina", packageName = "encoding", + functionName = "encodeBase64", + args = { + @Argument(name = "input", type = TypeKind.ARRAY, elementType = TypeKind.BYTE) + }, + returnType = {@ReturnType(type = TypeKind.STRING)}, + isPublic = true +) +public class EncodeBase64 extends BlockingNativeCallableUnit { + + @Override + public void execute(Context context) { + BValueArray input = (BValueArray) context.getRefArgument(0); + byte[] encodedValue = Base64.getEncoder().encode(input.getBytes()); + context.setReturnValues(new BString(new String(encodedValue, StandardCharsets.ISO_8859_1))); + } +} From 9a93c8fb27a4bf1b7c2498972d2f25abdd877e17 Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Tue, 5 Feb 2019 18:15:29 +0530 Subject: [PATCH 05/26] Add base64 URL encoding and decoding --- .../src/main/ballerina/encoding/encoding.bal | 12 ++++++++++++ .../stdlib/encoding/nativeimpl/DecodeBase64Url.java | 7 ++++--- .../stdlib/encoding/nativeimpl/EncodeBase64Url.java | 6 +++--- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/stdlib/encoding/src/main/ballerina/encoding/encoding.bal b/stdlib/encoding/src/main/ballerina/encoding/encoding.bal index 3cfdebb96fd8..62594d6118d5 100644 --- a/stdlib/encoding/src/main/ballerina/encoding/encoding.bal +++ b/stdlib/encoding/src/main/ballerina/encoding/encoding.bal @@ -34,6 +34,18 @@ public extern function encodeBase64(byte[] input) returns string; # + return - Decoded output or error if input is not a valid Base64 value public extern function decodeBase64(string input) returns byte[]|error; +# Returns the Base64 URL encoded `string` value of the given byte array. +# +# + input - Value to be encoded +# + return - Encoded output +public extern function encodeBase64Url(byte[] input) returns string; + +# Decode Base64 URL encoded `string` into byte array. +# +# + input - Value to be decoded +# + return - Decoded output or error if input is not a valid Base64 value +public extern function decodeBase64Url(string input) returns byte[]|error; + # Returns the Hex encoded `string` value of the given byte array. # # + input - Value to be encoded diff --git a/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/DecodeBase64Url.java b/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/DecodeBase64Url.java index a57b467edce7..58e9c3b9ded8 100644 --- a/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/DecodeBase64Url.java +++ b/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/DecodeBase64Url.java @@ -37,7 +37,7 @@ */ @BallerinaFunction( orgName = "ballerina", packageName = "encoding", - functionName = "decodeBase64", + functionName = "decodeBase64Url", args = { @Argument(name = "input", type = TypeKind.STRING) }, @@ -54,10 +54,11 @@ public class DecodeBase64Url extends BlockingNativeCallableUnit { public void execute(Context context) { String input = context.getStringArgument(0); try { - byte[] output = Base64.getDecoder().decode(input); + byte[] output = Base64.getUrlDecoder().decode(input); context.setReturnValues(new BValueArray(output)); } catch (IllegalArgumentException e) { - context.setReturnValues(EncodingUtil.createEncodingError(context, "input is not a valid Base64 value")); + context.setReturnValues(EncodingUtil.createEncodingError(context, "input is not a valid Base64 URL " + + "encoded value")); } } } diff --git a/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/EncodeBase64Url.java b/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/EncodeBase64Url.java index a5d2de3f672d..3d3e20ee094a 100644 --- a/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/EncodeBase64Url.java +++ b/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/EncodeBase64Url.java @@ -37,19 +37,19 @@ */ @BallerinaFunction( orgName = "ballerina", packageName = "encoding", - functionName = "encodeBase64", + functionName = "encodeBase64Url", args = { @Argument(name = "input", type = TypeKind.ARRAY, elementType = TypeKind.BYTE) }, returnType = {@ReturnType(type = TypeKind.STRING)}, isPublic = true ) -public class EncodeBase64 extends BlockingNativeCallableUnit { +public class EncodeBase64Url extends BlockingNativeCallableUnit { @Override public void execute(Context context) { BValueArray input = (BValueArray) context.getRefArgument(0); - byte[] encodedValue = Base64.getEncoder().encode(input.getBytes()); + byte[] encodedValue = Base64.getUrlEncoder().encode(input.getBytes()); context.setReturnValues(new BString(new String(encodedValue, StandardCharsets.ISO_8859_1))); } } From b88e2917e57d89add8f083fa9f5d52e3cdb87116 Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Tue, 5 Feb 2019 18:15:51 +0530 Subject: [PATCH 06/26] Move signing and verification to cryto stdlib --- .../src/main/ballerina/auth/jwt_common.bal | 14 ++++++++++- .../src/main/ballerina/auth/jwt_issuer.bal | 25 +++++++++++++++++-- .../auth/jwt_supported_auth_provider_util.bal | 2 +- .../src/main/ballerina/auth/jwt_validator.bal | 24 +++++++++++++++--- .../test/resources/test-src/jwt/jwt-test.bal | 2 +- 5 files changed, 58 insertions(+), 9 deletions(-) diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_common.bal b/stdlib/auth/src/main/ballerina/auth/jwt_common.bal index 48040637ed50..ca7193d802fd 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_common.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_common.bal @@ -14,6 +14,18 @@ // specific language governing permissions and limitations // under the License. +# The key algorithms supported by crypto module. +public type JwtSigningAlgorithm RS256|RS384|RS512; + +# The `RSA-SHA256` algorithm +public const RS256 = "RS256"; + +# The `RSA-SHA384` algorithm +public const RS384 = "RS384"; + +# The `RSA-SHA512` algorithm +public const RS512 = "RS512"; + //JOSH header parameters const string ALG = "alg"; const string TYP = "typ"; @@ -36,7 +48,7 @@ const string IAT = "iat"; # + kid - Key ID, hint indicating which key was used to secure the JWS # + customClaims - Map of custom claims public type JwtHeader record { - string alg = ""; + JwtSigningAlgorithm alg?; string typ?; string cty?; string kid?; diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal b/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal index 9d4d84f4e375..bfa71a9e2a53 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal @@ -30,7 +30,19 @@ public function issueJwt(JwtHeader header, JwtPayload payload, crypto:KeyStore k string jwtHeader = check buildHeaderString(header); string jwtPayload = check buildPayloadString(payload); string jwtAssertion = jwtHeader + "." + jwtPayload; - string signature = sign(jwtAssertion, header.alg, keyStore, keyAlias, keyPassword); + var privateKey = check crypto:decodePrivateKey(keyStore = keyStore, keyAlias = keyAlias, keyPassword = keyPassword); + + string signature = ""; + if (header.alg == RS256) { + signature = encoding:encodeBase64Url(check crypto:signRsaSha256(jwtAssertion.toByteArray("UTF-8"), privateKey)); + } else if (header.alg == RS384) { + signature = encoding:encodeBase64Url(check crypto:signRsaSha384(jwtAssertion.toByteArray("UTF-8"), privateKey)); + } else if (header.alg == RS512) { + signature = encoding:encodeBase64Url(check crypto:signRsaSha512(jwtAssertion.toByteArray("UTF-8"), privateKey)); + } else { + error jwtError = error(AUTH_ERROR_CODE, { message : "Unsupported JWS algorithm" }); + return jwtError; + } return (jwtAssertion + "." + signature); } @@ -40,7 +52,16 @@ function buildHeaderString(JwtHeader header) returns (string|error) { error jwtError = error(AUTH_ERROR_CODE, { message : "Mandatory field signing algorithm(alg) is empty." }); return jwtError; } - headerJson[ALG] = header.alg; + if (header.alg == RS256) { + headerJson[ALG] = "RS256"; + } else if (header.alg == RS384) { + headerJson[ALG] = "RS384"; + } else if (header.alg == RS512) { + headerJson[ALG] = "RS512"; + } else { + error jwtError = error(AUTH_ERROR_CODE, { message : "Unsupported JWS algorithm" }); + return jwtError; + } headerJson[TYP] = "JWT"; var customClaims = header["customClaims"]; if (customClaims is map) { diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal b/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal index adbdc6c6ffe1..ff842671b873 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal @@ -36,7 +36,7 @@ import ballerina/crypto; crypto:KeyStore keyStore; string keyAlias; string keyPassword; - string signingAlg; + JwtSigningAlgorithm signingAlg; !...; }; diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal b/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal index 6d01c85e6a51..3900c11de643 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal @@ -143,7 +143,13 @@ function parseHeader(json jwtHeaderJson) returns (JwtHeader) { foreach var key in keys { if (key == ALG) { - jwtHeader.alg = jwtHeaderJson[key].toString(); + if (jwtHeaderJson[key].toString() == "RS256") { + jwtHeader.alg = RS256; + } else if (jwtHeaderJson[key].toString() == "RS384") { + jwtHeader.alg = RS384; + } else if (jwtHeaderJson[key].toString() == "RS512") { + jwtHeader.alg = RS512; + } } else if (key == TYP) { jwtHeader.typ = jwtHeaderJson[key].toString(); } else if (key == CTY) { @@ -267,10 +273,20 @@ function validateMandatoryFields(JwtPayload jwtPayload) returns (boolean) { } function validateSignature(string[] encodedJWTComponents, JwtHeader jwtHeader, JWTValidatorConfig config) -returns error? { +returns boolean|error { string assertion = encodedJWTComponents[0] + "." + encodedJWTComponents[1]; - string signPart = encodedJWTComponents[2]; - return verifySignature(assertion, signPart, jwtHeader.alg, config.trustStore, config.certificateAlias); + byte[] signPart = check encoding:decodeBase64Url(encodedJWTComponents[2]); + crypto:PublicKey publicKey = check crypto:decodePublicKey(keyStore = config.trustStore, keyAlias = config.certificateAlias); + if (jwtHeader.alg == RS256) { + return crypto:verifyRsaSha256Signature(assertion.toByteArray("UTF-8"), signPart, publicKey); + } else if (jwtHeader.alg == RS384) { + return crypto:verifyRsaSha384Signature(assertion.toByteArray("UTF-8"), signPart, publicKey); + } else if (jwtHeader.alg == RS512) { + return crypto:verifyRsaSha512Signature(assertion.toByteArray("UTF-8"), signPart, publicKey); + } else { + error jwtError = error(AUTH_ERROR_CODE, { message : "Unsupported JWS algorithm" }); + return jwtError; + } } function validateIssuer(JwtPayload jwtPayload, JWTValidatorConfig config) returns (boolean) { diff --git a/stdlib/auth/src/test/resources/test-src/jwt/jwt-test.bal b/stdlib/auth/src/test/resources/test-src/jwt/jwt-test.bal index 10cdc11bee42..fa6f7e05f92a 100644 --- a/stdlib/auth/src/test/resources/test-src/jwt/jwt-test.bal +++ b/stdlib/auth/src/test/resources/test-src/jwt/jwt-test.bal @@ -5,7 +5,7 @@ import ballerina/crypto; function testIssueJwt (string keyStorePath) returns (string)|error { auth:JwtHeader header = {}; - header.alg = "RS256"; + header.alg = auth:RS256; header.typ = "JWT"; auth:JwtPayload payload = {}; From 71b215d65505104b08a753a22a42acae77767c00 Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Wed, 6 Feb 2019 10:25:24 +0530 Subject: [PATCH 07/26] Remove auth specific RSA crypto natives --- .../src/main/ballerina/auth/jwt_native.bal | 36 ------ .../auth/ldap/jwt/crypto/JWSAlgorithm.java | 44 ------- .../auth/ldap/jwt/crypto/JWSException.java | 47 ------- .../auth/ldap/jwt/crypto/JWSSigner.java | 40 ------ .../auth/ldap/jwt/crypto/JWSVerifier.java | 44 ------- .../auth/ldap/jwt/crypto/KeyStoreHolder.java | 108 ---------------- .../auth/ldap/jwt/crypto/RSASSAProvider.java | 51 -------- .../auth/ldap/jwt/crypto/RSASigner.java | 72 ----------- .../auth/ldap/jwt/crypto/RSAVerifier.java | 64 ---------- .../ldap/jwt/crypto/TrustStoreHolder.java | 117 ------------------ .../auth/ldap/jwt/signature/PathResolver.java | 62 ---------- .../auth/ldap/jwt/signature/Sign.java | 86 ------------- .../ldap/jwt/signature/VerifySignature.java | 104 ---------------- .../stdlib/auth/jwt/JWTAuthenticatorTest.java | 99 +++++++-------- .../stdlib/auth/jwt/crypto/JWTSignerTest.java | 61 --------- .../auth/jwt/crypto/JWTVerifierTest.java | 62 ---------- .../test-src/jwt-authenticator-test.bal | 9 ++ .../main/ballerina/http/service_endpoint.bal | 4 +- .../stdlib/auth/JWTAuthnHandlerTest.java | 77 ++++-------- .../test-src/auth/jwt-authn-handler-test.bal | 9 ++ 20 files changed, 92 insertions(+), 1104 deletions(-) delete mode 100644 stdlib/auth/src/main/ballerina/auth/jwt_native.bal delete mode 100644 stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSAlgorithm.java delete mode 100644 stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSException.java delete mode 100644 stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSSigner.java delete mode 100644 stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSVerifier.java delete mode 100644 stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/KeyStoreHolder.java delete mode 100644 stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/RSASSAProvider.java delete mode 100644 stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/RSASigner.java delete mode 100644 stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/RSAVerifier.java delete mode 100644 stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/TrustStoreHolder.java delete mode 100644 stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/signature/PathResolver.java delete mode 100644 stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/signature/Sign.java delete mode 100644 stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/signature/VerifySignature.java delete mode 100644 stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/crypto/JWTSignerTest.java delete mode 100644 stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/crypto/JWTVerifierTest.java diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_native.bal b/stdlib/auth/src/main/ballerina/auth/jwt_native.bal deleted file mode 100644 index 247a1a851514..000000000000 --- a/stdlib/auth/src/main/ballerina/auth/jwt_native.bal +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) 2018 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. - -# VerifySignature the signature of a given jwt. -# -# + data - Original data which has signed. -# + signature - Signature string. -# + algorithm - Signature algorithm. -# + trustStore - Truststore. -# + keyAlias - Key alias. -# + return - error if verification failed, nil if verification is successful. -extern function verifySignature(string data, string signature, string algorithm, crypto:TrustStore trustStore, - string keyAlias) returns error?; - -# Sign the given input jwt data. -# -# + data - Original that need to sign. -# + algorithm - Signature string. -# + keyStore - Keystore. -# + return - Signature. Signed string. -extern function sign(string data, string algorithm, crypto:KeyStore keyStore, string keyAlias, string keyPassword) -returns (string); - diff --git a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSAlgorithm.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSAlgorithm.java deleted file mode 100644 index c92d0af0854d..000000000000 --- a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSAlgorithm.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2018, 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.ballerinalang.auth.ldap.jwt.crypto; - -/** - * JSON Web Signature (JWS) algorithm name, represents the {@code alg} header - * parameter in JWS objects. - * - * @since 0.964.0 - */ -public final class JWSAlgorithm { - - /** - * RSASSA-PKCS-v1_5 SHA-256 hash algorithm. - */ - public static final String RS256 = "RS256"; - - /** - * RSASSA-PKCS-v1_5 SHA-384 hash algorithm. - */ - public static final String RS384 = "RS384"; - - /** - * RSASSA-PKCS-v1_5 SHA-512 hash algorithm (optional). - */ - public static final String RS512 = "RS512"; - -} diff --git a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSException.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSException.java deleted file mode 100644 index eca4a4edbf82..000000000000 --- a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSException.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2018, 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.ballerinalang.auth.ldap.jwt.crypto; - -/** - * A checked exception for wrapping potential exceptions thrown during JWT signature processing. - * - * @since 0.964.0 - */ -public class JWSException extends Exception { - - /** - * Constructs a new JWSException with the specified message. - * - * @param message Error message - */ - public JWSException(String message) { - super(message); - } - - /** - * Constructs a new JWSException with the specified message. - * - * @param message Error message - * @param cause The cause of the failure - */ - public JWSException(String message, Throwable cause) { - super(message, cause); - } - -} diff --git a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSSigner.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSSigner.java deleted file mode 100644 index 8c89208dd010..000000000000 --- a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSSigner.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2018, 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.ballerinalang.auth.ldap.jwt.crypto; - -/** - * JSON web signature. - * - * @since 0.964.0 - */ -public interface JWSSigner { - - /** - * Signs the specified input data. - * - * @param data This input should contain the header and body part of the JWT. - * BASE64URL(UTF8(JOSE header)) || '.' || BASE64URL(JWS payload) - * @param algorithm JWS algorithm used to secure the JWS. - * This is the 'alg' header parameter. - * @return The signature part of the JWS object. - * @throws JWSException If the JWS algorithm is not supported or if - * signing failed for some other internal reason. - */ - String sign(final String data, final String algorithm) throws JWSException; -} diff --git a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSVerifier.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSVerifier.java deleted file mode 100644 index 957ac48685c3..000000000000 --- a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/JWSVerifier.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2018, 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.ballerinalang.auth.ldap.jwt.crypto; - -/** - * JSON web signature verifier. - * - * @since 0.964.0 - */ -public interface JWSVerifier { - - /** - * Verifies the signature of the JWS object. - * - * @param data This input should contain the header and body part of the JWT. - * BASE64URL(UTF8(JOSE header)) || '.' || BASE64URL(JWS payload) - * @param signature Signature part of the JWT. - * @param algorithm JWS algorithm used to secure the JWS. - * This is the 'alg' header parameter. - * @return {@code true} if the signature was verified, - * {@code false} if the signature is invalid. - * @throws JWSException If the JWS algorithm is not supported, or if - * signature verification failed for some other - * internal reason. - */ - boolean verify(final String data, final String signature, final String algorithm) - throws JWSException; -} diff --git a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/KeyStoreHolder.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/KeyStoreHolder.java deleted file mode 100644 index b84214de4a70..000000000000 --- a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/KeyStoreHolder.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright (c) 2018, 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.ballerinalang.auth.ldap.jwt.crypto; - -import org.ballerinalang.util.exceptions.BallerinaException; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.security.KeyStore; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.UnrecoverableEntryException; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * KeyStoreHolder process the keystore, store it and provide an API to use keystore. - * PKCS12 is the keystore format supported. - * - * @since 0.970.0 - */ -public class KeyStoreHolder { - - private static final String PKCS12 = "pkcs12"; - private static final KeyStoreHolder keyStoreHolderInstance = new KeyStoreHolder(); - private Map keyStoreMap; - - private KeyStoreHolder() { - keyStoreMap = new ConcurrentHashMap(); - } - - /** - * Get the key store. - * - * @return keyStoreHolder instance - */ - public static KeyStoreHolder getInstance() { - return keyStoreHolderInstance; - } - - /** - * Get the private key for a given key alias. - * - * @param keyAlias the private key alias name - * @param keyPassword the password to protect the key - * @param keyStoreFilePath the key store file path - * @param keyStorePassword the key store password - * @return private key corresponding to the alias - * @throws BallerinaException if the keystore has not been initialized - * (loaded), if the algorithm for recovering the entry cannot be found, - * {@code alias} or {@code keyPassword} does not contain - * the information needed to recover the key (e.g. wrong password) - */ - public PrivateKey getPrivateKey(String keyAlias, char[] keyPassword, String keyStoreFilePath, - char[] keyStorePassword) throws BallerinaException { - KeyStore.PrivateKeyEntry pkEntry; - try { - pkEntry = (KeyStore.PrivateKeyEntry) getKeyStore(keyStoreFilePath, keyStorePassword) - .getEntry(keyAlias, new KeyStore.PasswordProtection(keyPassword)); - } catch (NoSuchAlgorithmException | UnrecoverableEntryException | java.security.KeyStoreException e) { - throw new BallerinaException("Failed to load private key: " + keyAlias, e); - } - if (pkEntry == null) { - throw new BallerinaException("Failed to load private key: " + keyAlias); - } - return pkEntry.getPrivateKey(); - } - - private KeyStore getKeyStore(String keyStoreFilePath, char[] keyStorePassword) { - KeyStore keyStore = keyStoreMap.get(keyStoreFilePath); - if (keyStore == null) { - KeyStore newKeyStore = loadTrustStore(keyStoreFilePath, keyStorePassword); - keyStoreMap.put(keyStoreFilePath, newKeyStore); - keyStore = keyStoreMap.get(keyStoreFilePath); - } - return keyStore; - } - - private KeyStore loadTrustStore(String keyStoreFilePath, char[] keyStorePassword) { - try (InputStream file = new FileInputStream(new File(keyStoreFilePath))) { - KeyStore trustStore = KeyStore.getInstance(PKCS12); - trustStore.load(file, keyStorePassword); - return trustStore; - } catch (FileNotFoundException e) { - throw new BallerinaException("Failed to load keyStore: file not found: " + keyStoreFilePath, e); - } catch (Exception e) { - throw new BallerinaException("Failed to load keyStore: " + e.getMessage(), e); - } - } -} diff --git a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/RSASSAProvider.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/RSASSAProvider.java deleted file mode 100644 index 6dfb3caad086..000000000000 --- a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/RSASSAProvider.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2018, 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.ballerinalang.auth.ldap.jwt.crypto; - -/** - * Provides the supported algorithms. - * - *

    See RFC 7518, sections - * 3.3 for more - * information. - * - * @since 0.964.0 - */ -public class RSASSAProvider { - - /** - * Gets the matching JCA algorithm name for the specified RSA-based JSON Web Algorithm. - * - * @param algorithm The JSON Web Algorithm (JWA). Must not {@code null}. - * @return The matching JCA algorithm name. - * @throws JWSException If the algorithm is not supported. - */ - protected static String getJCAAlgorithmName(final String algorithm) - throws JWSException { - if (JWSAlgorithm.RS256.equals(algorithm)) { - return "SHA256withRSA"; - } else if (JWSAlgorithm.RS384.equals(algorithm)) { - return "SHA384withRSA"; - } else if (JWSAlgorithm.RS512.equals(algorithm)) { - return "SHA512withRSA"; - } else { - throw new JWSException("Unsupported JWS algorithm" + algorithm); - } - } -} diff --git a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/RSASigner.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/RSASigner.java deleted file mode 100644 index 6018c3ceb049..000000000000 --- a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/RSASigner.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2018, 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.ballerinalang.auth.ldap.jwt.crypto; - -import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.Signature; -import java.security.SignatureException; -import java.util.Base64; - -/** - * Sign with RSA algorithms. Expects a private RSA key. - * - *

    See RFC 7518, sections - * 3.3 for more - * information. - * - * @since 0.964.0 - */ -public class RSASigner implements JWSSigner { - - /** - * The private RSA key. - */ - private final PrivateKey privateKey; - - /** - * Creates a new RSA signer. - * - * @param privateKey The private RSA key, algorithm must be "RSA". - * Must not be {@code null}. - */ - public RSASigner(final PrivateKey privateKey) { - if (!"RSA".equalsIgnoreCase(privateKey.getAlgorithm())) { - throw new IllegalArgumentException("The private key algorithm must be RSA"); - } - this.privateKey = privateKey; - } - - @Override - public String sign(String data, String algorithm) throws JWSException { - byte[] signatureBytes; - final Signature signature; - try { - signature = Signature.getInstance(RSASSAProvider.getJCAAlgorithmName(algorithm)); - signature.initSign(privateKey); - signature.update(data.getBytes(StandardCharsets.UTF_8)); - signatureBytes = signature.sign(); - } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) { - throw new JWSException(e.getMessage(), e); - } - return new String(Base64.getUrlEncoder().encode(signatureBytes), StandardCharsets.UTF_8); - } -} diff --git a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/RSAVerifier.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/RSAVerifier.java deleted file mode 100644 index 0911c8afa4ea..000000000000 --- a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/RSAVerifier.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2018, 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.ballerinalang.auth.ldap.jwt.crypto; - -import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.NoSuchAlgorithmException; -import java.security.Signature; -import java.security.SignatureException; -import java.security.interfaces.RSAPublicKey; -import java.util.Base64; - -/** - * VerifySignature signature with RSA algorithms. Expects a public RSA key. - * - *

    See RFC 7518, sections - * 3.3 for more - * information. - * - * @since 0.964.0 - */ -public class RSAVerifier implements JWSVerifier { - - /** - * The public RSA key. - */ - private final RSAPublicKey publicKey; - - public RSAVerifier(final RSAPublicKey publicKey) { - this.publicKey = publicKey; - } - - @Override - public boolean verify(String data, String signature, String algorithm) throws JWSException { - final Signature signatureVerifier; - byte[] dataInBytes = data.getBytes(StandardCharsets.UTF_8); - byte[] signatureData = Base64.getUrlDecoder().decode(signature.getBytes(StandardCharsets.UTF_8)); - String alg = RSASSAProvider.getJCAAlgorithmName(algorithm); - try { - signatureVerifier = Signature.getInstance(alg); - signatureVerifier.initVerify(publicKey); - signatureVerifier.update(dataInBytes); - return signatureVerifier.verify(signatureData); - } catch (NoSuchAlgorithmException | SignatureException | InvalidKeyException e) { - throw new JWSException(e.getMessage(), e); - } - } -} diff --git a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/TrustStoreHolder.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/TrustStoreHolder.java deleted file mode 100644 index a66f03cccb66..000000000000 --- a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/crypto/TrustStoreHolder.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (c) 2018, 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.ballerinalang.auth.ldap.jwt.crypto; - -import org.ballerinalang.util.exceptions.BallerinaException; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.InputStream; -import java.security.KeyStore; -import java.security.PublicKey; -import java.security.cert.Certificate; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -/** - * TrustStoreHolder process the trust store, store it and provide an API to use trust store. - * PKCS12 is the keystore format supported. - * - * @since 0.970.0 - */ -public class TrustStoreHolder { - - private static final String PKCS12 = "pkcs12"; - private static final TrustStoreHolder trustStoreHolderInstance = new TrustStoreHolder(); - private Map trustStoreMap; - - private TrustStoreHolder() { - trustStoreMap = new ConcurrentHashMap(); - } - - /** - * Get the trust store. - * - * @return trustStoreHolder instance - */ - public static TrustStoreHolder getInstance() { - return trustStoreHolderInstance; - } - - /** - * Get the public key for a given key alias from trustStore. - * - * @param certificateAlia the alias name - * @param trustStoreFilePath Trust store file path - * @param trustStorePassword Trust store password - * @return public key corresponding to the alias. - * @throws BallerinaException if the trustStore has not been initialized - * (loaded), or this operation fails for some other reason - */ - public PublicKey getTrustedPublicKey(String certificateAlia, String trustStoreFilePath, char[] - trustStorePassword) throws BallerinaException { - Certificate certificate = getTrustedCertificate(certificateAlia, trustStoreFilePath, trustStorePassword); - if (certificate == null) { - throw new BallerinaException("Failed to load trusted key: " + certificateAlia); - } - return certificate.getPublicKey(); - } - - /** - * Get the certificate for a given key alias from trustStore. - * - * @param certificateAlia the alias name - * @param trustStoreFilePath Trust store file path - * @param trustStorePassword Trust store password - * @return certificate corresponding to the alias. - * @throws BallerinaException if the trustStore has not been initialized - * (loaded), or this operation fails for some other reason - */ - public Certificate getTrustedCertificate(String certificateAlia, String trustStoreFilePath, char[] - trustStorePassword) throws BallerinaException { - try { - return getTrustStore(trustStoreFilePath, trustStorePassword).getCertificate(certificateAlia); - } catch (java.security.KeyStoreException e) { - throw new BallerinaException("Failed to load certificate: " + certificateAlia, e); - } - } - - private KeyStore getTrustStore(String trustStoreFilePath, char[] trustStorePassword) { - KeyStore trustStore = trustStoreMap.get(trustStoreFilePath); - if (trustStore == null) { - KeyStore newTrustStore = loadTrustStore(trustStoreFilePath, trustStorePassword); - trustStoreMap.put(trustStoreFilePath, newTrustStore); - trustStore = trustStoreMap.get(trustStoreFilePath); - } - return trustStore; - } - - private KeyStore loadTrustStore(String trustStoreFilePath, char[] trustStorePassword) { - try (InputStream file = new FileInputStream(new File(trustStoreFilePath))) { - KeyStore trustStore = KeyStore.getInstance(PKCS12); - trustStore.load(file, trustStorePassword); - return trustStore; - } catch (FileNotFoundException e) { - throw new BallerinaException("Failed to load trustStore: file not found: " + trustStoreFilePath, e); - } catch (Exception e) { - throw new BallerinaException("Failed to load trustStore: " + e.getMessage(), e); - } - } -} diff --git a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/signature/PathResolver.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/signature/PathResolver.java deleted file mode 100644 index ee8976ca0c04..000000000000 --- a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/signature/PathResolver.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2018, 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.ballerinalang.auth.ldap.jwt.signature; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Resolves any system properties in the given path. - */ -public class PathResolver { - - static String getResolvedPath (String path) { - Pattern varPattern = Pattern.compile("\\$\\{([^}]*)}"); - Matcher matcher = varPattern.matcher(path); - boolean found = matcher.find(); - if (!found) { - return path; - } - StringBuffer sb = new StringBuffer(); - do { - String sysPropKey = matcher.group(1); - String sysPropValue = getSystemVariableValue(sysPropKey, null); - if (sysPropValue == null || sysPropValue.length() == 0) { - throw new RuntimeException("System property " + sysPropKey + " is not specified"); - } - // Due to reported bug under CARBON-14746 - sysPropValue = sysPropValue.replace("\\", "\\\\"); - matcher.appendReplacement(sb, sysPropValue); - } while (matcher.find()); - matcher.appendTail(sb); - return sb.toString(); - } - - private static String getSystemVariableValue(String variableName, String defaultValue) { - String value; - if (System.getProperty(variableName) != null) { - value = System.getProperty(variableName); - } else if (System.getenv(variableName) != null) { - value = System.getenv(variableName); - } else { - value = defaultValue; - } - return value; - } -} diff --git a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/signature/Sign.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/signature/Sign.java deleted file mode 100644 index 996891f40955..000000000000 --- a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/signature/Sign.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (c) 2018, 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.ballerinalang.auth.ldap.jwt.signature; - -import org.ballerinalang.auth.ldap.jwt.crypto.JWSSigner; -import org.ballerinalang.auth.ldap.jwt.crypto.KeyStoreHolder; -import org.ballerinalang.auth.ldap.jwt.crypto.RSASigner; -import org.ballerinalang.bre.Context; -import org.ballerinalang.bre.bvm.BLangVMErrors; -import org.ballerinalang.bre.bvm.BlockingNativeCallableUnit; -import org.ballerinalang.model.types.TypeKind; -import org.ballerinalang.model.values.BMap; -import org.ballerinalang.model.values.BString; -import org.ballerinalang.model.values.BValue; -import org.ballerinalang.natives.annotations.Argument; -import org.ballerinalang.natives.annotations.BallerinaFunction; -import org.ballerinalang.natives.annotations.ReturnType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.security.PrivateKey; - -/** - * Extern function ballerinalang.jwt:sign. - * - * @since 0.964.0 - */ -@BallerinaFunction( - orgName = "ballerina", packageName = "auth", - functionName = "sign", - args = { - @Argument(name = "data", type = TypeKind.STRING), - @Argument(name = "algorithm", type = TypeKind.STRING), - @Argument(name = "keyStore", type = TypeKind.RECORD, structType = "KeyStore", - structPackage = "ballerina/internal"), - @Argument(name = "keyAlias", type = TypeKind.STRING), - @Argument(name = "keyPassword", type = TypeKind.STRING) - }, - returnType = {@ReturnType(type = TypeKind.STRING)}, - isPublic = true -) -public class Sign extends BlockingNativeCallableUnit { - private static final Logger log = LoggerFactory.getLogger(Sign.class); - private static final String KEY_STORE_PATH_FIELD = "path"; - private static final String KEY_STORE_PASSWORD_FIELD = "password"; - - @Override - public void execute(Context context) { - String data = context.getStringArgument(0); - String algorithm = context.getStringArgument(1); - String keyAlias = context.getStringArgument(2); - String keyPasswordStr = context.getStringArgument(3); - BMap keyStore = (BMap) context.getRefArgument(0); - char[] keyPassword = keyPasswordStr.toCharArray(); - char[] keyStorePassword = keyStore.get(KEY_STORE_PASSWORD_FIELD).stringValue().toCharArray(); - String signature = null; - PrivateKey privateKey; - try { - privateKey = KeyStoreHolder.getInstance().getPrivateKey(keyAlias, - keyPassword, PathResolver.getResolvedPath(keyStore.get(KEY_STORE_PATH_FIELD).stringValue()), - keyStorePassword); - JWSSigner signer = new RSASigner(privateKey); - signature = signer.sign(data, algorithm); - context.setReturnValues(new BString(signature)); - } catch (Exception e) { - log.error(e.getMessage(), e); - context.setReturnValues(new BString(null), BLangVMErrors.createError(context, e.getMessage())); - } - } -} diff --git a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/signature/VerifySignature.java b/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/signature/VerifySignature.java deleted file mode 100644 index 2c8a09df848d..000000000000 --- a/stdlib/auth/src/main/java/org/ballerinalang/auth/ldap/jwt/signature/VerifySignature.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) 2018, 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.ballerinalang.auth.ldap.jwt.signature; - -import org.ballerinalang.auth.ldap.jwt.crypto.JWSVerifier; -import org.ballerinalang.auth.ldap.jwt.crypto.RSAVerifier; -import org.ballerinalang.auth.ldap.jwt.crypto.TrustStoreHolder; -import org.ballerinalang.bre.Context; -import org.ballerinalang.bre.bvm.BLangVMErrors; -import org.ballerinalang.bre.bvm.BlockingNativeCallableUnit; -import org.ballerinalang.model.types.TypeKind; -import org.ballerinalang.model.values.BMap; -import org.ballerinalang.model.values.BValue; -import org.ballerinalang.natives.annotations.Argument; -import org.ballerinalang.natives.annotations.BallerinaFunction; -import org.ballerinalang.natives.annotations.ReturnType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.security.cert.CertificateExpiredException; -import java.security.cert.CertificateNotYetValidException; -import java.security.cert.X509Certificate; -import java.security.interfaces.RSAPublicKey; - -import static org.ballerinalang.bre.bvm.BLangVMErrors.STRUCT_GENERIC_ERROR; -import static org.ballerinalang.util.BLangConstants.BALLERINA_BUILTIN_PKG; - -/** - * Extern function ballerinalang.jwt:verifySignature. - * - * @since 0.964.0 - */ -@BallerinaFunction( - orgName = "ballerina", packageName = "auth", - functionName = "verifySignature", - args = { - @Argument(name = "data", type = TypeKind.STRING), - @Argument(name = "signature", type = TypeKind.STRING), - @Argument(name = "algorithm", type = TypeKind.STRING), - @Argument(name = "trustStore", type = TypeKind.RECORD, structType = "TrustStoreHolder", - structPackage = "ballerina/internal"), - @Argument(name = "keyAlias", type = TypeKind.STRING) - }, - returnType = { - @ReturnType(type = TypeKind.OBJECT, structType = STRUCT_GENERIC_ERROR, structPackage = - BALLERINA_BUILTIN_PKG) - }, - isPublic = true -) -public class VerifySignature extends BlockingNativeCallableUnit { - private static final Logger log = LoggerFactory.getLogger(VerifySignature.class); - private static final String TRUST_STORE_PATH = "path"; - private static final String TRUST_STORE_PASSWORD = "password"; - - @Override - public void execute(Context context) { - String data = context.getStringArgument(0); - String signature = context.getStringArgument(1); - String algorithm = context.getStringArgument(2); - String keyAlias = context.getStringArgument(3); - BMap trustStore = (BMap) context.getRefArgument(0); - char[] trustStorePassword = trustStore.get(TRUST_STORE_PASSWORD).stringValue().toCharArray(); - RSAPublicKey publicKey; - String msg = null; - try { - X509Certificate certificate = (X509Certificate) TrustStoreHolder.getInstance().getTrustedCertificate( - keyAlias, PathResolver.getResolvedPath(trustStore.get(TRUST_STORE_PATH).stringValue()), - trustStorePassword); - certificate.checkValidity(); - publicKey = (RSAPublicKey) certificate.getPublicKey(); - - JWSVerifier verifier = new RSAVerifier(publicKey); - if (!verifier.verify(data, signature, algorithm)) { - msg = "Invalid signature"; - } - } catch (CertificateExpiredException e) { - msg = "Certificate with alias " + keyAlias + " has expired"; - } catch (CertificateNotYetValidException e) { - msg = "Certificate with alias " + keyAlias + " is not yet valid"; - } catch (Exception e) { - msg = "Error in verifying signature"; - log.error(msg, e); - } - if (msg != null) { - context.setReturnValues(BLangVMErrors.createError(context, msg)); - } - } -} diff --git a/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JWTAuthenticatorTest.java b/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JWTAuthenticatorTest.java index c27de9cef94b..7cc0e29d01d4 100644 --- a/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JWTAuthenticatorTest.java +++ b/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JWTAuthenticatorTest.java @@ -18,32 +18,27 @@ package org.ballerinalang.stdlib.auth.jwt; -import org.ballerinalang.auth.ldap.jwt.crypto.JWSSigner; -import org.ballerinalang.auth.ldap.jwt.crypto.RSASigner; import org.ballerinalang.config.ConfigRegistry; import org.ballerinalang.launcher.util.BCompileUtil; import org.ballerinalang.launcher.util.BRunUtil; import org.ballerinalang.launcher.util.CompileResult; import org.ballerinalang.model.values.BBoolean; +import org.ballerinalang.model.values.BInteger; import org.ballerinalang.model.values.BMap; import org.ballerinalang.model.values.BString; import org.ballerinalang.model.values.BValue; +import org.ballerinalang.model.values.BValueArray; import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; import java.nio.file.CopyOption; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.security.KeyStore; -import java.security.PrivateKey; -import java.util.Base64; import java.util.HashMap; import java.util.Map; @@ -89,6 +84,7 @@ public class JWTAuthenticatorTest { private String resourceRoot; private String jwtToken; private String trustStorePath; + private String keyStorePath; private static final String BALLERINA_CONF = "ballerina.conf"; private static final String KEY_STORE = "ballerinaKeystore.p12"; private static final String TRUST_SORE = "ballerinaTruststore.p12"; @@ -97,6 +93,8 @@ public class JWTAuthenticatorTest { public void setup() throws Exception { trustStorePath = getClass().getClassLoader().getResource( "datafiles/keystore/ballerinaTruststore.p12").getPath(); + keyStorePath = getClass().getClassLoader().getResource( + "datafiles/keystore/ballerinaKeystore.p12").getPath(); resourceRoot = new File(getClass().getProtectionDomain().getCodeSource().getLocation().getPath()) .getAbsolutePath(); Path sourceRoot = Paths.get(resourceRoot, "test-src"); @@ -113,11 +111,49 @@ public void setup() throws Exception { // load configs ConfigRegistry registry = ConfigRegistry.getInstance(); registry.initRegistry(getRuntimeProperties(), ballerinaConfPath.toString(), null); + } + + @Test(description = "Test JWT issuer", priority = 1) + private void testGenerateJwt() { + BMap jwtHeader = new BMap<>(); + jwtHeader.put("alg", new BString("RS256")); + jwtHeader.put("typ", new BString("JWT")); + + long time = 32475251189000L; + BMap jwtBody = new BMap<>(); + jwtBody.put("sub", new BString("John")); + jwtBody.put("iss", new BString("wso2")); + jwtBody.put("aud", new BValueArray(new String[] {"ballerina"})); + jwtBody.put("scope", new BString("John test Doe")); + jwtBody.put("roles", new BValueArray(new String[] {"admin", "admin2"})); + jwtBody.put("exp", new BInteger(time)); + + BValue[] inputBValues = {jwtHeader, jwtBody, new BString(keyStorePath)}; + BValue[] returns = BRunUtil.invoke(compileResult, "generateJwt", inputBValues); + Assert.assertTrue(returns[0] instanceof BString); + Assert.assertTrue(returns[0].stringValue().startsWith("eyJhbGciOiJSUzI1NiIsICJ0eXAiOiJKV1QifQ==.eyJzdWI" + + "iOiJKb2huIiwgImlzcyI6IndzbzIiLCAiZXhwIjozMjQ3NTI1MTE4OTAwMCwgImF1ZCI6WyJiYWxsZXJpbmEiXX0=.")); + + jwtToken = returns[0].stringValue(); + } - jwtToken = generateJWT(); + @Test(description = "Test JWT verification", priority = 2) + private void testVerifyJwt() { + BMap trustStore = new BMap<>(); + trustStore.put("path", new BString(trustStorePath)); + trustStore.put("password", new BString("ballerina")); + BMap jwtConfig = new BMap<>(); + jwtConfig.put("issuer", new BString("wso2")); + jwtConfig.put("audience", new BString("ballerina")); + jwtConfig.put("trustStore", trustStore); + jwtConfig.put("clockSkew", new BInteger(0)); + jwtConfig.put("certificateAlias", new BString("ballerina")); + BValue[] inputBValues = {new BString(jwtToken), jwtConfig}; + BValue[] returns = BRunUtil.invoke(compileResult, "verifyJwt", inputBValues); + Assert.assertTrue(returns[0] instanceof BMap); } - @Test(description = "Test case for creating JWT authenticator with a cache") + @Test(description = "Test case for creating JWT authenticator with a cache", priority = 2) public void testCreateJwtAuthenticatorWithCache() { BValue[] inputBValues = {new BString(trustStorePath)}; BValue[] returns = BRunUtil.invoke(compileResult, "testJwtAuthenticatorCreationWithCache", inputBValues); @@ -125,7 +161,7 @@ public void testCreateJwtAuthenticatorWithCache() { Assert.assertTrue(returns[0] instanceof BMap); } - @Test(description = "Test case for JWT authenticator for authentication success") + @Test(description = "Test case for JWT authenticator for authentication success", priority = 2) public void testAuthenticationSuccess() { BValue[] inputBValues = {new BString(jwtToken), new BString(trustStorePath)}; BValue[] returns = BRunUtil.invoke(compileResult, "testAuthenticationSuccess", inputBValues); @@ -139,49 +175,6 @@ public void tearDown() throws IOException { Files.deleteIfExists(ballerinaTrustStoreCopyPath); } - private String generateJWT() throws Exception { - String header = buildHeader(); - String jwtHeader = new String(Base64.getUrlEncoder().encode(header.getBytes())); - String body = buildBody(); - String jwtBody = new String(Base64.getUrlEncoder().encode(body.getBytes())); - String assertion = jwtHeader + "." + jwtBody; - String algorithm = "RS256"; - PrivateKey privateKey = getPrivateKey(); - JWSSigner signer = new RSASigner(privateKey); - String signature = signer.sign(assertion, algorithm); - return assertion + "." + signature; - } - - private PrivateKey getPrivateKey() throws Exception { - KeyStore keyStore; - InputStream file = new FileInputStream(new File(getClass().getClassLoader().getResource( - "datafiles/keystore/ballerinaKeystore.p12").getPath())); - keyStore = KeyStore.getInstance("pkcs12"); - keyStore.load(file, "ballerina".toCharArray()); - KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry("ballerina", new KeyStore - .PasswordProtection("ballerina".toCharArray())); - return pkEntry.getPrivateKey(); - } - - private String buildHeader() { - return "{\n" + - " \"alg\": \"RS256\",\n" + - " \"typ\": \"JWT\"\n" + - "}"; - } - - private String buildBody() { - long time = System.currentTimeMillis() + 10000000; - return "{\n" + - " \"sub\": \"John\",\n" + - " \"iss\": \"wso2\",\n" + - " \"aud\": \"ballerina\",\n" + - " \"scope\": \"John test Doe\",\n" + - " \"roles\": [\"admin\",\"admin2\"],\n" + - " \"exp\": " + time + "\n" + - "}"; - } - private Map getRuntimeProperties() { Map runtimeConfigs = new HashMap<>(); runtimeConfigs.put(BALLERINA_CONF, diff --git a/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/crypto/JWTSignerTest.java b/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/crypto/JWTSignerTest.java deleted file mode 100644 index 30ca42675892..000000000000 --- a/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/crypto/JWTSignerTest.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright (c) 2018, 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.ballerinalang.stdlib.auth.jwt.crypto; - -import org.ballerinalang.auth.ldap.jwt.crypto.JWSSigner; -import org.ballerinalang.auth.ldap.jwt.crypto.RSASigner; -import org.testng.Assert; -import org.testng.annotations.Test; - -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.security.KeyStore; -import java.security.PrivateKey; - -/** - * Test native functions used to sign JWT token. - */ -public class JWTSignerTest { - - @Test(description = "Test RSASigner with SHA-256 hashing ") - public void testRSA256Verifier() throws Exception { - String data = "ewogICJhbGciOiAiUlMyNTYiLAogICJ0eXAiOiAiSldUIgp9.ewogICJzdWIiOiAiMTIzNjU0IiwKICAibmFtZSI6ICJK" + - "b2huIiwKICAiaXNzIjogIndzbzIiLAogICJhdWQiOiAiYmFsbGVyaW5hIiwKICAiZXhwIjogMTUxOTk5NDU2NDI0OQp9"; - String signature = "X10zu93zSfo0TJQdyDrWZEr5RfX-8vA3dNuxkVRhhj_v51Q7FQ2WUP_rQpJGL2VyFpu23W1ypXXGiDMqDZodqQ8v" + - "cf1ElO_qIC6ls0Ay6fHzjpLQdVU7bkFfpuqoboXfOSLCxwzHnvKNIWqmVBHW7CE4jPjb7_11QpT1CxwIUSXtVFk2" + - "Z3gpCyfwCVe_JXtBwDbyCQGO_g2tKUSwHvvNDu3THgCcB2ALIS_JznaK9iPf55YmeNwB_KRGkaY-VLvQ5iUILWp2" + - "J5SF3QavfXMNhv8GoEDBe2ZfbQgH5E-TpakoL51Ix8vELiznVl7sbtAqlD97440hW3wXoq68kboCVQ=="; - String algorithm = "RS256"; - PrivateKey privateKey = getPrivateKey(); - JWSSigner signer = new RSASigner(privateKey); - Assert.assertEquals(signature, signer.sign(data, algorithm)); - } - - private PrivateKey getPrivateKey() throws Exception { - KeyStore keyStore; - InputStream file = new FileInputStream(new File(getClass().getClassLoader().getResource( - "datafiles/keystore/ballerinaKeystore.p12").getPath())); - keyStore = java.security.KeyStore.getInstance("pkcs12"); - keyStore.load(file, "ballerina".toCharArray()); - KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry("ballerina", new KeyStore - .PasswordProtection("ballerina".toCharArray())); - return pkEntry.getPrivateKey(); - } -} diff --git a/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/crypto/JWTVerifierTest.java b/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/crypto/JWTVerifierTest.java deleted file mode 100644 index 6243c5144a65..000000000000 --- a/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/crypto/JWTVerifierTest.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (c) 2018, 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.ballerinalang.stdlib.auth.jwt.crypto; - -import org.ballerinalang.auth.ldap.jwt.crypto.JWSVerifier; -import org.ballerinalang.auth.ldap.jwt.crypto.RSAVerifier; -import org.testng.Assert; -import org.testng.annotations.Test; - -import java.io.File; -import java.io.FileInputStream; -import java.io.InputStream; -import java.security.KeyStore; -import java.security.cert.Certificate; -import java.security.interfaces.RSAPublicKey; - -/** - * Test native functions used to verify signed JWT token. - */ -public class JWTVerifierTest { - - @Test(description = "Test RSAVerifier with SHA-256 hashing ") - public void testRSA256Verifier() throws Exception { - String data = "ewogICJhbGciOiAiUlMyNTYiLAogICJ0eXAiOiAiSldUIgp9.ewogICJzdWIiOiAiMTIzNjU0IiwKICAibmFtZSI6ICJK" + - "b2huIiwKICAiaXNzIjogIndzbzIiLAogICJhdWQiOiAiYmFsbGVyaW5hIiwKICAiZXhwIjogMTUxOTk5NDU2NDI0OQp9"; - String signature = "X10zu93zSfo0TJQdyDrWZEr5RfX-8vA3dNuxkVRhhj_v51Q7FQ2WUP_rQpJGL2VyFpu23W1ypXXGiDMqDZodqQ8v" + - "cf1ElO_qIC6ls0Ay6fHzjpLQdVU7bkFfpuqoboXfOSLCxwzHnvKNIWqmVBHW7CE4jPjb7_11QpT1CxwIUSXtVFk2" + - "Z3gpCyfwCVe_JXtBwDbyCQGO_g2tKUSwHvvNDu3THgCcB2ALIS_JznaK9iPf55YmeNwB_KRGkaY-VLvQ5iUILWp2" + - "J5SF3QavfXMNhv8GoEDBe2ZfbQgH5E-TpakoL51Ix8vELiznVl7sbtAqlD97440hW3wXoq68kboCVQ=="; - String algorithm = "RS256"; - RSAPublicKey publicKey = getRSAPublicKey(); - - JWSVerifier verifier = new RSAVerifier(publicKey); - Assert.assertTrue(verifier.verify(data, signature, algorithm)); - } - - private RSAPublicKey getRSAPublicKey() throws Exception { - KeyStore trustStore; - InputStream file = new FileInputStream(new File(getClass().getClassLoader().getResource( - "datafiles/keystore/ballerinaTruststore.p12").getPath())); - trustStore = java.security.KeyStore.getInstance("pkcs12"); - trustStore.load(file, "ballerina".toCharArray()); - Certificate publicCertificate = trustStore.getCertificate("ballerina"); - return (RSAPublicKey) publicCertificate.getPublicKey(); - } -} diff --git a/stdlib/auth/src/test/resources/test-src/jwt-authenticator-test.bal b/stdlib/auth/src/test/resources/test-src/jwt-authenticator-test.bal index c80cbe97c553..16754a13b1fc 100644 --- a/stdlib/auth/src/test/resources/test-src/jwt-authenticator-test.bal +++ b/stdlib/auth/src/test/resources/test-src/jwt-authenticator-test.bal @@ -24,3 +24,12 @@ function testAuthenticationSuccess(string jwtToken, string trustStorePath) retur auth:JWTAuthProvider jwtAuthProvider = new(jwtConfig); return jwtAuthProvider.authenticate(jwtToken); } + +function generateJwt(auth:JwtHeader header, auth:JwtPayload payload, string keyStorePath) returns string|error { + crypto:KeyStore keyStore = { path: keyStorePath, password: "ballerina" }; + return auth:issueJwt(header, payload, keyStore, "ballerina", "ballerina"); +} + +function verifyJwt(string jwt, auth:JWTValidatorConfig config) returns auth:JwtPayload|error { + return auth:validateJwt(jwt, config); +} diff --git a/stdlib/http/src/main/ballerina/http/service_endpoint.bal b/stdlib/http/src/main/ballerina/http/service_endpoint.bal index f6ee70361f7f..2830cb353955 100644 --- a/stdlib/http/src/main/ballerina/http/service_endpoint.bal +++ b/stdlib/http/src/main/ballerina/http/service_endpoint.bal @@ -239,7 +239,7 @@ public type AuthProvider record { string keyAlias = ""; string keyPassword = ""; int expTime = 0; - string signingAlg = ""; + auth:JwtSigningAlgorithm signingAlg = auth:RS512; boolean propagateJwt = false; !...; }; @@ -418,7 +418,7 @@ function getInferredJwtAuthProviderConfig(AuthProvider authProvider) returns aut auth:InferredJwtAuthProviderConfig jwtAuthConfig = { issuer: authProvider.issuer == "" ? defaultIssuer : authProvider.issuer, expTime: authProvider.expTime == 0 ? defaultExpTime : authProvider.expTime, - signingAlg: authProvider.signingAlg == "" ? defaultSignAlg : authProvider.signingAlg, + signingAlg: authProvider.signingAlg, audience: authProvider.audience == "" ? defaultAudience : authProvider.audience, keyAlias: authProvider.keyAlias, keyPassword: authProvider.keyPassword, diff --git a/stdlib/http/src/test/java/org/ballerinalang/stdlib/auth/JWTAuthnHandlerTest.java b/stdlib/http/src/test/java/org/ballerinalang/stdlib/auth/JWTAuthnHandlerTest.java index eb28cd2146c7..b5250fbffd6c 100644 --- a/stdlib/http/src/test/java/org/ballerinalang/stdlib/auth/JWTAuthnHandlerTest.java +++ b/stdlib/http/src/test/java/org/ballerinalang/stdlib/auth/JWTAuthnHandlerTest.java @@ -23,26 +23,22 @@ import org.ballerinalang.launcher.util.BRunUtil; import org.ballerinalang.launcher.util.CompileResult; import org.ballerinalang.model.values.BBoolean; +import org.ballerinalang.model.values.BInteger; +import org.ballerinalang.model.values.BMap; import org.ballerinalang.model.values.BString; import org.ballerinalang.model.values.BValue; -import org.ballerinalang.auth.ldap.jwt.crypto.JWSSigner; -import org.ballerinalang.auth.ldap.jwt.crypto.RSASigner; +import org.ballerinalang.model.values.BValueArray; import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import java.io.File; -import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStream; import java.nio.file.CopyOption; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.security.KeyStore; -import java.security.PrivateKey; -import java.util.Base64; import java.util.HashMap; import java.util.Map; @@ -88,6 +84,7 @@ public class JWTAuthnHandlerTest { private String resourceRoot; private String jwtToken; private String trustStorePath; + private String keyStorePath; private static final String BALLERINA_CONF = "ballerina.conf"; private static final String KEY_STORE = "ballerinaKeystore.p12"; private static final String TRUST_SORE = "ballerinaTruststore.p12"; @@ -96,6 +93,8 @@ public class JWTAuthnHandlerTest { public void setup() throws Exception { trustStorePath = getClass().getClassLoader().getResource( "datafiles/keystore/ballerinaTruststore.p12").getPath(); + keyStorePath = getClass().getClassLoader().getResource( + "datafiles/keystore/ballerinaKeystore.p12").getPath(); resourceRoot = new File(getClass().getProtectionDomain().getCodeSource().getLocation().getPath()) .getAbsolutePath(); Path sourceRoot = Paths.get(resourceRoot, "test-src", "auth"); @@ -113,7 +112,26 @@ public void setup() throws Exception { ConfigRegistry registry = ConfigRegistry.getInstance(); registry.initRegistry(getRuntimeProperties(), ballerinaConfPath.toString(), null); - jwtToken = generateJWT(); + BMap jwtHeader = new BMap<>(); + jwtHeader.put("alg", new BString("RS256")); + jwtHeader.put("typ", new BString("JWT")); + + long time = 32475251189000l; + BMap jwtBody = new BMap<>(); + jwtBody.put("sub", new BString("John")); + jwtBody.put("iss", new BString("wso2")); + jwtBody.put("aud", new BValueArray(new String[] {"ballerina"})); + jwtBody.put("scope", new BString("John test Doe")); + jwtBody.put("roles", new BValueArray(new String[] {"admin", "admin2"})); + jwtBody.put("exp", new BInteger(time)); + + BValue[] inputBValues = {jwtHeader, jwtBody, new BString(keyStorePath)}; + BValue[] returns = BRunUtil.invoke(compileResult, "generateJwt", inputBValues); + Assert.assertTrue(returns[0] instanceof BString); + Assert.assertTrue(returns[0].stringValue().startsWith("eyJhbGciOiJSUzI1NiIsICJ0eXAiOiJKV1QifQ==.eyJzdWI" + + "iOiJKb2huIiwgImlzcyI6IndzbzIiLCAiZXhwIjozMjQ3NTI1MTE4OTAwMCwgImF1ZCI6WyJiYWxsZXJpbmEiXX0=.")); + + jwtToken = returns[0].stringValue(); } @Test(description = "Test case for JWT auth interceptor canHandle method, without the bearer header") @@ -151,49 +169,6 @@ public void tearDown() throws IOException { Files.deleteIfExists(ballerinaTrustStoreCopyPath); } - private String generateJWT() throws Exception { - String header = buildHeader(); - String jwtHeader = new String(Base64.getUrlEncoder().encode(header.getBytes())); - String body = buildBody(); - String jwtBody = new String(Base64.getUrlEncoder().encode(body.getBytes())); - String assertion = jwtHeader + "." + jwtBody; - String algorithm = "RS256"; - PrivateKey privateKey = getPrivateKey(); - JWSSigner signer = new RSASigner(privateKey); - String signature = signer.sign(assertion, algorithm); - return assertion + "." + signature; - } - - private PrivateKey getPrivateKey() throws Exception { - KeyStore keyStore; - InputStream file = new FileInputStream(new File(getClass().getClassLoader().getResource( - "datafiles/keystore/ballerinaKeystore.p12").getPath())); - keyStore = KeyStore.getInstance("pkcs12"); - keyStore.load(file, "ballerina".toCharArray()); - KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry) keyStore.getEntry("ballerina", - new KeyStore.PasswordProtection("ballerina".toCharArray())); - return pkEntry.getPrivateKey(); - } - - private String buildHeader() { - return "{\n" + - " \"alg\": \"RS256\",\n" + - " \"typ\": \"JWT\"\n" + - "}"; - } - - private String buildBody() { - long time = System.currentTimeMillis() + 10000000; - return "{\n" + - " \"sub\": \"John\",\n" + - " \"iss\": \"wso2\",\n" + - " \"aud\": \"ballerina\",\n" + - " \"scope\": \"John test Doe\",\n" + - " \"roles\": [\"admin\",\"admin2\"],\n" + - " \"exp\": " + time + "\n" + - "}"; - } - private Map getRuntimeProperties() { Map runtimeConfigs = new HashMap<>(); runtimeConfigs.put(BALLERINA_CONF, diff --git a/stdlib/http/src/test/resources/test-src/auth/jwt-authn-handler-test.bal b/stdlib/http/src/test/resources/test-src/auth/jwt-authn-handler-test.bal index 8a6815527a32..1594d95e736d 100644 --- a/stdlib/http/src/test/resources/test-src/auth/jwt-authn-handler-test.bal +++ b/stdlib/http/src/test/resources/test-src/auth/jwt-authn-handler-test.bal @@ -56,3 +56,12 @@ function createJwtAuthProvider(string trustStorePath) returns auth:JWTAuthProvid auth:JWTAuthProvider jwtAuthProvider = new(jwtConfig); return jwtAuthProvider; } + +function generateJwt(auth:JwtHeader header, auth:JwtPayload payload, string keyStorePath) returns string|error { + crypto:KeyStore keyStore = { path: keyStorePath, password: "ballerina" }; + return auth:issueJwt(header, payload, keyStore, "ballerina", "ballerina"); +} + +function verifyJwt(string jwt, auth:JWTValidatorConfig config) returns auth:JwtPayload|error { + return auth:validateJwt(jwt, config); +} From 09ce49bd91c1c0994471ea6a67d4c8717c469c58 Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Fri, 8 Feb 2019 10:52:35 +0530 Subject: [PATCH 08/26] Add certificate parsing to crypto stdlib --- stdlib/crypto/pom.xml | 10 ++++ .../src/main/ballerina/crypto/crypto.bal | 26 ++++++++++ .../stdlib/crypto/Constants.java | 36 +++++++++++++ .../stdlib/crypto/CryptoUtils.java | 41 +++++++++++++++ .../crypto/nativeimpl/DecodePrivateKey.java | 3 +- .../crypto/nativeimpl/DecodePublicKey.java | 51 ++++++++++++++++--- .../stdlib/crypto/KeyParsingTest.java | 19 ++++++- ...ontext.java => AuthenticationContext.java} | 0 8 files changed, 177 insertions(+), 9 deletions(-) rename stdlib/runtime/src/main/java/org/ballerinalang/stdlib/runtime/nativeimpl/{AuthContext.java => AuthenticationContext.java} (100%) diff --git a/stdlib/crypto/pom.xml b/stdlib/crypto/pom.xml index 6c3449e104df..99b9e27a7a1b 100644 --- a/stdlib/crypto/pom.xml +++ b/stdlib/crypto/pom.xml @@ -44,6 +44,16 @@ org.ballerinalang lib-creator + + org.ballerinalang + ballerina-time + + + org.ballerinalang + ballerina-time + zip + ballerina-binary-repo + org.ballerinalang ballerina-builtin diff --git a/stdlib/crypto/src/main/ballerina/crypto/crypto.bal b/stdlib/crypto/src/main/ballerina/crypto/crypto.bal index 8dd8c5dcf1a1..c663582cedd9 100644 --- a/stdlib/crypto/src/main/ballerina/crypto/crypto.bal +++ b/stdlib/crypto/src/main/ballerina/crypto/crypto.bal @@ -14,6 +14,8 @@ // specific language governing permissions and limitations // under the License. +import ballerina/time; + # The key algorithms supported by crypto module. public type KeyAlgorithm RSA; @@ -51,8 +53,32 @@ public type PrivateKey record { # Public key used in cryptographic operations. # # + algorithm - Key algorithm +# + certificate - Public key certificate public type PublicKey record { KeyAlgorithm algorithm; + Certificate? certificate; + !...; +}; + +# X509 public key certificate information. +# +# + version0 - Version number +# + serial - Serial number +# + issuer - Issuer name +# + subject - Subject name +# + notBefore - Not before validity period of certificate +# + notAfter - Not after validity period of certificate +# + signature - Raw signature bits +# + signingAlgorithm - Signature algorithm +public type Certificate record { + int version0; + int serial; + string issuer; + string subject; + time:Time notBefore; + time:Time notAfter; + byte[] signature; + string signingAlgorithm; !...; }; diff --git a/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/Constants.java b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/Constants.java index f376aee27bed..6cea5fb6fccd 100644 --- a/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/Constants.java +++ b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/Constants.java @@ -40,12 +40,18 @@ public class Constants { // Record used to reference to a public key. public static final String PUBLIC_KEY_RECORD = "PublicKey"; + // Record used to reference to a public key certificate. + public static final String CERTIFICATE_RECORD = "Certificate"; + // Native data key for private key within the PrivateKey record. public static final String NATIVE_DATA_PRIVATE_KEY = "NATIVE_DATA_PRIVATE_KEY"; // Native data key for private key within the PublicKey record. public static final String NATIVE_DATA_PUBLIC_KEY = "NATIVE_DATA_PUBLIC_KEY"; + // Native data key for private key within the PublicKey record. + public static final String NATIVE_DATA_PUBLIC_KEY_CERTIFICATE = "NATIVE_DATA_PUBLIC_KEY_CERTIFICATE"; + // Path field in KEY_STORE_RECORD record. public static final String KEY_STORE_RECORD_PATH_FIELD = "path"; @@ -64,6 +70,33 @@ public class Constants { // Algorithm field in PUBLIC_KEY_RECORD. public static final String PUBLIC_KEY_RECORD_ALGORITHM_FIELD = "algorithm"; + // Algorithm field in PUBLIC_KEY_RECORD. + public static final String PUBLIC_KEY_RECORD_CERTIFICATE_FIELD = "certificate"; + + // Version field in CERTIFICATE_RECORD. + public static final String CERTIFICATE_RECORD_VERSION_FIELD = "version0"; + + // Serial field in CERTIFICATE_RECORD. + public static final String CERTIFICATE_RECORD_SERIAL_FIELD = "serial"; + + // Issuer field in CERTIFICATE_RECORD. + public static final String CERTIFICATE_RECORD_ISSUER_FIELD = "issuer"; + + // Subject field in CERTIFICATE_RECORD. + public static final String CERTIFICATE_RECORD_SUBJECT_FIELD = "subject"; + + // NotBefore field in CERTIFICATE_RECORD. + public static final String CERTIFICATE_RECORD_NOT_BEFORE_FIELD = "notBefore"; + + // NotAfter field in CERTIFICATE_RECORD. + public static final String CERTIFICATE_RECORD_NOT_AFTER_FIELD = "notAfter"; + + // Signature field in CERTIFICATE_RECORD. + public static final String CERTIFICATE_RECORD_SIGNATURE_FIELD = "signature"; + + // SigningAlgorithm field in CERTIFICATE_RECORD. + public static final String CERTIFICATE_RECORD_SIGNATURE_ALG_FIELD = "signingAlgorithm"; + // Error record for crypto module. public static final String CRYPTO_ERROR = "CryptoError"; @@ -75,4 +108,7 @@ public class Constants { // PKCS12 keystore type public static final String KEYSTORE_TYPE_PKCS12 = "PKCS12"; + + // GMT timezone name used for X509 validity times + public static final String TIMEZONE_GMT = "GMT"; } diff --git a/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/CryptoUtils.java b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/CryptoUtils.java index b970945c1dd6..f012db39ce5b 100644 --- a/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/CryptoUtils.java +++ b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/CryptoUtils.java @@ -34,6 +34,8 @@ import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.crypto.Mac; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; @@ -45,6 +47,8 @@ */ public class CryptoUtils { + private static final Pattern varPattern = Pattern.compile("\\$\\{([^}]*)}"); + private CryptoUtils() { } @@ -147,4 +151,41 @@ public static BError createCryptoError(Context context, String errMsg) { errorRecord.put(Constants.MESSAGE, new BString(errMsg)); return BLangVMErrors.createError(context, true, BTypes.typeError, Constants.ENCODING_ERROR_CODE, errorRecord); } + + public static String substituteVariables(String value) { + Matcher matcher = varPattern.matcher(value); + boolean found = matcher.find(); + if (!found) { + return value; + } else { + StringBuffer sb = new StringBuffer(); + + do { + String sysPropKey = matcher.group(1); + String sysPropValue = getSystemVariableValue(sysPropKey, null); + if (sysPropValue == null || sysPropValue.length() == 0) { + throw new RuntimeException("System property " + sysPropKey + " is not specified"); + } + + sysPropValue = sysPropValue.replace("\\", "\\\\"); + matcher.appendReplacement(sb, sysPropValue); + } while(matcher.find()); + + matcher.appendTail(sb); + return sb.toString(); + } + } + + public static String getSystemVariableValue(String variableName, String defaultValue) { + String value; + if (System.getProperty(variableName) != null) { + value = System.getProperty(variableName); + } else if (System.getenv(variableName) != null) { + value = System.getenv(variableName); + } else { + value = defaultValue; + } + + return value; + } } diff --git a/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/DecodePrivateKey.java b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/DecodePrivateKey.java index 8034ae18a87a..7c759aa0dc2a 100644 --- a/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/DecodePrivateKey.java +++ b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/DecodePrivateKey.java @@ -74,7 +74,8 @@ public void execute(Context context) { PrivateKey privateKey = null; // TODO: Add support for reading key from a provided string or directly using PEM encoded file. if (keyStore != null) { - File keyStoreFile = new File(keyStore.get(Constants.KEY_STORE_RECORD_PATH_FIELD).stringValue()); + File keyStoreFile = new File(CryptoUtils + .substituteVariables(keyStore.get(Constants.KEY_STORE_RECORD_PATH_FIELD).stringValue())); try (FileInputStream fileInputStream = new FileInputStream(keyStoreFile)) { KeyStore keystore = KeyStore.getInstance(Constants.KEYSTORE_TYPE_PKCS12); try { diff --git a/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/DecodePublicKey.java b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/DecodePublicKey.java index c4b5fddb999d..79a077fc5077 100644 --- a/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/DecodePublicKey.java +++ b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/DecodePublicKey.java @@ -22,15 +22,19 @@ import org.ballerinalang.bre.bvm.BlockingNativeCallableUnit; import org.ballerinalang.connector.api.BLangConnectorSPIUtil; import org.ballerinalang.model.types.TypeKind; +import org.ballerinalang.model.values.BInteger; import org.ballerinalang.model.values.BMap; import org.ballerinalang.model.values.BString; import org.ballerinalang.model.values.BValue; +import org.ballerinalang.model.values.BValueArray; import org.ballerinalang.natives.annotations.Argument; import org.ballerinalang.natives.annotations.BallerinaFunction; import org.ballerinalang.natives.annotations.ReturnType; import org.ballerinalang.stdlib.crypto.Constants; import org.ballerinalang.stdlib.crypto.CryptoUtils; +import org.ballerinalang.stdlib.time.util.TimeUtils; import org.ballerinalang.util.exceptions.BallerinaException; +import org.wso2.carbon.transport.http.netty.common.Util; import java.io.File; import java.io.FileInputStream; @@ -40,7 +44,9 @@ import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; +import java.security.cert.Certificate; import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; /** * Function for decoding public key. @@ -71,7 +77,8 @@ public void execute(Context context) { PublicKey publicKey = null; // TODO: Add support for reading key from a provided string or directly using PEM encoded file. if (keyStore != null) { - File keyStoreFile = new File(keyStore.get(Constants.KEY_STORE_RECORD_PATH_FIELD).stringValue()); + File keyStoreFile = new File(Util.substituteVariables(keyStore.get(Constants.KEY_STORE_RECORD_PATH_FIELD) + .stringValue())); try (FileInputStream fileInputStream = new FileInputStream(keyStoreFile)) { KeyStore keystore = KeyStore.getInstance(Constants.KEYSTORE_TYPE_PKCS12); try { @@ -83,15 +90,47 @@ public void execute(Context context) { return; } + Certificate certificate = keystore.getCertificate(keyAlias.stringValue()); + BMap certificateBMap = BLangConnectorSPIUtil.createBStruct(context, + Constants.CRYPTO_PACKAGE, Constants.CERTIFICATE_RECORD); + if (certificate instanceof X509Certificate) { + X509Certificate x509Certificate = (X509Certificate) certificate; + certificateBMap.put(Constants.CERTIFICATE_RECORD_ISSUER_FIELD, + new BString(x509Certificate.getIssuerX500Principal().getName())); + certificateBMap.put(Constants.CERTIFICATE_RECORD_SUBJECT_FIELD, + new BString(x509Certificate.getSubjectX500Principal().getName())); + certificateBMap.put(Constants.CERTIFICATE_RECORD_VERSION_FIELD, + new BInteger(x509Certificate.getVersion())); + certificateBMap.put(Constants.CERTIFICATE_RECORD_SERIAL_FIELD, + new BInteger(x509Certificate.getSerialNumber().longValue())); + + certificateBMap.put(Constants.CERTIFICATE_RECORD_NOT_BEFORE_FIELD, TimeUtils + .createTimeStruct(TimeUtils.getTimeZoneStructInfo(context), + TimeUtils.getTimeStructInfo(context), x509Certificate.getNotBefore().getTime(), + Constants.TIMEZONE_GMT)); + certificateBMap.put(Constants.CERTIFICATE_RECORD_NOT_AFTER_FIELD, TimeUtils + .createTimeStruct(TimeUtils.getTimeZoneStructInfo(context), + TimeUtils.getTimeStructInfo(context), x509Certificate.getNotAfter().getTime(), + Constants.TIMEZONE_GMT)); + + certificateBMap.put(Constants.CERTIFICATE_RECORD_SIGNATURE_FIELD, + new BValueArray(x509Certificate.getSignature())); + certificateBMap.put(Constants.CERTIFICATE_RECORD_SIGNATURE_ALG_FIELD, + new BString(x509Certificate.getSigAlgName())); + } + publicKey = certificate.getPublicKey(); //TODO: Add support for DSA/ECDSA keys and associated crypto operations - publicKey = keystore.getCertificate(keyAlias.stringValue()).getPublicKey(); if (publicKey.getAlgorithm().equals("RSA")) { - BMap publicKeyStruct = BLangConnectorSPIUtil.createBStruct(context, + BMap publicKeyBMap = BLangConnectorSPIUtil.createBStruct(context, Constants.CRYPTO_PACKAGE, Constants.PUBLIC_KEY_RECORD); - publicKeyStruct.addNativeData(Constants.NATIVE_DATA_PUBLIC_KEY, publicKey); - publicKeyStruct.put(Constants.PUBLIC_KEY_RECORD_ALGORITHM_FIELD, + publicKeyBMap.addNativeData(Constants.NATIVE_DATA_PUBLIC_KEY, publicKey); + publicKeyBMap.addNativeData(Constants.NATIVE_DATA_PUBLIC_KEY_CERTIFICATE, certificate); + publicKeyBMap.put(Constants.PUBLIC_KEY_RECORD_ALGORITHM_FIELD, new BString(publicKey.getAlgorithm())); - context.setReturnValues(publicKeyStruct); + if (certificateBMap.size() > 0) { + publicKeyBMap.put(Constants.PUBLIC_KEY_RECORD_CERTIFICATE_FIELD, certificateBMap); + } + context.setReturnValues(publicKeyBMap); } else { context.setReturnValues(CryptoUtils.createCryptoError(context, "not a valid RSA key")); } diff --git a/stdlib/crypto/src/test/java/org/ballerinalang/stdlib/crypto/KeyParsingTest.java b/stdlib/crypto/src/test/java/org/ballerinalang/stdlib/crypto/KeyParsingTest.java index bd142e662281..dd88aedf8583 100644 --- a/stdlib/crypto/src/test/java/org/ballerinalang/stdlib/crypto/KeyParsingTest.java +++ b/stdlib/crypto/src/test/java/org/ballerinalang/stdlib/crypto/KeyParsingTest.java @@ -22,6 +22,7 @@ import org.ballerinalang.model.values.BMap; import org.ballerinalang.model.values.BString; import org.ballerinalang.model.values.BValue; +import org.ballerinalang.model.values.BValueArray; import org.ballerinalang.util.exceptions.BLangRuntimeException; import org.testng.Assert; import org.testng.annotations.BeforeClass; @@ -51,7 +52,8 @@ public void testParsingEncryptedPrivateKeyFromP12() { new BString("ballerina"), new BString("ballerina")}; BValue[] returnValues = BRunUtil.invoke(compileResult, "testParsingPrivateKeyFromP12", args); Assert.assertFalse(returnValues == null || returnValues.length == 0 || returnValues[0] == null); - Assert.assertEquals(((BMap) returnValues[0]).get("algorithm").stringValue(), "RSA"); + Assert.assertEquals(((BMap) returnValues[0]).get(Constants.PRIVATE_KEY_RECORD_ALGORITHM_FIELD).stringValue(), + "RSA"); } @Test(description = "Check parsing public-key from a p12 file.") @@ -61,7 +63,20 @@ public void testParsingPublicKeyFromP12() { new BString("ballerina")}; BValue[] returnValues = BRunUtil.invoke(compileResult, "testParsingPublicKeyFromP12", args); Assert.assertFalse(returnValues == null || returnValues.length == 0 || returnValues[0] == null); - Assert.assertEquals(((BMap) returnValues[0]).get("algorithm").stringValue(), "RSA"); + Assert.assertEquals(((BMap) returnValues[0]).get(Constants.PUBLIC_KEY_RECORD_ALGORITHM_FIELD).stringValue(), + "RSA"); + Assert.assertTrue(((BMap) returnValues[0]).get(Constants.PUBLIC_KEY_RECORD_CERTIFICATE_FIELD) instanceof BMap); + BMap certificate = (BMap) ((BMap) returnValues[0]).get("certificate"); + Assert.assertEquals(certificate.get(Constants.CERTIFICATE_RECORD_SERIAL_FIELD).stringValue(), "2097012467"); + Assert.assertEquals(certificate.get(Constants.CERTIFICATE_RECORD_ISSUER_FIELD).stringValue(), + "CN=localhost,OU=WSO2,O=WSO2,L=Mountain View,ST=CA,C=US"); + Assert.assertEquals(certificate.get(Constants.CERTIFICATE_RECORD_SUBJECT_FIELD).stringValue(), + "CN=localhost,OU=WSO2,O=WSO2,L=Mountain View,ST=CA,C=US"); + Assert.assertTrue(certificate.get(Constants.CERTIFICATE_RECORD_NOT_BEFORE_FIELD) instanceof BMap); + Assert.assertTrue(certificate.get(Constants.CERTIFICATE_RECORD_NOT_AFTER_FIELD) instanceof BMap); + Assert.assertTrue(certificate.get(Constants.CERTIFICATE_RECORD_SIGNATURE_FIELD) instanceof BValueArray); + Assert.assertEquals(certificate.get(Constants.CERTIFICATE_RECORD_SIGNATURE_ALG_FIELD).stringValue(), + "SHA256withRSA"); } @Test(description = "Check attemting to read a private key from a non-existing p12 file.", diff --git a/stdlib/runtime/src/main/java/org/ballerinalang/stdlib/runtime/nativeimpl/AuthContext.java b/stdlib/runtime/src/main/java/org/ballerinalang/stdlib/runtime/nativeimpl/AuthenticationContext.java similarity index 100% rename from stdlib/runtime/src/main/java/org/ballerinalang/stdlib/runtime/nativeimpl/AuthContext.java rename to stdlib/runtime/src/main/java/org/ballerinalang/stdlib/runtime/nativeimpl/AuthenticationContext.java From a630b1981bf9ac69854aa2df82de10de493cba7f Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Fri, 8 Feb 2019 12:09:16 +0530 Subject: [PATCH 09/26] Rename fields of invocation context --- .../secured_client_with_jwt_auth.bal | 4 +- .../tests/secured_service_with_jwt_test.bal | 16 ++-- .../auth/config_auth_store_provider.bal | 6 +- .../main/ballerina/auth/jwt_auth_provider.bal | 34 ++++---- .../auth/jwt_supported_auth_provider_util.bal | 8 +- .../src/main/ballerina/auth/jwt_validator.bal | 32 +++++++- .../auth/ldap_auth_store_provider.bal | 6 +- .../main/ballerina/http/auth/authz_filter.bal | 2 +- .../ballerina/http/auth/authz_handler.bal | 8 +- .../http/auth/basic_authn_handler.bal | 4 +- .../ballerina/http/http_secure_client.bal | 2 +- .../src/main/ballerina/runtime/Module.md | 2 +- .../ballerina/runtime/invocation-context.bal | 14 ++-- .../nativeimpl/AuthenticationContext.java | 18 ++--- .../runtime/nativeimpl/InvocationContext.java | 10 +-- .../nativeimpl/InvocationContextUtils.java | 17 ++-- .../resources/test-src/invocation-context.bal | 26 +++---- .../ballerinalang/test/auth/AuthBaseTest.java | 2 +- .../test/auth/ServiceLevelAuthnTest.java | 78 +++++++------------ ..._certificate_with_no_expiry_validation.bal | 46 +++++++++++ 20 files changed, 194 insertions(+), 141 deletions(-) create mode 100644 tests/ballerina-integration-test/src/test/resources/auth/authservices/14_authn_with_expired_certificate_with_no_expiry_validation.bal diff --git a/examples/secured-client-with-jwt-auth/secured_client_with_jwt_auth.bal b/examples/secured-client-with-jwt-auth/secured_client_with_jwt_auth.bal index c7ccc3540386..88f9109140fc 100644 --- a/examples/secured-client-with-jwt-auth/secured_client_with_jwt_auth.bal +++ b/examples/secured-client-with-jwt-auth/secured_client_with_jwt_auth.bal @@ -23,8 +23,8 @@ public function main() { "KE3DZgssvgPgI9PBItnkipQ3CqqXWhV-RFBkVBEGPDYXTUVGbXhdNOBSwKw5ZoVJrCU" + "iNG5XD0K4sgN9udVTi3EMKNMnVQaq399k6RYPAy3vIhByS6QZtRjOG8X93WJw-9GLiH" + "vcabuid80lnrs2-mAEcstgiHVw"; - runtime:getInvocationContext().authContext.scheme = "jwt"; - runtime:getInvocationContext().authContext.authToken = token; + runtime:getInvocationContext().authenticationContext.scheme = "jwt"; + runtime:getInvocationContext().authenticationContext.authToken = token; // Send a `GET` request to the specified endpoint. var response = httpEndpoint->get("/hello/sayHello"); diff --git a/examples/secured-service-with-jwt/tests/secured_service_with_jwt_test.bal b/examples/secured-service-with-jwt/tests/secured_service_with_jwt_test.bal index feac7ad913ed..1b82985b8c85 100644 --- a/examples/secured-service-with-jwt/tests/secured_service_with_jwt_test.bal +++ b/examples/secured-service-with-jwt/tests/secured_service_with_jwt_test.bal @@ -72,13 +72,13 @@ function setJwtTokenToAuthContext () { "KE3DZgssvgPgI9PBItnkipQ3CqqXWhV-RFBkVBEGPDYXTUVGbXhdNOBSwKw5ZoVJrCU" + "iNG5XD0K4sgN9udVTi3EMKNMnVQaq399k6RYPAy3vIhByS6QZtRjOG8X93WJw-9GLiH" + "vcabuid80lnrs2-mAEcstgiHVw"; - runtime:getInvocationContext().authContext.scheme = "jwt"; - runtime:getInvocationContext().authContext.authToken = token; + runtime:getInvocationContext().authenticationContext.scheme = "jwt"; + runtime:getInvocationContext().authenticationContext.authToken = token; } function clearTokenFromAuthContext () { - runtime:getInvocationContext().authContext.scheme = "jwt"; - runtime:getInvocationContext().authContext.authToken = ""; + runtime:getInvocationContext().authenticationContext.scheme = "jwt"; + runtime:getInvocationContext().authenticationContext.authToken = ""; } function setInvalidJwtTokenToAuthContext () { @@ -91,8 +91,8 @@ function setInvalidJwtTokenToAuthContext () { "aPWGUnUoIExjYxrBMLGUTzMaM1knyI8agG7z6nKm0ZBMdti1AphGkqH50rDm9Arjvy256aNO-" + "cw6lWkDneZl5WdV63RGNNNSj8ElyRW6HMdLmHQ3HIkQ4f1K8tCshwgbyb19bw8nCeYihpPeOn" + "gVobfGY2yXm7QGjmiVInALAqisylo348WB6qOKduDrbDZYcFDKQuYConx5wF-7Wl9hg2HA"; - runtime:getInvocationContext().authContext.scheme = "jwt"; - runtime:getInvocationContext().authContext.authToken = token; + runtime:getInvocationContext().authenticationContext.scheme = "jwt"; + runtime:getInvocationContext().authenticationContext.authToken = token; } function setJwtTokenWithNoScopesToAuthContext () { @@ -105,6 +105,6 @@ function setJwtTokenWithNoScopesToAuthContext () { "NhJRyht0GSa59VhonCFIAL505_u5vfO4fhmCjslYCr6WcpYW1tLf-vDmRLIqshYX7RZkK" + "Es2a1pfjg5XkJiJSxqQ_-lLzeQfb-eMmZzT5ob-cE9qpBhjrXoYpYLy371TtuOdREdhXh" + "Ogu12RJMaCE1FlA1ZoyLrmzj2Mm3RHc_A88lKoGvaEBcGzJwllekuQeDUJ1P90SGA"; - runtime:getInvocationContext().authContext.scheme = "jwt"; - runtime:getInvocationContext().authContext.authToken = token; + runtime:getInvocationContext().authenticationContext.scheme = "jwt"; + runtime:getInvocationContext().authenticationContext.authToken = token; } diff --git a/stdlib/auth/src/main/ballerina/auth/config_auth_store_provider.bal b/stdlib/auth/src/main/ballerina/auth/config_auth_store_provider.bal index bcf902d606b7..d4564cc67c87 100644 --- a/stdlib/auth/src/main/ballerina/auth/config_auth_store_provider.bal +++ b/stdlib/auth/src/main/ballerina/auth/config_auth_store_provider.bal @@ -30,10 +30,10 @@ public type ConfigAuthStoreProvider object { public function authenticate(string user, string password) returns boolean { boolean isAuthenticated = password == self.readPassword(user); if(isAuthenticated){ - runtime:UserPrincipal userPrincipal = runtime:getInvocationContext().userPrincipal; - userPrincipal.userId = user; + runtime:Principal principal = runtime:getInvocationContext().principal; + principal.userId = user; // By default set userId as username. - userPrincipal.username = user; + principal.username = user; } return isAuthenticated; } diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_auth_provider.bal b/stdlib/auth/src/main/ballerina/auth/jwt_auth_provider.bal index b88f5f6ef1b8..120bb4dd33d3 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_auth_provider.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_auth_provider.bal @@ -44,7 +44,7 @@ public type JWTAuthProvider object { if (self.authCache.hasKey(jwtToken)) { var payload = self.authenticateFromCache(jwtToken); if (payload is JwtPayload) { - self.setAuthContext(payload, jwtToken); + self.setAuthenticationContext(payload, jwtToken); return true; } else { return false; @@ -53,7 +53,7 @@ public type JWTAuthProvider object { var payload = validateJwt(jwtToken, self.jwtAuthProviderConfig); if (payload is JwtPayload) { - self.setAuthContext(payload, jwtToken); + self.setAuthenticationContext(payload, jwtToken); self.addToAuthenticationCache(jwtToken, payload.exp, payload); return true; } else { @@ -62,8 +62,8 @@ public type JWTAuthProvider object { } function authenticateFromCache(string jwtToken) returns JwtPayload|() { - var context = trap self.authCache.get(jwtToken); - if (context is CachedJWTAuthContext) { + var context = trap self.authCache.get(jwtToken); + if (context is CachedJWTAuthenticationContext) { // convert to current time and check the expiry time if (context.expiryTime > (time:currentTime().time / 1000)) { JwtPayload payload = context.jwtPayload; @@ -77,34 +77,34 @@ public type JWTAuthProvider object { } function addToAuthenticationCache(string jwtToken, int exp, JwtPayload payload) { - CachedJWTAuthContext cachedContext = {jwtPayload : payload, expiryTime : exp}; + CachedJWTAuthenticationContext cachedContext = {jwtPayload : payload, expiryTime : exp}; self.authCache.put(jwtToken, cachedContext); log:printDebug(function() returns string { return "Add authenticated user :" + payload.sub + " to the cache"; }); } - function setAuthContext(JwtPayload jwtPayload, string jwtToken) { - runtime:UserPrincipal userPrincipal = runtime:getInvocationContext().userPrincipal; - userPrincipal.userId = jwtPayload.iss + ":" + jwtPayload.sub; + function setAuthenticationContext(JwtPayload jwtPayload, string jwtToken) { + runtime:Principal principal = runtime:getInvocationContext().principal; + principal.userId = jwtPayload.iss + ":" + jwtPayload.sub; // By default set sub as username. - userPrincipal.username = jwtPayload.sub; - userPrincipal.claims = jwtPayload.customClaims; + principal.username = jwtPayload.sub; + principal.claims = jwtPayload.customClaims; if (jwtPayload.customClaims.hasKey(SCOPES)) { var scopeString = jwtPayload.customClaims[SCOPES]; if (scopeString is string) { - userPrincipal.scopes = scopeString.split(" "); + principal.scopes = scopeString.split(" "); } } if (jwtPayload.customClaims.hasKey(USERNAME)) { var name = jwtPayload.customClaims[USERNAME]; if (name is string) { - userPrincipal.username = name; + principal.username = name; } } - runtime:AuthContext authContext = runtime:getInvocationContext().authContext; - authContext.scheme = AUTH_TYPE_JWT; - authContext.authToken = jwtToken; + runtime:AuthenticationContext authenticationContext = runtime:getInvocationContext().authenticationContext; + authenticationContext.scheme = AUTH_TYPE_JWT; + authenticationContext.authToken = jwtToken; } }; @@ -121,16 +121,18 @@ const string AUTH_TYPE_JWT = "jwt"; # + clockSkew - Time in seconds to mitigate clock skew # + trustStore - Trust store used for signature verification # + certificateAlias - Token signed key alias +# + validateCertificate - Validate public key certificate notBefore and notAfter periods public type JWTAuthProviderConfig record { string issuer; string audience; int clockSkew = 0; crypto:TrustStore trustStore; string certificateAlias; + boolean validateCertificate?; !...; }; -type CachedJWTAuthContext record { +type CachedJWTAuthenticationContext record { JwtPayload jwtPayload; int expiryTime; !...; diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal b/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal index ff842671b873..3aace5b73cf7 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal @@ -40,7 +40,7 @@ import ballerina/crypto; !...; }; -# Sets the jwt access token to the AuthContext +# Sets the jwt access token to the AuthenticationContext # # + username - user name # + authConfig - authentication provider configurations that supports generating JWT for client interactions @@ -49,9 +49,9 @@ function setAuthToken(string username, InferredJwtAuthProviderConfig authConfig) JwtPayload payload = createPayload(username, authConfig); var token = issueJwt(header, payload, authConfig.keyStore, authConfig.keyAlias, authConfig.keyPassword); if (token is string) { - runtime:AuthContext authContext = runtime:getInvocationContext().authContext; - authContext.scheme = "jwt"; - authContext.authToken = token; + runtime:AuthenticationContext authenticationContext = runtime:getInvocationContext().authenticationContext; + authenticationContext.scheme = "jwt"; + authenticationContext.authToken = token; } } diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal b/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal index 3900c11de643..ec656c7303c9 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal @@ -26,13 +26,16 @@ import ballerina/io; # + audience - Expected audience # + clockSkew - Clock skew in seconds # + trustStore - Trust store used for signature verification -# + certificateAlias - Token signed key alias +# + certificateAlias - Token signed public key certificate alias +# + validateCertificate - Validate public key certificate notBefore and notAfter periods public type JWTValidatorConfig record { string issuer; string audience; int clockSkew = 0; crypto:TrustStore trustStore; string certificateAlias; + boolean validateCertificate?; + !...; }; # Validity given JWT string. @@ -231,6 +234,13 @@ function validateJwtRecords(string[] encodedJWTComponents, JwtHeader jwtHeader, { message : "Mandatory fields(Issuer,Subject, Expiration time or Audience) are empty in the given JSON Web Token." }); return jwtError; } + if (config["validateCertificate"] is ()) { + config["validateCertificate"] = true; + } + if (config.validateCertificate == true && !check validateCertificate(config)) { + error jwtError = error(AUTH_ERROR_CODE, { message : "Public key certificate validity period has passed" }); + return jwtError; + } var signatureValidationResult = validateSignature(encodedJWTComponents, jwtHeader, config); if (signatureValidationResult is error) { error jwtError = error(AUTH_ERROR_CODE, { message : signatureValidationResult.reason() }); @@ -272,11 +282,29 @@ function validateMandatoryFields(JwtPayload jwtPayload) returns (boolean) { return true; } +function validateCertificate(JWTValidatorConfig config) returns boolean|error { + crypto:PublicKey publicKey = check crypto:decodePublicKey(keyStore = config.trustStore, + keyAlias = config.certificateAlias); + time:Time currTimeInGmt = time:toTimeZone(time:currentTime(), "GMT"); + int currTimeInGmtMillis = currTimeInGmt.time; + + var certificate = publicKey.certificate; + if (certificate is crypto:Certificate) { + int notBefore = certificate.notBefore.time; + int notAfter = certificate.notAfter.time; + if (currTimeInGmtMillis >= notBefore && currTimeInGmtMillis <= notAfter) { + return true; + } + } + return false; +} + function validateSignature(string[] encodedJWTComponents, JwtHeader jwtHeader, JWTValidatorConfig config) returns boolean|error { string assertion = encodedJWTComponents[0] + "." + encodedJWTComponents[1]; byte[] signPart = check encoding:decodeBase64Url(encodedJWTComponents[2]); - crypto:PublicKey publicKey = check crypto:decodePublicKey(keyStore = config.trustStore, keyAlias = config.certificateAlias); + crypto:PublicKey publicKey = check crypto:decodePublicKey(keyStore = config.trustStore, + keyAlias = config.certificateAlias); if (jwtHeader.alg == RS256) { return crypto:verifyRsaSha256Signature(assertion.toByteArray("UTF-8"), signPart, publicKey); } else if (jwtHeader.alg == RS384) { diff --git a/stdlib/auth/src/main/ballerina/auth/ldap_auth_store_provider.bal b/stdlib/auth/src/main/ballerina/auth/ldap_auth_store_provider.bal index a58a740cc761..814a8ecabb1b 100644 --- a/stdlib/auth/src/main/ballerina/auth/ldap_auth_store_provider.bal +++ b/stdlib/auth/src/main/ballerina/auth/ldap_auth_store_provider.bal @@ -103,10 +103,10 @@ public type LdapAuthStoreProvider object { public function authenticate(string username, string password) returns boolean { boolean isAuthenticated = self.doAuthenticate(username, password); if (isAuthenticated) { - runtime:UserPrincipal userPrincipal = runtime:getInvocationContext().userPrincipal; - userPrincipal.userId = self.ldapAuthProviderConfig.domainName + ":" + username; + runtime:Principal principal = runtime:getInvocationContext().principal; + principal.userId = self.ldapAuthProviderConfig.domainName + ":" + username; // By default set userId as username. - userPrincipal.username = username; + principal.username = username; } return isAuthenticated; } diff --git a/stdlib/http/src/main/ballerina/http/auth/authz_filter.bal b/stdlib/http/src/main/ballerina/http/auth/authz_filter.bal index bbe03e1f8c96..cdc9b11d6a6a 100644 --- a/stdlib/http/src/main/ballerina/http/auth/authz_filter.bal +++ b/stdlib/http/src/main/ballerina/http/auth/authz_filter.bal @@ -51,7 +51,7 @@ public type AuthzFilter object { boolean authorized; if (scopes is string[]) { if (self.authzHandler.canHandle(request)) { - authorized = self.authzHandler.handle(runtime:getInvocationContext().userPrincipal.username, + authorized = self.authzHandler.handle(runtime:getInvocationContext().principal.username, context.serviceName, context.resourceName, request.method, scopes); } else { authorized = false; diff --git a/stdlib/http/src/main/ballerina/http/auth/authz_handler.bal b/stdlib/http/src/main/ballerina/http/auth/authz_handler.bal index 980e02e67d3f..f0be9129a888 100644 --- a/stdlib/http/src/main/ballerina/http/auth/authz_handler.bal +++ b/stdlib/http/src/main/ballerina/http/auth/authz_handler.bal @@ -70,11 +70,11 @@ function HttpAuthzHandler.handle (string username, string serviceName, string re string[] scopes) returns (boolean) { // first, check in the cache. cache key is ---, // since different resources can have different scopes - string authzCacheKey = runtime:getInvocationContext().userPrincipal.userId + + string authzCacheKey = runtime:getInvocationContext().principal.userId + "-" + serviceName + "-" + resourceName + "-" + method; - string[] authCtxtScopes = runtime:getInvocationContext().userPrincipal.scopes; - //TODO: Make sure userPrincipal.scopes array is sorted to prevent cache-misses that could happen due to ordering + string[] authCtxtScopes = runtime:getInvocationContext().principal.scopes; + //TODO: Make sure principal.scopes array is sorted to prevent cache-misses that could happen due to ordering if (authCtxtScopes.length() > 0) { authzCacheKey += "-"; foreach var authCtxtScope in authCtxtScopes { @@ -173,7 +173,7 @@ function matchScopes (string[] scopesOfResource, string[] scopesForRequest) retu } function HttpAuthzHandler.canHandle (Request req) returns (boolean) { - if (runtime:getInvocationContext().userPrincipal.username.length() == 0) { + if (runtime:getInvocationContext().principal.username.length() == 0) { log:printError("Username not set in auth context. Unable to authorize"); return false; } diff --git a/stdlib/http/src/main/ballerina/http/auth/basic_authn_handler.bal b/stdlib/http/src/main/ballerina/http/auth/basic_authn_handler.bal index cc556824fbf9..58f8d0f3838f 100644 --- a/stdlib/http/src/main/ballerina/http/auth/basic_authn_handler.bal +++ b/stdlib/http/src/main/ballerina/http/auth/basic_authn_handler.bal @@ -64,11 +64,11 @@ public function HttpBasicAuthnHandler.handle(Request req) returns (boolean) { boolean authenticated = self.authStoreProvider.authenticate(username, password); if (authenticated) { // set username - runtime:getInvocationContext().userPrincipal.username = username; + runtime:getInvocationContext().principal.username = username; // read scopes and set to the invocation context string[] scopes = self.authStoreProvider.getScopes(username); if (scopes.length() > 0) { - runtime:getInvocationContext().userPrincipal.scopes = scopes; + runtime:getInvocationContext().principal.scopes = scopes; } } return authenticated; diff --git a/stdlib/http/src/main/ballerina/http/http_secure_client.bal b/stdlib/http/src/main/ballerina/http/http_secure_client.bal index 06b9383f505f..a927e03ee8bf 100644 --- a/stdlib/http/src/main/ballerina/http/http_secure_client.bal +++ b/stdlib/http/src/main/ballerina/http/http_secure_client.bal @@ -322,7 +322,7 @@ function generateSecureRequest(Request req, ClientEndpointConfig config) returns req.setHeader(AUTH_HEADER, AUTH_SCHEME_BEARER + WHITE_SPACE + accessToken); } } else if (scheme == JWT_AUTH) { - string authToken = runtime:getInvocationContext().authContext.authToken; + string authToken = runtime:getInvocationContext().authenticationContext.authToken; if (authToken == EMPTY_STRING) { error err = error(HTTP_ERROR_CODE, { message: "Authentication token is not set at invocation context" }); return err; diff --git a/stdlib/runtime/src/main/ballerina/runtime/Module.md b/stdlib/runtime/src/main/ballerina/runtime/Module.md index ea8cd0d34b9c..e336bd33caba 100644 --- a/stdlib/runtime/src/main/ballerina/runtime/Module.md +++ b/stdlib/runtime/src/main/ballerina/runtime/Module.md @@ -6,7 +6,7 @@ This module includes functions to interact with the runtime, the invocation cont The Invocation Context is a data holder that is created per request and preserved for a single request-response flow. The Invocation Context comprises of a unique ID, a `UserPrincipal` instance that includes user details and an - `AuthContext` instance that has the authentication related details if available. + `AuthenticationContext` instance that has the authentication related details if available. ### Errors diff --git a/stdlib/runtime/src/main/ballerina/runtime/invocation-context.bal b/stdlib/runtime/src/main/ballerina/runtime/invocation-context.bal index 15c24a3002eb..880a036d192f 100644 --- a/stdlib/runtime/src/main/ballerina/runtime/invocation-context.bal +++ b/stdlib/runtime/src/main/ballerina/runtime/invocation-context.bal @@ -17,13 +17,13 @@ # Represents the InvocationContext. # # + id - Unique id generated when initiating the invocation context. -# + userPrincipal - User principal instance. -# + authContext - Authentication context instance. +# + principal - User principal instance. +# + authenticationContext - Authentication context instance. # + attributes - Context attributes. public type InvocationContext record { string id; - UserPrincipal userPrincipal; - AuthContext authContext; + Principal principal; + AuthenticationContext authenticationContext; map attributes; !...; }; @@ -32,19 +32,19 @@ public type InvocationContext record { # # + scheme - Authentication token type. e.g: JWT etc. # + authToken - Relevant token for the schema. -public type AuthContext record { +public type AuthenticationContext record { string scheme; string authToken; !...; }; -# Represents the UserPrincipal, populated with authenticated user information. +# Represents the Principal, populated with authenticated user information. # # + userId - User Id of the authenticated user. # + username - Username of the authenticated user. # + claims - Claims of the authenticated user. # + scopes - Authenticated user scopes. -public type UserPrincipal record { +public type Principal record { string userId; string username; map claims; diff --git a/stdlib/runtime/src/main/java/org/ballerinalang/stdlib/runtime/nativeimpl/AuthenticationContext.java b/stdlib/runtime/src/main/java/org/ballerinalang/stdlib/runtime/nativeimpl/AuthenticationContext.java index e7e68d22572c..35004c1aaa87 100644 --- a/stdlib/runtime/src/main/java/org/ballerinalang/stdlib/runtime/nativeimpl/AuthenticationContext.java +++ b/stdlib/runtime/src/main/java/org/ballerinalang/stdlib/runtime/nativeimpl/AuthenticationContext.java @@ -23,34 +23,34 @@ import org.ballerinalang.model.values.BValue; /** - * AuthContext represents and holds the authentication information. + * AuthenticationContext represents and holds the authentication information. * * @since 0.970.0 */ -public class AuthContext { +public class AuthenticationContext { public static final String AUTH_SCHEME_KEY = "scheme"; public static final String AUTH_TOKEN_KEY = "authToken"; - private BMap authContextStruct; + private BMap authenticationContextStruct; - public AuthContext(BMap authContextStruct) { - this.authContextStruct = authContextStruct; + public AuthenticationContext(BMap authContextStruct) { + this.authenticationContextStruct = authContextStruct; } public String getScheme() { - return authContextStruct.get(AUTH_SCHEME_KEY).stringValue(); + return authenticationContextStruct.get(AUTH_SCHEME_KEY).stringValue(); } public void setScheme(String authType) { - authContextStruct.put(AUTH_SCHEME_KEY, new BString(authType)); + authenticationContextStruct.put(AUTH_SCHEME_KEY, new BString(authType)); } public String getAuthToken() { - return authContextStruct.get(AUTH_TOKEN_KEY).stringValue(); + return authenticationContextStruct.get(AUTH_TOKEN_KEY).stringValue(); } public void setAuthToken(String authToken) { - authContextStruct.put(AUTH_TOKEN_KEY, new BString(authToken)); + authenticationContextStruct.put(AUTH_TOKEN_KEY, new BString(authToken)); } } diff --git a/stdlib/runtime/src/main/java/org/ballerinalang/stdlib/runtime/nativeimpl/InvocationContext.java b/stdlib/runtime/src/main/java/org/ballerinalang/stdlib/runtime/nativeimpl/InvocationContext.java index e6f8b1d874a1..1d72d793a7e9 100644 --- a/stdlib/runtime/src/main/java/org/ballerinalang/stdlib/runtime/nativeimpl/InvocationContext.java +++ b/stdlib/runtime/src/main/java/org/ballerinalang/stdlib/runtime/nativeimpl/InvocationContext.java @@ -32,12 +32,12 @@ public class InvocationContext { public static final String INVOCATION_ID_KEY = "id"; private BMap invocationContextStruct; private UserPrincipal userPrincipal; - private AuthContext authContext; + private AuthenticationContext authenticationContext; public InvocationContext(BMap invocationContextStruct, UserPrincipal userPrincipal, - AuthContext authenticationContext) { + AuthenticationContext authenticationContext) { this.invocationContextStruct = invocationContextStruct; - this.authContext = authenticationContext; + this.authenticationContext = authenticationContext; this.userPrincipal = userPrincipal; } @@ -53,8 +53,8 @@ public void setId(String id) { invocationContextStruct.put(INVOCATION_ID_KEY, new BString(id)); } - public AuthContext getAuthContext() { - return authContext; + public AuthenticationContext getAuthenticationContext() { + return authenticationContext; } public UserPrincipal getUserPrincipal() { diff --git a/stdlib/runtime/src/main/java/org/ballerinalang/stdlib/runtime/nativeimpl/InvocationContextUtils.java b/stdlib/runtime/src/main/java/org/ballerinalang/stdlib/runtime/nativeimpl/InvocationContextUtils.java index b668678fea81..fe202f9b9d9c 100644 --- a/stdlib/runtime/src/main/java/org/ballerinalang/stdlib/runtime/nativeimpl/InvocationContextUtils.java +++ b/stdlib/runtime/src/main/java/org/ballerinalang/stdlib/runtime/nativeimpl/InvocationContextUtils.java @@ -41,8 +41,8 @@ public class InvocationContextUtils { public static final String INVOCATION_CONTEXT_PROPERTY = "InvocationContext"; public static final String STRUCT_TYPE_INVOCATION_CONTEXT = "InvocationContext"; - public static final String STRUCT_TYPE_AUTH_CONTEXT = "AuthContext"; - public static final String STRUCT_TYPE_USER_PRINCIPAL = "UserPrincipal"; + public static final String STRUCT_TYPE_AUTHENTICATION_CONTEXT = "AuthenticationContext"; + public static final String STRUCT_TYPE_PRINCIPAL = "Principal"; public static InvocationContext getInvocationContext(Context context) { InvocationContext invocationContext = (InvocationContext) context.getProperty(InvocationContextUtils @@ -66,12 +66,12 @@ public static BMap getInvocationContextStruct(Context context) { private static InvocationContext initInvocationContext(Context context) { BMap userPrincipalStruct = createUserPrincipal(context); UserPrincipal userPrincipal = new UserPrincipal(userPrincipalStruct); - BMap authContextStruct = createAuthContext(context); - AuthContext authContext = new AuthContext(authContextStruct); + BMap authContextStruct = createAuthenticationContext(context); + AuthenticationContext authenticationContext = new AuthenticationContext(authContextStruct); BMap invocationContextStruct = createInvocationContext(context, userPrincipalStruct, authContextStruct); InvocationContext invocationContext = new InvocationContext( - invocationContextStruct, userPrincipal, authContext); + invocationContextStruct, userPrincipal, authenticationContext); return invocationContext; } @@ -92,15 +92,16 @@ private static BMap createInvocationContext(Context context, BMa authContext, new BMap()); } - private static BMap createAuthContext(Context context) { - StructureTypeInfo authContextInfo = getStructInfo(context, BALLERINA_RUNTIME_PKG, STRUCT_TYPE_AUTH_CONTEXT); + private static BMap createAuthenticationContext(Context context) { + StructureTypeInfo authContextInfo = getStructInfo(context, BALLERINA_RUNTIME_PKG, + STRUCT_TYPE_AUTHENTICATION_CONTEXT); String scheme = ""; String authToken = ""; return BLangVMStructs.createBStruct(authContextInfo, scheme, authToken); } private static BMap createUserPrincipal(Context context) { - StructureTypeInfo authContextInfo = getStructInfo(context, BALLERINA_RUNTIME_PKG, STRUCT_TYPE_USER_PRINCIPAL); + StructureTypeInfo authContextInfo = getStructInfo(context, BALLERINA_RUNTIME_PKG, STRUCT_TYPE_PRINCIPAL); String userId = ""; String username = ""; BMap claims = new BMap<>(); diff --git a/stdlib/runtime/src/test/resources/test-src/invocation-context.bal b/stdlib/runtime/src/test/resources/test-src/invocation-context.bal index 80d9db438f8f..4e49c7550743 100644 --- a/stdlib/runtime/src/test/resources/test-src/invocation-context.bal +++ b/stdlib/runtime/src/test/resources/test-src/invocation-context.bal @@ -6,22 +6,22 @@ function testInvocationId() returns (string) { function testUserId() returns (boolean) { string userId = "124876jk23i4"; - runtime:getInvocationContext().userPrincipal.userId = userId; - return userId == runtime:getInvocationContext().userPrincipal.userId; + runtime:getInvocationContext().principal.userId = userId; + return userId == runtime:getInvocationContext().principal.userId; } function testUsername() returns (boolean) { string username = "tom"; - runtime:getInvocationContext().userPrincipal.username = username; - return username == runtime:getInvocationContext().userPrincipal.username; + runtime:getInvocationContext().principal.username = username; + return username == runtime:getInvocationContext().principal.username; } function testUserClaims() returns (boolean) { map claims = { email: "tom@ballerina.com", org: "wso2" }; - runtime:getInvocationContext().userPrincipal.claims = claims; - if (runtime:getInvocationContext().userPrincipal.claims.hasKey("email")) { + runtime:getInvocationContext().principal.claims = claims; + if (runtime:getInvocationContext().principal.claims.hasKey("email")) { string emailInContext = ""; - var result = runtime:getInvocationContext().userPrincipal.claims["email"]; + var result = runtime:getInvocationContext().principal.claims["email"]; emailInContext = result; return "tom@ballerina.com" == emailInContext; @@ -31,20 +31,20 @@ function testUserClaims() returns (boolean) { function testAllowedScopes() returns (boolean) { string[] scopes = ["email", "profile"]; - runtime:getInvocationContext().userPrincipal.scopes = scopes; - return "email" == runtime:getInvocationContext().userPrincipal.scopes[0]; + runtime:getInvocationContext().principal.scopes = scopes; + return "email" == runtime:getInvocationContext().principal.scopes[0]; } function testAuthType() returns (boolean) { string authType = "JWT"; - runtime:getInvocationContext().authContext.scheme = authType; - return authType == runtime:getInvocationContext().authContext.scheme; + runtime:getInvocationContext().authenticationContext.scheme = authType; + return authType == runtime:getInvocationContext().authenticationContext.scheme; } function testAuthToken() returns (boolean) { string authToken = "abc.xyz.pqr"; - runtime:getInvocationContext().authContext.authToken = authToken; - return authToken == runtime:getInvocationContext().authContext.authToken; + runtime:getInvocationContext().authenticationContext.authToken = authToken; + return authToken == runtime:getInvocationContext().authenticationContext.authToken; } function testAttributes() returns boolean { diff --git a/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/AuthBaseTest.java b/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/AuthBaseTest.java index 9b6410a6ec30..249c2cef9a44 100644 --- a/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/AuthBaseTest.java +++ b/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/AuthBaseTest.java @@ -36,7 +36,7 @@ public class AuthBaseTest extends BaseTest { @BeforeGroups(value = "auth-test", alwaysRun = true) public void start() throws Exception { - int[] requiredPorts = new int[]{9090, 9091, 9092, 9093, 9094, 9095, 9096, 9097, 9098, 9099, 9100, 9101, + int[] requiredPorts = new int[]{9090, 9091, 9092, 9093, 9094, 9095, 9096, 9097, 9098, 9099, 9100, 9101, 9102, 9190, 9191, 9192, 9193, 9194}; embeddedDirectoryServer = new EmbeddedDirectoryServer(); embeddedDirectoryServer.startLdapServer(9389); diff --git a/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/ServiceLevelAuthnTest.java b/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/ServiceLevelAuthnTest.java index 7b3cfeabddff..91f2f2b7bc8c 100644 --- a/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/ServiceLevelAuthnTest.java +++ b/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/ServiceLevelAuthnTest.java @@ -37,39 +37,6 @@ public class ServiceLevelAuthnTest extends AuthBaseTest { private final int servicePort = 9094; private final int servicePortForExpiredCertificateTest = 9101; - @Test(description = "Authn and authz success test case") - public void testAuthSuccess() throws Exception { - Map headers = new HashMap<>(); - headers.put(HttpHeaderNames.CONTENT_TYPE.toString(), TestConstant.CONTENT_TYPE_TEXT_PLAIN); - headers.put("Authorization", "Basic aXN1cnU6eHh4"); - HttpResponse response = HttpsClientRequest.doGet(serverInstance.getServiceURLHttps(servicePort, "echo/test"), - headers, serverInstance.getServerHome()); - Assert.assertNotNull(response); - Assert.assertEquals(response.getResponseCode(), 200, "Response code mismatched"); - } - - @Test(description = "Authn success and authz failure test case") - public void testAuthzFailure() throws Exception { - Map headers = new HashMap<>(); - headers.put(HttpHeaderNames.CONTENT_TYPE.toString(), TestConstant.CONTENT_TYPE_TEXT_PLAIN); - headers.put("Authorization", "Basic aXNoYXJhOmFiYw=="); - HttpResponse response = HttpsClientRequest.doGet(serverInstance.getServiceURLHttps(servicePort, "echo/test"), - headers, serverInstance.getServerHome()); - Assert.assertNotNull(response); - Assert.assertEquals(response.getResponseCode(), 403, "Response code mismatched"); - } - - @Test(description = "Authn and authz failure test case") - public void testAuthFailure() throws Exception { - Map headers = new HashMap<>(); - headers.put(HttpHeaderNames.CONTENT_TYPE.toString(), TestConstant.CONTENT_TYPE_TEXT_PLAIN); - headers.put("Authorization", "Basic dGVzdDp0ZXN0MTIz"); - HttpResponse response = HttpsClientRequest.doGet(serverInstance.getServiceURLHttps(servicePort, "echo/test"), - headers, serverInstance.getServerHome()); - Assert.assertNotNull(response); - Assert.assertEquals(response.getResponseCode(), 401, "Response code mismatched"); - } - @Test(description = "Auth with JWT signed with expired trusted certificate") public void testAuthnWithJWTSignedWithExpiredTrustedCertificate() throws Exception { // JWT used in the test: @@ -101,25 +68,34 @@ public void testAuthnWithJWTSignedWithExpiredTrustedCertificate() throws Excepti Assert.assertEquals(response.getResponseCode(), 401, "Response code mismatched"); } - @Test(description = "Authn and authz success test case for a request with path parameters") - public void testAuthSuccessWithPathParameter() throws Exception { - Map headers = new HashMap<>(); - headers.put(HttpHeaderNames.CONTENT_TYPE.toString(), TestConstant.CONTENT_TYPE_TEXT_PLAIN); - headers.put("Authorization", "Basic aXN1cnU6eHh4"); - HttpResponse response = HttpsClientRequest.doGet(serverInstance.getServiceURLHttps(servicePort, "echo/path/1"), - headers, serverInstance.getServerHome()); + @Test(description = "Auth with JWT signed with expired trusted certificate but with expiry validation off") + public void testAuthnWithJWTSignedWithExpiredTrustedCertificateWithNoExpiryValidation() throws Exception { + // JWT used in the test: + // { + // "sub": "ballerina", + // "iss": "ballerina", + // "exp": 2818415019, + // "iat": 1524575019, + // "jti": "f5aded50585c46f2b8ca233d0c2a3c9d", + // "aud": [ + // "ballerina", + // "ballerina.org", + // "ballerina.io" + // ], + // "scope": "hello" + //} + Map headersMap = new HashMap<>(); + headersMap.put("Authorization", "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiYWxsZXJpbmEiLCJpc" + + "3MiOiJiYWxsZXJpbmEiLCJleHAiOjI4MTg0MTUwMTksImlhdCI6MTUyNDU3NTAxOSwianRpIjoiZjVhZGVkNTA1ODVjNDZmMm" + + "I4Y2EyMzNkMGMyYTNjOWQiLCJhdWQiOlsiYmFsbGVyaW5hIiwiYmFsbGVyaW5hLm9yZyIsImJhbGxlcmluYS5pbyJdLCJzY29" + + "wZSI6ImhlbGxvIn0.itXiQOLVA_PpVEDz3bCpA8cowZ_4nsUf_syv9cw2byAGjxE7w2JPb5RBi4hhIPqeQX0BAl56PedRvIwb" + + "B9DkUDdEF9DIc3uYDTxOgys8fAyK-6hLsgjln65slb627bTTWwIcUszKeZLTIw1z4XKDShe9gQJGLiOCWOQ1YxmrnDM6HgOQb" + + "18xqUzweCRL-DLAAYwjbzGQ56ekbEdAg02sFco4aozOyt8OUDwS9cH_JlhUn2JEHmVKaatljEnfgRc8fOW6Y5IJ7dOPp7ra5e" + + "00sk7JwYY8wKaZWxAGSgRpWgTY6C4XRjGIsR5ZWQdXCAnV27idGDrtR2uG4YQwCWUCzA"); + HttpResponse response = HttpsClientRequest.doGet(serverInstance + .getServiceURLHttps(servicePortForExpiredCertificateTest, "echo14/test14"), + headersMap, serverInstance.getServerHome()); Assert.assertNotNull(response); Assert.assertEquals(response.getResponseCode(), 200, "Response code mismatched"); } - - @Test(description = "Authn and authz failure test case for a request with path parameters") - public void testAuthFailureWithPathParameter() throws Exception { - Map headers = new HashMap<>(); - headers.put(HttpHeaderNames.CONTENT_TYPE.toString(), TestConstant.CONTENT_TYPE_TEXT_PLAIN); - headers.put("Authorization", "Basic dGVzdDp0ZXN0MTIz"); - HttpResponse response = HttpsClientRequest.doGet(serverInstance.getServiceURLHttps(servicePort, "echo/path/1"), - headers, serverInstance.getServerHome()); - Assert.assertNotNull(response); - Assert.assertEquals(response.getResponseCode(), 401, "Response code mismatched"); - } } diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/14_authn_with_expired_certificate_with_no_expiry_validation.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/14_authn_with_expired_certificate_with_no_expiry_validation.bal new file mode 100644 index 000000000000..a61aa9b10ed4 --- /dev/null +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/14_authn_with_expired_certificate_with_no_expiry_validation.bal @@ -0,0 +1,46 @@ +// Copyright (c) 2019 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. + +import ballerina/http; + +http:AuthProvider jwtAuthProvider14 = { + scheme:"jwt", + issuer:"ballerina", + audience: "ballerina.io", + certificateAlias: "cert", + validateCertificate: false, + trustStore: { + path: "../../../src/test/resources/auth/testtruststore.p12", + password: "ballerina" + } +}; + +listener http:Listener listener14 = new(9102, config = { + authProviders:[jwtAuthProvider14], + secureSocket: { + keyStore: { + path: "${ballerina.home}/bre/security/ballerinaKeystore.p12", + password: "ballerina" + } + } +}); + +service echo14 on listener14 { + + resource function test14 (http:Caller caller, http:Request req) { + _ = caller -> respond(()); + } +} From a416ecb2e0484aaf3fdfffbc42d655816e2c7671 Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Fri, 8 Feb 2019 17:12:43 +0530 Subject: [PATCH 10/26] Add integration test for cert validation --- .../src/main/ballerina/auth/jwt_validator.bal | 2 +- .../crypto/nativeimpl/DecodePublicKey.java | 5 +- .../main/ballerina/http/service_endpoint.bal | 5 + .../stdlib/auth/JWTAuthnHandlerTest.java | 2 +- .../dependency-reduced-pom.xml | 117 ++++++++++++++++++ .../test/auth/ServiceLevelAuthnTest.java | 5 +- 6 files changed, 129 insertions(+), 7 deletions(-) create mode 100644 tests/ballerina-integration-test-utils/dependency-reduced-pom.xml diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal b/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal index ec656c7303c9..064061ce7d8e 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal @@ -235,7 +235,7 @@ function validateJwtRecords(string[] encodedJWTComponents, JwtHeader jwtHeader, return jwtError; } if (config["validateCertificate"] is ()) { - config["validateCertificate"] = true; + config.validateCertificate = true; } if (config.validateCertificate == true && !check validateCertificate(config)) { error jwtError = error(AUTH_ERROR_CODE, { message : "Public key certificate validity period has passed" }); diff --git a/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/DecodePublicKey.java b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/DecodePublicKey.java index 79a077fc5077..489991dcf239 100644 --- a/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/DecodePublicKey.java +++ b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/nativeimpl/DecodePublicKey.java @@ -34,7 +34,6 @@ import org.ballerinalang.stdlib.crypto.CryptoUtils; import org.ballerinalang.stdlib.time.util.TimeUtils; import org.ballerinalang.util.exceptions.BallerinaException; -import org.wso2.carbon.transport.http.netty.common.Util; import java.io.File; import java.io.FileInputStream; @@ -77,8 +76,8 @@ public void execute(Context context) { PublicKey publicKey = null; // TODO: Add support for reading key from a provided string or directly using PEM encoded file. if (keyStore != null) { - File keyStoreFile = new File(Util.substituteVariables(keyStore.get(Constants.KEY_STORE_RECORD_PATH_FIELD) - .stringValue())); + File keyStoreFile = new File(CryptoUtils + .substituteVariables(keyStore.get(Constants.KEY_STORE_RECORD_PATH_FIELD).stringValue())); try (FileInputStream fileInputStream = new FileInputStream(keyStoreFile)) { KeyStore keystore = KeyStore.getInstance(Constants.KEYSTORE_TYPE_PKCS12); try { diff --git a/stdlib/http/src/main/ballerina/http/service_endpoint.bal b/stdlib/http/src/main/ballerina/http/service_endpoint.bal index 2830cb353955..a0dc3fa4d3ac 100644 --- a/stdlib/http/src/main/ballerina/http/service_endpoint.bal +++ b/stdlib/http/src/main/ballerina/http/service_endpoint.bal @@ -234,6 +234,7 @@ public type AuthProvider record { string audience = ""; TrustStore? trustStore = (); string certificateAlias = ""; + boolean validateCertificate?; int clockSkew = 0; KeyStore? keyStore = (); string keyAlias = ""; @@ -395,6 +396,10 @@ function createAuthHandler(AuthProvider authProvider, string instanceId) returns clockSkew: authProvider.clockSkew, trustStore: trustStore }; + var validateCertificate = authProvider["validateCertificate"]; + if (validateCertificate is boolean) { + jwtConfig.validateCertificate = validateCertificate; + } auth:JWTAuthProvider jwtAuthProvider = new(jwtConfig); HttpJwtAuthnHandler jwtAuthnHandler = new(jwtAuthProvider); return jwtAuthnHandler; diff --git a/stdlib/http/src/test/java/org/ballerinalang/stdlib/auth/JWTAuthnHandlerTest.java b/stdlib/http/src/test/java/org/ballerinalang/stdlib/auth/JWTAuthnHandlerTest.java index b5250fbffd6c..e389d3be5a78 100644 --- a/stdlib/http/src/test/java/org/ballerinalang/stdlib/auth/JWTAuthnHandlerTest.java +++ b/stdlib/http/src/test/java/org/ballerinalang/stdlib/auth/JWTAuthnHandlerTest.java @@ -116,7 +116,7 @@ public void setup() throws Exception { jwtHeader.put("alg", new BString("RS256")); jwtHeader.put("typ", new BString("JWT")); - long time = 32475251189000l; + long time = 32475251189000L; BMap jwtBody = new BMap<>(); jwtBody.put("sub", new BString("John")); jwtBody.put("iss", new BString("wso2")); diff --git a/tests/ballerina-integration-test-utils/dependency-reduced-pom.xml b/tests/ballerina-integration-test-utils/dependency-reduced-pom.xml new file mode 100644 index 000000000000..c85c4ff66255 --- /dev/null +++ b/tests/ballerina-integration-test-utils/dependency-reduced-pom.xml @@ -0,0 +1,117 @@ + + + + ballerina-parent + org.ballerinalang + 0.990.3-SNAPSHOT + ../../pom.xml + + 4.0.0 + ballerina-integration-test-utils + Ballerina - Integration Test Utils + http://ballerina-lang.org + + + + kr.motd.maven + os-maven-plugin + ${os.maven.plugin.version} + + + + + maven-jar-plugin + + + + org.ballerinalang.test.agent.BallerinaServerAgent + + + + + + maven-shade-plugin + 3.1.1 + + + package + + shade + + + + + org.apache.mina:mina-core + org.slf4j:slf4j-api + org.apache.commons:commons-lang3 + org.wso2.transport.http:org.wso2.transport.http.netty + org.wso2.eclipse.osgi:org.eclipse.osgi + org.wso2.eclipse.osgi:org.eclipse.osgi.services + io.netty:netty-common + io.netty:netty-buffer + io.netty:netty-transport + io.netty:netty-handler + io.netty:netty-codec + io.netty:netty-handler-proxy + io.netty:netty-codec-socks + io.netty:netty-codec-http2 + io.netty:netty-resolver + commons-pool.wso2:commons-pool + commons-pool:commons-pool + org.wso2.orbit.org.yaml:snakeyaml + org.bouncycastle:bcprov-jdk15on + org.bouncycastle:bcpkix-jdk15on + io.netty:netty-tcnative-boringssl-static + org.testng:testng + com.beust:jcommander + io.netty:netty-codec-http + io.netty:netty-transport-native-unix-common + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + + + + + org.apache.mina + mina-core + 2.0.16 + compile + + + org.apache.commons + commons-lang3 + 3.5 + compile + + + org.wso2.transport.http + org.wso2.transport.http.netty + 6.0.259 + compile + + + org.testng + testng + 6.13.1 + compile + + + io.netty + netty-codec-http + 4.1.19.Final + compile + + + + spotbugs-exclude.xml + **/generated/**, + **/generated-sources/** + + diff --git a/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/ServiceLevelAuthnTest.java b/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/ServiceLevelAuthnTest.java index 91f2f2b7bc8c..c4a1f94b6845 100644 --- a/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/ServiceLevelAuthnTest.java +++ b/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/ServiceLevelAuthnTest.java @@ -36,6 +36,7 @@ public class ServiceLevelAuthnTest extends AuthBaseTest { private final int servicePort = 9094; private final int servicePortForExpiredCertificateTest = 9101; + private final int servicePortForExpiredCertificateTestWithNoExpiryValidation = 9102; @Test(description = "Auth with JWT signed with expired trusted certificate") public void testAuthnWithJWTSignedWithExpiredTrustedCertificate() throws Exception { @@ -93,8 +94,8 @@ public void testAuthnWithJWTSignedWithExpiredTrustedCertificateWithNoExpiryValid "18xqUzweCRL-DLAAYwjbzGQ56ekbEdAg02sFco4aozOyt8OUDwS9cH_JlhUn2JEHmVKaatljEnfgRc8fOW6Y5IJ7dOPp7ra5e" + "00sk7JwYY8wKaZWxAGSgRpWgTY6C4XRjGIsR5ZWQdXCAnV27idGDrtR2uG4YQwCWUCzA"); HttpResponse response = HttpsClientRequest.doGet(serverInstance - .getServiceURLHttps(servicePortForExpiredCertificateTest, "echo14/test14"), - headersMap, serverInstance.getServerHome()); + .getServiceURLHttps(servicePortForExpiredCertificateTestWithNoExpiryValidation, + "echo14/test14"), headersMap, serverInstance.getServerHome()); Assert.assertNotNull(response); Assert.assertEquals(response.getResponseCode(), 200, "Response code mismatched"); } From ef8f24614b065e5b35e0faf877f3637dafaa2b84 Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Mon, 11 Feb 2019 09:25:55 +0530 Subject: [PATCH 11/26] Restructure auth configs --- .../main/ballerina/auth/auth_constants.bal | 13 +++++++++ .../main/ballerina/http/client_endpoint.bal | 28 +++++++++++++++---- .../ballerina/http/http_secure_client.bal | 6 ---- .../main/ballerina/http/service_endpoint.bal | 6 ++-- 4 files changed, 38 insertions(+), 15 deletions(-) diff --git a/stdlib/auth/src/main/ballerina/auth/auth_constants.bal b/stdlib/auth/src/main/ballerina/auth/auth_constants.bal index 28f533a1abe2..5fb6d9811df7 100644 --- a/stdlib/auth/src/main/ballerina/auth/auth_constants.bal +++ b/stdlib/auth/src/main/ballerina/auth/auth_constants.bal @@ -16,3 +16,16 @@ # Constant for the auth error code public final string AUTH_ERROR_CODE = "{ballerina/auth}AuthError"; + +public type InboundAuthScheme BASIC_AUTH|JWT; + +public type OutboundAuthScheme BASIC_AUTH|OAUTH2|JWT; + +public const BASIC_AUTH = "Basic"; +public const OAUTH2 = "OAuth2"; +public const JWT = "JWT"; + +public type AuthStoreProvider CONFIG|LDAP; + +public const CONFIG = "Config"; +public const LDAP = "LDAP"; \ No newline at end of file diff --git a/stdlib/http/src/main/ballerina/http/client_endpoint.bal b/stdlib/http/src/main/ballerina/http/client_endpoint.bal index 9fe192c5f530..a69e3e012ada 100644 --- a/stdlib/http/src/main/ballerina/http/client_endpoint.bal +++ b/stdlib/http/src/main/ballerina/http/client_endpoint.bal @@ -334,9 +334,28 @@ public type ConnectionThrottling record { # AuthConfig record can be used to configure the authentication mechanism used by the HTTP endpoint. # -# + scheme - Scheme of the configuration (Basic, OAuth2, JWT etc.) +# + scheme - Authentication scheme +# + basicAuthConfig - Configuration for BasicAuth scheme +# + oAuth2Config - Configuration for OAuth2 scheme +public type AuthConfig record { + OutboundAuthScheme scheme; + BasicAuthConfig basicAuthConfig?; + OAuth2AuthConfig oAuth2Config?; + !...; +}; + +# BasicAuthConfig record can be used to configure Basic Authentication used by the HTTP endpoint. +# # + username - Username for Basic authentication # + password - Password for Basic authentication +public type BasicAuthConfig record { + string username = ""; + string password = ""; + !...; +}; + +# OAuth2AuthConfig record can be used to configure OAuth2 based authentication used by the HTTP endpoint. +# # + accessToken - Access token for OAuth2 authentication # + refreshToken - Refresh token for OAuth2 authentication # + refreshUrl - Refresh token URL for OAuth2 authentication @@ -347,10 +366,7 @@ public type ConnectionThrottling record { # + clientSecret - Client secret for OAuth2 authentication # + credentialBearer - How client authentication is sent to refresh access token (AuthHeaderBearer, PostBodyBearer) # + scopes - Scope of the access request -public type AuthConfig record { - AuthScheme scheme; - string username = ""; - string password = ""; +public type OAuth2AuthConfig record { string accessToken = ""; string refreshToken = ""; string refreshUrl = ""; @@ -359,8 +375,8 @@ public type AuthConfig record { string tokenUrl = ""; string clientId = ""; string clientSecret = ""; - CredentialBearer credentialBearer = AUTH_HEADER_BEARER; string[] scopes = []; + CredentialBearer credentialBearer = AUTH_HEADER_BEARER; !...; }; diff --git a/stdlib/http/src/main/ballerina/http/http_secure_client.bal b/stdlib/http/src/main/ballerina/http/http_secure_client.bal index a927e03ee8bf..810943593dd2 100644 --- a/stdlib/http/src/main/ballerina/http/http_secure_client.bal +++ b/stdlib/http/src/main/ballerina/http/http_secure_client.bal @@ -22,12 +22,6 @@ import ballerina/encoding; const string EMPTY_STRING = ""; const string WHITE_SPACE = " "; -public type AuthScheme BASIC_AUTH|OAUTH2|JWT_AUTH; - -public const BASIC_AUTH = "Basic"; -public const OAUTH2 = "OAuth2"; -public const JWT_AUTH = "JWT"; - # Specifies how the authentication credentials should be sent when using the refresh token to refresh the access token public type CredentialBearer AUTH_HEADER_BEARER|POST_BODY_BEARER; diff --git a/stdlib/http/src/main/ballerina/http/service_endpoint.bal b/stdlib/http/src/main/ballerina/http/service_endpoint.bal index a0dc3fa4d3ac..df71f8cd52a1 100644 --- a/stdlib/http/src/main/ballerina/http/service_endpoint.bal +++ b/stdlib/http/src/main/ballerina/http/service_endpoint.bal @@ -212,7 +212,7 @@ public type AuthCacheConfig record { # # + scheme - Authentication scheme # + id - Authentication provider instance id -# + authStoreProvider - Authentication store provider (file, LDAP, etc.) implementation +# + authStoreProvider - Authentication store provider (Config, LDAP, etc.) implementation # + authStoreProviderConfig - Auth store related configurations # + issuer - Identifier of the token issuer # + audience - Identifier of the token recipients @@ -226,9 +226,9 @@ public type AuthCacheConfig record { # + signingAlg - The signing algorithm which is used to sign the JWT token # + propagateJwt - `true` if propagating authentication info as JWT public type AuthProvider record { - string scheme = ""; + auth:InboundAuthScheme scheme; string id = ""; - string authStoreProvider = ""; + auth:AuthStoreProvider authStoreProvider = (); auth:LdapAuthProviderConfig? authStoreProviderConfig = (); string issuer = ""; string audience = ""; From 25ceabf87f005e8a14b4e2c22d5ca8e2a0c6ec50 Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Mon, 11 Feb 2019 13:50:01 +0530 Subject: [PATCH 12/26] Correct integration tests --- .../main/ballerina/auth/auth_constants.bal | 13 -- .../auth/config_auth_store_provider.bal | 8 + .../src/main/ballerina/auth/jwt_issuer.bal | 2 +- .../auth/jwt_supported_auth_provider_util.bal | 18 +-- .../jwt_supported_config_auth_provider.bal | 8 +- .../auth/jwt_supported_ldap_auth_provider.bal | 12 +- .../src/main/ballerina/auth/jwt_validator.bal | 6 +- .../auth/ldap_auth_store_provider.bal | 4 +- .../http/auth/authn_handler_chain.bal | 2 +- .../ballerina/http/auth/authz_handler.bal | 4 +- .../http/auth/basic_authn_handler.bal | 2 +- .../ballerina/http/auth/jwt_authn_handler.bal | 3 +- .../src/main/ballerina/http/auth/utils.bal | 22 +-- .../ballerina/http/http_secure_client.bal | 135 +++++++++------- .../main/ballerina/http/service_endpoint.bal | 149 ++++++------------ .../test/auth/ServiceLevelAuthnTest.java | 2 - .../auth/authclients/oauth-client.bal | 60 ++++--- ...config_inheritance_auth_disabling_test.bal | 4 +- .../02_authn_config_inheritance_test.bal | 4 +- .../03_authz_config_inheritance_test.bal | 4 +- .../04_resource_level_auth_config_test.bal | 4 +- .../05_service_level_auth_config_test.bal | 4 +- .../07_auth_test_without_annotations.bal | 4 +- .../08_authn_with_multiple_providers.bal | 32 ++-- .../09_authn_with_different_scopes.bal | 16 +- ...cure_service_no_token_propagation_test.bal | 20 +-- ..._secure_service_token_propagation_test.bal | 39 +++-- ..._secure_service_wrong_provider_id_test.bal | 4 +- .../13_authn_with_expired_certificate.bal | 16 +- ..._certificate_with_no_expiry_validation.bal | 18 ++- .../ldap_auth_store_authorization_test.bal | 6 +- .../authservices/ldap_auth_store_test.bal | 6 +- 32 files changed, 313 insertions(+), 318 deletions(-) diff --git a/stdlib/auth/src/main/ballerina/auth/auth_constants.bal b/stdlib/auth/src/main/ballerina/auth/auth_constants.bal index 5fb6d9811df7..28f533a1abe2 100644 --- a/stdlib/auth/src/main/ballerina/auth/auth_constants.bal +++ b/stdlib/auth/src/main/ballerina/auth/auth_constants.bal @@ -16,16 +16,3 @@ # Constant for the auth error code public final string AUTH_ERROR_CODE = "{ballerina/auth}AuthError"; - -public type InboundAuthScheme BASIC_AUTH|JWT; - -public type OutboundAuthScheme BASIC_AUTH|OAUTH2|JWT; - -public const BASIC_AUTH = "Basic"; -public const OAUTH2 = "OAuth2"; -public const JWT = "JWT"; - -public type AuthStoreProvider CONFIG|LDAP; - -public const CONFIG = "Config"; -public const LDAP = "LDAP"; \ No newline at end of file diff --git a/stdlib/auth/src/main/ballerina/auth/config_auth_store_provider.bal b/stdlib/auth/src/main/ballerina/auth/config_auth_store_provider.bal index d4564cc67c87..4a24708797ce 100644 --- a/stdlib/auth/src/main/ballerina/auth/config_auth_store_provider.bal +++ b/stdlib/auth/src/main/ballerina/auth/config_auth_store_provider.bal @@ -19,6 +19,14 @@ import ballerina/runtime; const string CONFIG_USER_SECTION = "b7a.users"; +# Represents configurations that required for Config auth store. +# +# + inferredJwtIssuerConfig - Inferred JWT issuer configuration used to generate JWT for client invocations +public type ConfigAuthProviderConfig record { + InferredJwtIssuerConfig? inferredJwtIssuerConfig = (); + !...; +}; + # Represents Ballerina configuration file based auth store provider public type ConfigAuthStoreProvider object { diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal b/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal index bfa71a9e2a53..5cacbd32fcd5 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal @@ -14,8 +14,8 @@ // specific language governing permissions and limitations // under the License. -import ballerina/encoding; import ballerina/crypto; +import ballerina/encoding; # Issue a JWT token. # diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal b/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal index 3aace5b73cf7..a57f8edf572e 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal @@ -14,11 +14,11 @@ // specific language governing permissions and limitations // under the License. +import ballerina/crypto; import ballerina/internal; import ballerina/runtime; import ballerina/system; import ballerina/time; -import ballerina/crypto; # Represents authentication provider configurations that supports generating JWT for client interactions. # @@ -29,14 +29,14 @@ import ballerina/crypto; # + keyAlias - Key alias for signing newly issued JWT tokens # + keyPassword - Key password for signing newly issued JWT tokens # + signingAlg - Signing algorithm for signing newly issued JWT tokens - public type InferredJwtAuthProviderConfig record { - string issuer; - string audience; - int expTime = 0; +public type InferredJwtIssuerConfig record { + string issuer = "ballerina"; + string audience = "ballerina"; + int expTime = 300; crypto:KeyStore keyStore; string keyAlias; string keyPassword; - JwtSigningAlgorithm signingAlg; + JwtSigningAlgorithm signingAlg = RS256; !...; }; @@ -44,7 +44,7 @@ import ballerina/crypto; # # + username - user name # + authConfig - authentication provider configurations that supports generating JWT for client interactions -function setAuthToken(string username, InferredJwtAuthProviderConfig authConfig) { +function setAuthToken(string username, InferredJwtIssuerConfig authConfig) { JwtHeader header = createHeader(authConfig); JwtPayload payload = createPayload(username, authConfig); var token = issueJwt(header, payload, authConfig.keyStore, authConfig.keyAlias, authConfig.keyPassword); @@ -55,12 +55,12 @@ function setAuthToken(string username, InferredJwtAuthProviderConfig authConfig) } } -function createHeader(InferredJwtAuthProviderConfig authConfig) returns (JwtHeader) { +function createHeader(InferredJwtIssuerConfig authConfig) returns (JwtHeader) { JwtHeader header = { alg: authConfig.signingAlg, typ: "JWT" }; return header; } -function createPayload(string username, InferredJwtAuthProviderConfig authConfig) returns (JwtPayload) { +function createPayload(string username, InferredJwtIssuerConfig authConfig) returns (JwtPayload) { string audList = authConfig.audience; string[] audience = audList.split(" "); JwtPayload payload = { diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_supported_config_auth_provider.bal b/stdlib/auth/src/main/ballerina/auth/jwt_supported_config_auth_provider.bal index b8ceedc913ea..2d91b82aed8c 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_supported_config_auth_provider.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_supported_config_auth_provider.bal @@ -16,17 +16,17 @@ public type ConfigJwtAuthProvider object { - public InferredJwtAuthProviderConfig configJwtAuthProviderConfig; + public InferredJwtIssuerConfig inferredJwtIssuerConfig; public ConfigAuthStoreProvider configAuthProvider = new; - public function __init(InferredJwtAuthProviderConfig configJwtAuthProviderConfig) { - self.configJwtAuthProviderConfig = configJwtAuthProviderConfig; + public function __init(InferredJwtIssuerConfig inferredJwtIssuerConfig) { + self.inferredJwtIssuerConfig = inferredJwtIssuerConfig; } public function authenticate(string username, string password) returns boolean { boolean isAuthenticated = self.configAuthProvider.authenticate(username, password); if (isAuthenticated){ - setAuthToken(username, self.configJwtAuthProviderConfig); + setAuthToken(username, self.inferredJwtIssuerConfig); } return isAuthenticated; } diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_supported_ldap_auth_provider.bal b/stdlib/auth/src/main/ballerina/auth/jwt_supported_ldap_auth_provider.bal index 2c847b9cb474..1af50438310b 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_supported_ldap_auth_provider.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_supported_ldap_auth_provider.bal @@ -16,20 +16,20 @@ # Represents LDAP authentication provider that supports generating JWT for client interactions # -# + ldapJwtAuthProviderConfig - JWT configurations +# + inferredJwtIssuerConfig - JWT configurations # + ldapAuthProvider - LDAP auth store provider public type LdapJwtAuthProvider object { - public InferredJwtAuthProviderConfig ldapJwtAuthProviderConfig; + public InferredJwtIssuerConfig inferredJwtIssuerConfig; public LdapAuthStoreProvider ldapAuthProvider; # Provides authentication based on the configured LDAP user store # - # + ldapJwtAuthProviderConfig - Configuration for JWT token propagation + # + inferredJwtIssuerConfig - Configuration for JWT token propagation # + ldapAuthProvider - LDAP auth store provider - public function __init(InferredJwtAuthProviderConfig ldapJwtAuthProviderConfig, + public function __init(InferredJwtIssuerConfig inferredJwtIssuerConfig, LdapAuthStoreProvider ldapAuthProvider) { - self.ldapJwtAuthProviderConfig = ldapJwtAuthProviderConfig; + self.inferredJwtIssuerConfig = inferredJwtIssuerConfig; self.ldapAuthProvider = ldapAuthProvider; } @@ -41,7 +41,7 @@ public type LdapJwtAuthProvider object { public function authenticate(string username, string password) returns boolean { boolean isAuthenticated = self.ldapAuthProvider.authenticate(username, password); if (isAuthenticated){ - setAuthToken(username, self.ldapJwtAuthProviderConfig); + setAuthToken(username, self.inferredJwtIssuerConfig); } return isAuthenticated; } diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal b/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal index 064061ce7d8e..d3783eae842e 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal @@ -14,12 +14,12 @@ // specific language governing permissions and limitations // under the License. -import ballerina/log; -import ballerina/time; -import ballerina/encoding; import ballerina/crypto; +import ballerina/encoding; import ballerina/internal; import ballerina/io; +import ballerina/log; +import ballerina/time; # Represents JWT validator configurations. # + issuer - Expected issuer diff --git a/stdlib/auth/src/main/ballerina/auth/ldap_auth_store_provider.bal b/stdlib/auth/src/main/ballerina/auth/ldap_auth_store_provider.bal index 814a8ecabb1b..4caa2c093227 100644 --- a/stdlib/auth/src/main/ballerina/auth/ldap_auth_store_provider.bal +++ b/stdlib/auth/src/main/ballerina/auth/ldap_auth_store_provider.bal @@ -14,8 +14,8 @@ // specific language governing permissions and limitations // under the License. -import ballerina/runtime; import ballerina/crypto; +import ballerina/runtime; # Represents configurations that required for LDAP auth store. # @@ -41,6 +41,7 @@ import ballerina/crypto; # + retryAttempts - Retry the authentication request if a timeout happened # + secureClientSocket - The SSL configurations for the ldap client socket. This needs to be configured in order to # communicate through ldaps. +# + inferredJwtIssuerConfig - Inferred JWT issuer configuration used to generate JWT for client invocations public type LdapAuthProviderConfig record { string domainName; string connectionURL; @@ -63,6 +64,7 @@ public type LdapAuthProviderConfig record { int readTimeout = 60000; int retryAttempts = 0; SecureClientSocket? secureClientSocket = (); + InferredJwtIssuerConfig? inferredJwtIssuerConfig = (); !...; }; diff --git a/stdlib/http/src/main/ballerina/http/auth/authn_handler_chain.bal b/stdlib/http/src/main/ballerina/http/auth/authn_handler_chain.bal index bdad36717524..52ac2ba8664a 100644 --- a/stdlib/http/src/main/ballerina/http/auth/authn_handler_chain.bal +++ b/stdlib/http/src/main/ballerina/http/auth/authn_handler_chain.bal @@ -15,8 +15,8 @@ // under the License. -import ballerina/log; import ballerina/auth; +import ballerina/log; # Representation of Authentication handler chain # diff --git a/stdlib/http/src/main/ballerina/http/auth/authz_handler.bal b/stdlib/http/src/main/ballerina/http/auth/authz_handler.bal index f0be9129a888..02e0d9d57a50 100644 --- a/stdlib/http/src/main/ballerina/http/auth/authz_handler.bal +++ b/stdlib/http/src/main/ballerina/http/auth/authz_handler.bal @@ -16,9 +16,9 @@ import ballerina/cache; -import ballerina/runtime; -import ballerina/log; import ballerina/io; +import ballerina/log; +import ballerina/runtime; # Representation of Authorization Handler for HTTP # diff --git a/stdlib/http/src/main/ballerina/http/auth/basic_authn_handler.bal b/stdlib/http/src/main/ballerina/http/auth/basic_authn_handler.bal index 58f8d0f3838f..292efa8b52d2 100644 --- a/stdlib/http/src/main/ballerina/http/auth/basic_authn_handler.bal +++ b/stdlib/http/src/main/ballerina/http/auth/basic_authn_handler.bal @@ -16,9 +16,9 @@ import ballerina/auth; +import ballerina/encoding; import ballerina/log; import ballerina/runtime; -import ballerina/encoding; # Authentication cache name. const string AUTH_CACHE = "basic_auth_cache"; diff --git a/stdlib/http/src/main/ballerina/http/auth/jwt_authn_handler.bal b/stdlib/http/src/main/ballerina/http/auth/jwt_authn_handler.bal index 71279602bffe..c3160bf5ed79 100644 --- a/stdlib/http/src/main/ballerina/http/auth/jwt_authn_handler.bal +++ b/stdlib/http/src/main/ballerina/http/auth/jwt_authn_handler.bal @@ -14,9 +14,8 @@ // specific language governing permissions and limitations // under the License. - -import ballerina/log; import ballerina/auth; +import ballerina/log; # Representation of JWT Auth handler for HTTP traffic # diff --git a/stdlib/http/src/main/ballerina/http/auth/utils.bal b/stdlib/http/src/main/ballerina/http/auth/utils.bal index 39a81403f603..fd1f1b57d0b0 100644 --- a/stdlib/http/src/main/ballerina/http/auth/utils.bal +++ b/stdlib/http/src/main/ballerina/http/auth/utils.bal @@ -26,17 +26,19 @@ public const string AUTH_HEADER = "Authorization"; public const string AUTH_SCHEME_BASIC = "Basic"; # Bearer authentication scheme. public const string AUTH_SCHEME_BEARER = "Bearer"; -# Auth provider config name. -public const string AUTH_PROVIDER_CONFIG = "config"; -# LDAP auth provider config name. -public const string AUTH_PROVIDER_LDAP = "ldap"; -# Authn scheme basic. -public const string AUTHN_SCHEME_BASIC = "basic"; -# Authn scheme JWT. -public const string AUTH_SCHEME_JWT = "jwt"; -# Authn scheme OAuth2. -public const string AUTH_SCHEME_OAUTH2 = "oauth2"; +public type InboundAuthScheme BASIC_AUTH|JWT_AUTH; + +public type OutboundAuthScheme BASIC_AUTH|OAUTH2|JWT_AUTH; + +public const BASIC_AUTH = "BASIC_AUTH"; +public const OAUTH2 = "OAUTH2"; +public const JWT_AUTH = "JWT_AUTH"; + +public type AuthStoreProvider CONFIG_AUTH_STORE|LDAP_AUTH_STORE; + +public const CONFIG_AUTH_STORE = "CONFIG_AUTH_STORE"; +public const LDAP_AUTH_STORE = "LDAP_AUTH_STORE"; # Extracts the basic authentication header value from the request. # diff --git a/stdlib/http/src/main/ballerina/http/http_secure_client.bal b/stdlib/http/src/main/ballerina/http/http_secure_client.bal index 810943593dd2..76f8f1de6d4f 100644 --- a/stdlib/http/src/main/ballerina/http/http_secure_client.bal +++ b/stdlib/http/src/main/ballerina/http/http_secure_client.bal @@ -14,10 +14,10 @@ // specific language governing permissions and limitations // under the License. +import ballerina/encoding; import ballerina/io; import ballerina/mime; import ballerina/runtime; -import ballerina/encoding; const string EMPTY_STRING = ""; const string WHITE_SPACE = " "; @@ -300,22 +300,34 @@ public function createHttpSecureClient(string url, ClientEndpointConfig config) # + config - Client endpoint configurations # + return - The Error occured during HTTP client invocation function generateSecureRequest(Request req, ClientEndpointConfig config) returns ()|error { - var scheme = config.auth.scheme; - if (scheme is AuthScheme) { - if (scheme == BASIC_AUTH) { - string username = config.auth.username ?: ""; - string password = config.auth.password ?: ""; - string str = username + ":" + password; - string token = encoding:encodeBase64(str.toByteArray("UTF-8")); - req.setHeader(AUTH_HEADER, AUTH_SCHEME_BASIC + WHITE_SPACE + token); - } else if (scheme == OAUTH2) { - string accessToken = config.auth.accessToken ?: ""; - if (accessToken == EMPTY_STRING) { - return updateRequestAndConfig(req, config); + var auth = config.auth; + if (auth is AuthConfig) { + if (auth.scheme == BASIC_AUTH) { + var basicAuthConfig = auth["basicAuthConfig"]; + if (basicAuthConfig is ()) { + error e = error("Basic auth config not provided"); + panic e; } else { - req.setHeader(AUTH_HEADER, AUTH_SCHEME_BEARER + WHITE_SPACE + accessToken); + string username = basicAuthConfig.username; + string password = basicAuthConfig.password; + string str = username + ":" + password; + string token = encoding:encodeBase64(str.toByteArray("UTF-8")); + req.setHeader(AUTH_HEADER, AUTH_SCHEME_BASIC + WHITE_SPACE + token); } - } else if (scheme == JWT_AUTH) { + } else if (auth.scheme == OAUTH2) { + var oAuth2Config = auth["oAuth2Config"]; + if (oAuth2Config is ()) { + error e = error("OAuth2 config not provided"); + panic e; + } else { + string accessToken = oAuth2Config.accessToken; + if (accessToken == EMPTY_STRING) { + return updateRequestAndConfig(req, config); + } else { + req.setHeader(AUTH_HEADER, AUTH_SCHEME_BEARER + WHITE_SPACE + accessToken); + } + } + } else if (auth.scheme == JWT_AUTH) { string authToken = runtime:getInvocationContext().authenticationContext.authToken; if (authToken == EMPTY_STRING) { error err = error(HTTP_ERROR_CODE, { message: "Authentication token is not set at invocation context" }); @@ -323,7 +335,7 @@ function generateSecureRequest(Request req, ClientEndpointConfig config) returns } req.setHeader(AUTH_HEADER, AUTH_SCHEME_BEARER + WHITE_SPACE + authToken); } else { - error err = error(HTTP_ERROR_CODE, { message: "Invalid authentication scheme. It should be basic, oauth2 or jwt" }); + error err = error(HTTP_ERROR_CODE, { message: "Unsupported auth scheme" }); return err; } } @@ -338,8 +350,8 @@ function generateSecureRequest(Request req, ClientEndpointConfig config) returns function updateRequestAndConfig(Request req, ClientEndpointConfig config) returns ()|error { string accessToken = check getAccessTokenFromRefreshToken(config); req.setHeader(AUTH_HEADER, AUTH_SCHEME_BEARER + WHITE_SPACE + accessToken); - AuthConfig? authConfig = config.auth; - if (authConfig is AuthConfig) { + OAuth2AuthConfig? authConfig = config.auth.oAuth2Config; + if (authConfig is OAuth2AuthConfig) { authConfig.accessToken = accessToken; } return (); @@ -351,51 +363,58 @@ function updateRequestAndConfig(Request req, ClientEndpointConfig config) return # + return - AccessToken received from the authorization server or `error` if error occured during HTTP client invocation function getAccessTokenFromRefreshToken(ClientEndpointConfig config) returns string|error { Client refreshTokenClient; - string refreshToken = config.auth.refreshToken ?: ""; - string clientId = config.auth.clientId ?: ""; - string clientSecret = config.auth.clientSecret ?: ""; - string refreshUrl = config.auth.refreshUrl ?: ""; - string[] scopes = config.auth.scopes ?: []; - - if (refreshToken == EMPTY_STRING || clientId == EMPTY_STRING || clientSecret == EMPTY_STRING || refreshUrl == EMPTY_STRING) { - error err = error(HTTP_ERROR_CODE, - { message: "Failed to generate new access token since one or more of refresh token, client id, client secret, - refresh url are not provided" }); - return err; - } + var oAuth2Config = config.auth.oAuth2Config; + if (oAuth2Config is OAuth2AuthConfig) { + string refreshToken = oAuth2Config.refreshToken; + string clientId = oAuth2Config.clientId; + string clientSecret = oAuth2Config.clientSecret; + string refreshUrl = oAuth2Config.refreshUrl; + string[] scopes = oAuth2Config.scopes; - var simpleClient = createClient(refreshUrl, {}); - if (simpleClient is Client) { - refreshTokenClient = simpleClient; - Request refreshTokenRequest = new; - string textPayload = "grant_type=refresh_token&refresh_token=" + refreshToken; - string scopeString = EMPTY_STRING; - foreach var requestScope in scopes { - scopeString = scopeString + WHITE_SPACE + requestScope; - } - if (scopeString != EMPTY_STRING) { - textPayload = textPayload + "&scope=" + scopeString.trim(); - } - if (config.auth.credentialBearer == AUTH_HEADER_BEARER) { - string clientIdSecret = clientId + ":" + clientSecret; - refreshTokenRequest.addHeader(AUTH_HEADER, AUTH_SCHEME_BASIC + WHITE_SPACE + - encoding:encodeBase64(clientIdSecret.toByteArray("UTF-8"))); - } else { - textPayload = textPayload + "&client_id=" + clientId + "&client_secret=" + clientSecret; + if (refreshToken == EMPTY_STRING || clientId == EMPTY_STRING || clientSecret == EMPTY_STRING + || refreshUrl == EMPTY_STRING) { + error err = error(HTTP_ERROR_CODE, + { message: "Failed to generate new access token since one or more of refresh token, client id, + client secret, refresh url are not provided" }); + return err; } - refreshTokenRequest.setTextPayload(untaint textPayload, contentType = mime:APPLICATION_FORM_URLENCODED); - Response refreshTokenResponse = check refreshTokenClient->post(EMPTY_STRING, refreshTokenRequest); - json generatedToken = check refreshTokenResponse.getJsonPayload(); - if (refreshTokenResponse.statusCode == OK_200) { - return generatedToken.access_token.toString(); + var simpleClient = createClient(refreshUrl, {}); + if (simpleClient is Client) { + refreshTokenClient = simpleClient; + Request refreshTokenRequest = new; + string textPayload = "grant_type=refresh_token&refresh_token=" + refreshToken; + string scopeString = EMPTY_STRING; + foreach var requestScope in scopes { + scopeString = scopeString + WHITE_SPACE + requestScope; + } + if (scopeString != EMPTY_STRING) { + textPayload = textPayload + "&scope=" + scopeString.trim(); + } + if (oAuth2Config.credentialBearer == AUTH_HEADER_BEARER) { + string clientIdSecret = clientId + ":" + clientSecret; + refreshTokenRequest.addHeader(AUTH_HEADER, AUTH_SCHEME_BASIC + WHITE_SPACE + + encoding:encodeBase64(clientIdSecret.toByteArray("UTF-8"))); + } else { + textPayload = textPayload + "&client_id=" + clientId + "&client_secret=" + clientSecret; + } + refreshTokenRequest.setTextPayload(untaint textPayload, contentType = mime:APPLICATION_FORM_URLENCODED); + Response refreshTokenResponse = check refreshTokenClient->post(EMPTY_STRING, refreshTokenRequest); + + json generatedToken = check refreshTokenResponse.getJsonPayload(); + if (refreshTokenResponse.statusCode == OK_200) { + return generatedToken.access_token.toString(); + } else { + error err = error(HTTP_ERROR_CODE, + { message: "Failed to generate new access token from the given refresh token" }); + return err; + } } else { - error err = error(HTTP_ERROR_CODE, - { message: "Failed to generate new access token from the given refresh token" }); - return err; + return simpleClient; } } else { - return simpleClient; + error e = error("OAuth2 config not provided"); + panic e; } } @@ -408,7 +427,7 @@ function getAccessTokenFromRefreshToken(ClientEndpointConfig config) returns str # + return - Whether the client should retry or not function isRetryRequired(Response response, ClientEndpointConfig config) returns boolean { var scheme = config.auth.scheme; - if (scheme is AuthScheme) { + if (scheme is InboundAuthScheme) { if (scheme == OAUTH2 && response.statusCode == UNAUTHORIZED_401) { return true; } diff --git a/stdlib/http/src/main/ballerina/http/service_endpoint.bal b/stdlib/http/src/main/ballerina/http/service_endpoint.bal index df71f8cd52a1..d85de6e5b45d 100644 --- a/stdlib/http/src/main/ballerina/http/service_endpoint.bal +++ b/stdlib/http/src/main/ballerina/http/service_endpoint.bal @@ -15,9 +15,9 @@ // under the License. import ballerina/auth; +import ballerina/crypto; import ballerina/log; import ballerina/system; -import ballerina/crypto; ///////////////////////////// /// HTTP Listener Endpoint /// @@ -209,39 +209,20 @@ public type AuthCacheConfig record { }; # Configuration for authentication providers. -# -# + scheme - Authentication scheme + # + id - Authentication provider instance id +# + scheme - Authentication scheme # + authStoreProvider - Authentication store provider (Config, LDAP, etc.) implementation -# + authStoreProviderConfig - Auth store related configurations -# + issuer - Identifier of the token issuer -# + audience - Identifier of the token recipients -# + trustStore - Trustore configurations -# + certificateAlias - Token signed key alias -# + clockSkew - Time in seconds to mitigate clock skew -# + keyStore - `KeyStore` instance providing key store related configurations -# + keyAlias - The Key Alias -# + keyPassword - The Key password -# + expTime - Expiry time -# + signingAlg - The signing algorithm which is used to sign the JWT token -# + propagateJwt - `true` if propagating authentication info as JWT +# + ldapAuthProviderConfig - LDAP auth provider related configurations +# + configAuthProviderConfig - Config auth provider related configurations +# + jwtAuthProviderConfig - JWT auth provider related configurations public type AuthProvider record { - auth:InboundAuthScheme scheme; string id = ""; - auth:AuthStoreProvider authStoreProvider = (); - auth:LdapAuthProviderConfig? authStoreProviderConfig = (); - string issuer = ""; - string audience = ""; - TrustStore? trustStore = (); - string certificateAlias = ""; - boolean validateCertificate?; - int clockSkew = 0; - KeyStore? keyStore = (); - string keyAlias = ""; - string keyPassword = ""; - int expTime = 0; - auth:JwtSigningAlgorithm signingAlg = auth:RS512; - boolean propagateJwt = false; + InboundAuthScheme scheme; + AuthStoreProvider? authStoreProvider = (); + auth:LdapAuthProviderConfig? ldapAuthProviderConfig = (); + auth:ConfigAuthProviderConfig? configAuthProviderConfig = (); + auth:JWTAuthProviderConfig? jwtAuthProviderConfig = (); !...; }; @@ -315,21 +296,21 @@ function createAuthFiltersForSecureListener(ServiceEndpointConfiguration config, auth:AuthStoreProvider authStoreProvider = new; foreach var provider in authProviderList { - if (provider.scheme == AUTHN_SCHEME_BASIC) { - if (provider.authStoreProvider == AUTH_PROVIDER_LDAP) { - var authStoreProviderConfig = provider.authStoreProviderConfig; - if (authStoreProviderConfig is auth:LdapAuthProviderConfig) { - auth:LdapAuthStoreProvider ldapAuthStoreProvider = new(authStoreProviderConfig, instanceId); + if (provider.scheme == BASIC_AUTH) { + if (provider.authStoreProvider == LDAP_AUTH_STORE) { + var ldapAuthProviderConfig = provider.ldapAuthProviderConfig; + if (ldapAuthProviderConfig is auth:LdapAuthProviderConfig) { + auth:LdapAuthStoreProvider ldapAuthStoreProvider = new(ldapAuthProviderConfig, instanceId); authStoreProvider = ldapAuthStoreProvider; } else { - error e = error("Authstore config not provided for : " + provider.authStoreProvider); + error e = error("LDAP auth provider config not provided"); panic e; } - } else if (provider.authStoreProvider == AUTH_PROVIDER_CONFIG) { + } else if (provider.authStoreProvider == CONFIG_AUTH_STORE) { auth:ConfigAuthStoreProvider configAuthStoreProvider = new; authStoreProvider = configAuthStoreProvider; } else { - error configError = error("Unsupported auth store provider : " + provider.authStoreProvider); + error configError = error("Unsupported auth store provider"); panic configError; } } @@ -350,88 +331,60 @@ function createBasicAuthHandler() returns HttpAuthnHandler { } function createAuthHandler(AuthProvider authProvider, string instanceId) returns HttpAuthnHandler { - if (authProvider.scheme == AUTHN_SCHEME_BASIC) { + if (authProvider.scheme == BASIC_AUTH) { auth:AuthStoreProvider authStoreProvider = new; - if (authProvider.authStoreProvider == AUTH_PROVIDER_CONFIG) { - if (authProvider.propagateJwt) { - auth:ConfigJwtAuthProvider configAuthProvider = new(getInferredJwtAuthProviderConfig(authProvider)); - authStoreProvider = configAuthProvider; - } else { + if (authProvider.authStoreProvider == CONFIG_AUTH_STORE) { + var configAuthProviderConfig = authProvider.configAuthProviderConfig; + boolean authStoreProviderInitialized = false; + if (configAuthProviderConfig is auth:ConfigAuthProviderConfig) { + var inferredJwtIssuerConfig = configAuthProviderConfig.inferredJwtIssuerConfig; + if (inferredJwtIssuerConfig is auth:InferredJwtIssuerConfig) { + auth:ConfigJwtAuthProvider configAuthProvider = new(inferredJwtIssuerConfig); + authStoreProvider = configAuthProvider; + authStoreProviderInitialized = true; + } + } + if (!authStoreProviderInitialized) { auth:ConfigAuthStoreProvider configAuthStoreProvider = new; authStoreProvider = configAuthStoreProvider; } - } else if (authProvider.authStoreProvider == AUTH_PROVIDER_LDAP) { - var authStoreProviderConfig = authProvider.authStoreProviderConfig; - if (authStoreProviderConfig is auth:LdapAuthProviderConfig) { - auth:LdapAuthStoreProvider ldapAuthStoreProvider = new(authStoreProviderConfig, instanceId); - if (authProvider.propagateJwt) { - auth:LdapJwtAuthProvider ldapAuthProvider = - new(getInferredJwtAuthProviderConfig(authProvider),ldapAuthStoreProvider); + } else if (authProvider.authStoreProvider == LDAP_AUTH_STORE) { + var ldapAuthProviderConfig = authProvider.ldapAuthProviderConfig; + if (ldapAuthProviderConfig is auth:LdapAuthProviderConfig) { + auth:LdapAuthStoreProvider ldapAuthStoreProvider = new(ldapAuthProviderConfig, instanceId); + var inferredJwtIssuerConfig = ldapAuthProviderConfig.inferredJwtIssuerConfig; + if (inferredJwtIssuerConfig is auth:InferredJwtIssuerConfig) { + auth:LdapJwtAuthProvider ldapAuthProvider = new(inferredJwtIssuerConfig, ldapAuthStoreProvider); authStoreProvider = ldapAuthProvider; } else { authStoreProvider = ldapAuthStoreProvider; } } else { - error e = error("Authstore config not provided for : " + authProvider.authStoreProvider); + error e = error("LDAP auth provider config not provided"); panic e; } } else { - // other auth providers are unsupported yet - error e = error("Invalid auth provider: " + authProvider.authStoreProvider); + error e = error("Unsupported auth store provider"); panic e; } HttpBasicAuthnHandler basicAuthHandler = new(authStoreProvider); return basicAuthHandler; - } else if (authProvider.scheme == AUTH_SCHEME_JWT){ - string trustStorePath = authProvider.trustStore.path ?: ""; - string trustStorePassword = authProvider.trustStore.password ?: ""; - crypto:TrustStore trustStore = { - path: trustStorePath, - password: trustStorePassword - }; - auth:JWTAuthProviderConfig jwtConfig = { - issuer: authProvider.issuer, - audience: authProvider.audience, - certificateAlias: authProvider.certificateAlias, - clockSkew: authProvider.clockSkew, - trustStore: trustStore - }; - var validateCertificate = authProvider["validateCertificate"]; - if (validateCertificate is boolean) { - jwtConfig.validateCertificate = validateCertificate; + } else if (authProvider.scheme == JWT_AUTH){ + var jwtAuthProviderConfig = authProvider.jwtAuthProviderConfig; + if (jwtAuthProviderConfig is auth:JWTAuthProviderConfig) { + auth:JWTAuthProvider jwtAuthProvider = new(jwtAuthProviderConfig); + HttpJwtAuthnHandler jwtAuthnHandler = new(jwtAuthProvider); + return jwtAuthnHandler; + } else { + error e = error("JWT auth provider config not provided"); + panic e; } - auth:JWTAuthProvider jwtAuthProvider = new(jwtConfig); - HttpJwtAuthnHandler jwtAuthnHandler = new(jwtAuthProvider); - return jwtAuthnHandler; } else { - error e = error("Invalid auth scheme: " + authProvider.scheme); + error e = error("Unsupported auth scheme"); panic e; } } -function getInferredJwtAuthProviderConfig(AuthProvider authProvider) returns auth:InferredJwtAuthProviderConfig { - //ConfigJwtAuthProviderConfig - string defaultIssuer = "ballerina"; - string defaultAudience = "ballerina"; - int defaultExpTime = 300; // in seconds - string defaultSignAlg = "RS256"; - - crypto:KeyStore keyStore = { - path: authProvider.keyStore.path ?: "", - password: authProvider.keyStore.password ?: "" - }; - auth:InferredJwtAuthProviderConfig jwtAuthConfig = { - issuer: authProvider.issuer == "" ? defaultIssuer : authProvider.issuer, - expTime: authProvider.expTime == 0 ? defaultExpTime : authProvider.expTime, - signingAlg: authProvider.signingAlg, - audience: authProvider.audience == "" ? defaultAudience : authProvider.audience, - keyAlias: authProvider.keyAlias, - keyPassword: authProvider.keyPassword, - keyStore: keyStore - }; - return jwtAuthConfig; -} - ////////////////////////////////// /// WebSocket Service Endpoint /// ////////////////////////////////// diff --git a/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/ServiceLevelAuthnTest.java b/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/ServiceLevelAuthnTest.java index c4a1f94b6845..705e24501d49 100644 --- a/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/ServiceLevelAuthnTest.java +++ b/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/ServiceLevelAuthnTest.java @@ -18,10 +18,8 @@ package org.ballerinalang.test.auth; -import io.netty.handler.codec.http.HttpHeaderNames; import org.ballerinalang.test.util.HttpResponse; import org.ballerinalang.test.util.HttpsClientRequest; -import org.ballerinalang.test.util.TestConstant; import org.testng.Assert; import org.testng.annotations.Test; diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authclients/oauth-client.bal b/tests/ballerina-integration-test/src/test/resources/auth/authclients/oauth-client.bal index ed0a64fa14dd..880936da11ac 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authclients/oauth-client.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authclients/oauth-client.bal @@ -19,55 +19,65 @@ import ballerina/http; http:Client clientEP1 = new("https://localhost:9095/foo", config = { auth: { scheme: http:OAUTH2, - refreshToken: "5Aep861..zRMyCurAUgnwQaEjnCVqxK2utna7Mm4nb9UamD7BW50R2huecjSaLlv5mT1z_TViZ", - clientId: "3MVG9YDQS5WtC11paU2WcQjBB3L5w4gz52uriT8ksZ3nUVjKvrfQMrU4uvZohTftxStwNEW4cfStBEGRxRL68", - clientSecret: "9205371918321623741", - refreshUrl: "https://localhost:9095/foo/token", - credentialBearer: http:POST_BODY_BEARER + oAuth2Config: { + refreshToken: "5Aep861..zRMyCurAUgnwQaEjnCVqxK2utna7Mm4nb9UamD7BW50R2huecjSaLlv5mT1z_TViZ", + clientId: "3MVG9YDQS5WtC11paU2WcQjBB3L5w4gz52uriT8ksZ3nUVjKvrfQMrU4uvZohTftxStwNEW4cfStBEGRxRL68", + clientSecret: "9205371918321623741", + refreshUrl: "https://localhost:9095/foo/token", + credentialBearer: http:POST_BODY_BEARER + } } }); http:Client clientEP2 = new("https://localhost:9095/foo", config = { auth: { scheme: http:OAUTH2, - refreshToken: "5Aep861..zRMyCurAUgnwQaEjnCVqxK2utna7Mm4nb9UamD7BW50R2huecjSaLlv5mT1z_TViZ", - clientId: "3MVG9YDQS5WtC11paU2WcQjBB3L5w4gz52uriT8ksZ3nUVjKvrfQMrU4uvZohTftxStwNEW4cfStBEGRxRL68", - clientSecret: "9205371918321623741", - refreshUrl: "https://localhost:9095/foo/token", - credentialBearer: http:AUTH_HEADER_BEARER + oAuth2Config: { + refreshToken: "5Aep861..zRMyCurAUgnwQaEjnCVqxK2utna7Mm4nb9UamD7BW50R2huecjSaLlv5mT1z_TViZ", + clientId: "3MVG9YDQS5WtC11paU2WcQjBB3L5w4gz52uriT8ksZ3nUVjKvrfQMrU4uvZohTftxStwNEW4cfStBEGRxRL68", + clientSecret: "9205371918321623741", + refreshUrl: "https://localhost:9095/foo/token", + credentialBearer: http:AUTH_HEADER_BEARER + } } }); http:Client clientEP3 = new("https://localhost:9095/foo", config = { auth: { scheme: http:OAUTH2, - refreshToken: "5Aep861..zRMyCurAUgnwQaEjnCVqxK2utna7Mm4nb9UamD7BW50R2huecjSaLlv5mT1z_TViZ", - clientId: "3MVG9YDQS5WtC11paU2WcQjBB3L5w4gz52uriT8ksZ3nUVjKvrfQMrU4uvZohTftxStwNEW4cfStBEGRxRL68", - clientSecret: "9205371918321623741", - refreshUrl: "https://localhost:9095/foo/token" + oAuth2Config: { + refreshToken: "5Aep861..zRMyCurAUgnwQaEjnCVqxK2utna7Mm4nb9UamD7BW50R2huecjSaLlv5mT1z_TViZ", + clientId: "3MVG9YDQS5WtC11paU2WcQjBB3L5w4gz52uriT8ksZ3nUVjKvrfQMrU4uvZohTftxStwNEW4cfStBEGRxRL68", + clientSecret: "9205371918321623741", + refreshUrl: "https://localhost:9095/foo/token" + } } }); http:Client clientEP4 = new("https://localhost:9095/foo", config = { auth: { scheme: http:OAUTH2, - refreshToken: "5Aep861..zRMyCurAUgnwQaEjnCVqxK2utna7Mm4nb9UamD7BW50R2huecjSaLlv5mT1z_TViZ", - clientId: "3MVG9YDQS5WtC11paU2WcQjBB3L5w4gz52uriT8ksZ3nUVjKvrfQMrU4uvZohTftxStwNEW4cfStBEGRxRL68", - clientSecret: "9205371918321623741", - refreshUrl: "https://localhost:9095/foo/token", - scopes: ["token-scope1", "token-scope2"] + oAuth2Config : { + refreshToken: "5Aep861..zRMyCurAUgnwQaEjnCVqxK2utna7Mm4nb9UamD7BW50R2huecjSaLlv5mT1z_TViZ", + clientId: "3MVG9YDQS5WtC11paU2WcQjBB3L5w4gz52uriT8ksZ3nUVjKvrfQMrU4uvZohTftxStwNEW4cfStBEGRxRL68", + clientSecret: "9205371918321623741", + refreshUrl: "https://localhost:9095/foo/token", + scopes: ["token-scope1", "token-scope2"] + } } }); http:Client clientEP5 = new("https://localhost:9095/foo", config = { auth: { scheme: http:OAUTH2, - refreshToken: "5Aep861..zRMyCurAUgnwQaEjnCVqxK2utna7Mm4nb9UamD7BW50R2huecjSaLlv5mT1z_TViZ", - clientId: "3MVG9YDQS5WtC11paU2WcQjBB3L5w4gz52uriT8ksZ3nUVjKvrfQMrU4uvZohTftxStwNEW4cfStBEGRxRL68", - clientSecret: "9205371918321623741", - refreshUrl: "https://localhost:9095/foo/token", - credentialBearer: http:POST_BODY_BEARER, - scopes: ["token-scope1", "token-scope2"] + oAuth2Config: { + refreshToken: "5Aep861..zRMyCurAUgnwQaEjnCVqxK2utna7Mm4nb9UamD7BW50R2huecjSaLlv5mT1z_TViZ", + clientId: "3MVG9YDQS5WtC11paU2WcQjBB3L5w4gz52uriT8ksZ3nUVjKvrfQMrU4uvZohTftxStwNEW4cfStBEGRxRL68", + clientSecret: "9205371918321623741", + refreshUrl: "https://localhost:9095/foo/token", + credentialBearer: http:POST_BODY_BEARER, + scopes: ["token-scope1", "token-scope2"] + } } }); diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/01_authn_config_inheritance_auth_disabling_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/01_authn_config_inheritance_auth_disabling_test.bal index b83846f92409..393e935afbe6 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/01_authn_config_inheritance_auth_disabling_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/01_authn_config_inheritance_auth_disabling_test.bal @@ -1,8 +1,8 @@ import ballerina/http; http:AuthProvider basicAuthProvider01 = { - scheme: "basic", - authStoreProvider: "config" + scheme: http:BASIC_AUTH, + authStoreProvider: http:CONFIG_AUTH_STORE }; listener http:Listener listener01 = new(9090, config = { diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/02_authn_config_inheritance_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/02_authn_config_inheritance_test.bal index 6de7c0d3f66d..ab023cf8d569 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/02_authn_config_inheritance_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/02_authn_config_inheritance_test.bal @@ -1,8 +1,8 @@ import ballerina/http; http:AuthProvider basicAuthProvider02 = { - scheme: "basic", - authStoreProvider: "config" + scheme: http:BASIC_AUTH, + authStoreProvider: http:CONFIG_AUTH_STORE }; listener http:Listener listener02 = new(9091, config = { diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/03_authz_config_inheritance_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/03_authz_config_inheritance_test.bal index 7181bcf5a37c..4c08e0331206 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/03_authz_config_inheritance_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/03_authz_config_inheritance_test.bal @@ -1,8 +1,8 @@ import ballerina/http; http:AuthProvider basicAuthProvider03 = { - scheme: "basic", - authStoreProvider: "config" + scheme: http:BASIC_AUTH, + authStoreProvider: http:CONFIG_AUTH_STORE }; listener http:Listener listener03 = new(9092, config = { diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/04_resource_level_auth_config_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/04_resource_level_auth_config_test.bal index e913c48e45f3..0d67adeb2dae 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/04_resource_level_auth_config_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/04_resource_level_auth_config_test.bal @@ -1,8 +1,8 @@ import ballerina/http; http:AuthProvider basicAuthProvider04 = { - scheme: "basic", - authStoreProvider: "config" + scheme: http:BASIC_AUTH, + authStoreProvider: http:CONFIG_AUTH_STORE }; listener http:Listener listener04 = new(9093, config = { diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/05_service_level_auth_config_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/05_service_level_auth_config_test.bal index 0ca9ad52eba1..b0cec735296b 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/05_service_level_auth_config_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/05_service_level_auth_config_test.bal @@ -1,8 +1,8 @@ import ballerina/http; http:AuthProvider basicAuthProvider05 = { - scheme: "basic", - authStoreProvider: "config" + scheme: http:BASIC_AUTH, + authStoreProvider: http:CONFIG_AUTH_STORE }; listener http:Listener listener05 = new(9094, config = { diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/07_auth_test_without_annotations.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/07_auth_test_without_annotations.bal index bab1a9e33cae..7248f5563bd9 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/07_auth_test_without_annotations.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/07_auth_test_without_annotations.bal @@ -17,8 +17,8 @@ import ballerina/http; http:AuthProvider basicAuthProvider07 = { - scheme: "basic", - authStoreProvider: "config" + scheme: http:BASIC_AUTH, + authStoreProvider: http:CONFIG_AUTH_STORE }; listener http:Listener listener07 = new(9098, config = { diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/08_authn_with_multiple_providers.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/08_authn_with_multiple_providers.bal index 2f853d225667..30572fe9d831 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/08_authn_with_multiple_providers.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/08_authn_with_multiple_providers.bal @@ -17,24 +17,28 @@ import ballerina/http; http:AuthProvider jwtAuthProvider1 = { - scheme: "jwt", - issuer: "example1", - audience: "ballerina", - certificateAlias: "ballerina", - trustStore: { - path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", - password: "ballerina" + scheme: http:JWT_AUTH, + jwtAuthProviderConfig: { + issuer: "example1", + audience: "ballerina", + certificateAlias: "ballerina", + trustStore: { + path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", + password: "ballerina" + } } }; http:AuthProvider jwtAuthProvider2 = { - scheme: "jwt", - issuer: "example2", - audience: "ballerina", - certificateAlias: "ballerina", - trustStore: { - path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", - password: "ballerina" + scheme: http:JWT_AUTH, + jwtAuthProviderConfig: { + issuer: "example2", + audience: "ballerina", + certificateAlias: "ballerina", + trustStore: { + path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", + password: "ballerina" + } } }; diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/09_authn_with_different_scopes.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/09_authn_with_different_scopes.bal index 0e70d9ff7bee..2da76b8b2a59 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/09_authn_with_different_scopes.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/09_authn_with_different_scopes.bal @@ -17,13 +17,15 @@ import ballerina/http; http:AuthProvider jwtAuthProvider3 = { - scheme: "jwt", - issuer: "ballerina", - audience: "ballerina", - certificateAlias: "ballerina", - trustStore: { - path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", - password: "ballerina" + scheme: http:JWT_AUTH, + jwtAuthProviderConfig: { + issuer: "ballerina", + audience: "ballerina", + certificateAlias: "ballerina", + trustStore: { + path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", + password: "ballerina" + } } }; diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/10_secure_service_no_token_propagation_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/10_secure_service_no_token_propagation_test.bal index ca98e5a970d7..ce1ba34f1940 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/10_secure_service_no_token_propagation_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/10_secure_service_no_token_propagation_test.bal @@ -2,8 +2,8 @@ import ballerina/http; // token propagation is set to false by default http:AuthProvider basicAuthProvider10 = { - scheme: "basic", - authStoreProvider: "config" + scheme: http:BASIC_AUTH, + authStoreProvider: http:CONFIG_AUTH_STORE }; listener http:Listener listener10_1 = new(9190, config = { @@ -38,13 +38,15 @@ service passthroughService on listener10_1 { } http:AuthProvider jwtAuthProvider10 = { - scheme: "jwt", - issuer: "ballerina", - audience: "ballerina", - certificateAlias: "ballerina", - trustStore: { - path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", - password: "ballerina" + scheme: http:JWT_AUTH, + jwtAuthProviderConfig: { + issuer: "ballerina", + audience: "ballerina", + certificateAlias: "ballerina", + trustStore: { + path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", + password: "ballerina" + } } }; diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/11_secure_service_token_propagation_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/11_secure_service_token_propagation_test.bal index 4e50d2a0669e..907000e39adf 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/11_secure_service_token_propagation_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/11_secure_service_token_propagation_test.bal @@ -1,15 +1,18 @@ import ballerina/http; http:AuthProvider basicAuthProvider11 = { - scheme: "basic", - authStoreProvider: "config", - propagateJwt: true, - issuer: "ballerina", - keyAlias: "ballerina", - keyPassword: "ballerina", - keyStore: { - path: "${ballerina.home}/bre/security/ballerinaKeystore.p12", - password: "ballerina" + scheme: http:BASIC_AUTH, + authStoreProvider: http:CONFIG_AUTH_STORE, + configAuthProviderConfig: { + inferredJwtIssuerConfig: { + issuer: "ballerina", + keyAlias: "ballerina", + keyPassword: "ballerina", + keyStore: { + path: "${ballerina.home}/bre/security/ballerinaKeystore.p12", + password: "ballerina" + } + } } }; @@ -24,7 +27,7 @@ listener http:Listener listener11 = new(9192, config = { }); http:Client nyseEP03 = new("http://localhost:9193", config = { - auth: { scheme: "JWT" } + auth: { scheme: http:JWT_AUTH } }); @http:ServiceConfig { basePath: "/passthrough" } @@ -46,13 +49,15 @@ service passthroughService03 on listener11 { } http:AuthProvider jwtAuthProvider03 = { - scheme: "jwt", - issuer: "ballerina", - audience: "ballerina", - certificateAlias: "ballerina", - trustStore: { - path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", - password: "ballerina" + scheme: http:JWT_AUTH, + jwtAuthProviderConfig: { + issuer: "ballerina", + audience: "ballerina", + certificateAlias: "ballerina", + trustStore: { + path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", + password: "ballerina" + } } }; diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/12_secure_service_wrong_provider_id_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/12_secure_service_wrong_provider_id_test.bal index cc710cd196b5..f9db42f64aaf 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/12_secure_service_wrong_provider_id_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/12_secure_service_wrong_provider_id_test.bal @@ -2,8 +2,8 @@ import ballerina/http; http:AuthProvider basicAuthProvider12 = { id: "basic1", - scheme: "basic", - authStoreProvider: "config" + scheme: http:BASIC_AUTH, + authStoreProvider: http:CONFIG_AUTH_STORE }; listener http:Listener listener12 = new(9194, config = { diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/13_authn_with_expired_certificate.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/13_authn_with_expired_certificate.bal index 6b2e75f3204e..c02f5cfa5d2d 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/13_authn_with_expired_certificate.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/13_authn_with_expired_certificate.bal @@ -17,13 +17,15 @@ import ballerina/http; http:AuthProvider jwtAuthProvider4 = { - scheme:"jwt", - issuer:"ballerina", - audience: "ballerina.io", - certificateAlias: "cert", - trustStore: { - path: "../../../src/test/resources/auth/testtruststore.p12", - password: "ballerina" + scheme: http:JWT_AUTH, + jwtAuthProviderConfig: { + issuer:"ballerina", + audience: "ballerina.io", + certificateAlias: "cert", + trustStore: { + path: "../../../src/test/resources/auth/testtruststore.p12", + password: "ballerina" + } } }; diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/14_authn_with_expired_certificate_with_no_expiry_validation.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/14_authn_with_expired_certificate_with_no_expiry_validation.bal index a61aa9b10ed4..cd4342410a58 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/14_authn_with_expired_certificate_with_no_expiry_validation.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/14_authn_with_expired_certificate_with_no_expiry_validation.bal @@ -17,14 +17,16 @@ import ballerina/http; http:AuthProvider jwtAuthProvider14 = { - scheme:"jwt", - issuer:"ballerina", - audience: "ballerina.io", - certificateAlias: "cert", - validateCertificate: false, - trustStore: { - path: "../../../src/test/resources/auth/testtruststore.p12", - password: "ballerina" + scheme: http:JWT_AUTH, + jwtAuthProviderConfig: { + issuer:"ballerina", + audience: "ballerina.io", + certificateAlias: "cert", + validateCertificate: false, + trustStore: { + path: "../../../src/test/resources/auth/testtruststore.p12", + password: "ballerina" + } } }; diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/ldap_auth_store_authorization_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/ldap_auth_store_authorization_test.bal index 039cd8f2e2e0..7f225b9273e8 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/ldap_auth_store_authorization_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/ldap_auth_store_authorization_test.bal @@ -42,9 +42,9 @@ auth:LdapAuthProviderConfig ldapConfig = { http:AuthProvider authProvider = { id: "basic02", - scheme: http:AUTHN_SCHEME_BASIC, - authStoreProvider: http:AUTH_PROVIDER_LDAP, - authStoreProviderConfig: ldapConfig + scheme: http:BASIC_AUTH, + authStoreProvider: http:LDAP_AUTH_STORE, + ldapAuthProviderConfig: ldapConfig }; listener http:Listener authEP = new(9097, config = { diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/ldap_auth_store_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/ldap_auth_store_test.bal index 85c2a3625525..3e148deb059c 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/ldap_auth_store_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/ldap_auth_store_test.bal @@ -42,9 +42,9 @@ auth:LdapAuthProviderConfig ldapAuthProviderConfig = { http:AuthProvider basicAuthProvider = { id: "basic01", - scheme: http:AUTHN_SCHEME_BASIC, - authStoreProvider: http:AUTH_PROVIDER_LDAP, - authStoreProviderConfig: ldapAuthProviderConfig + scheme: http:BASIC_AUTH, + authStoreProvider: http:LDAP_AUTH_STORE, + ldapAuthProviderConfig: ldapAuthProviderConfig }; listener http:Listener ep = new(9096, config = { From d1a149fede18c7145e1ca01d380f0ae3ed2cf224 Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Wed, 13 Feb 2019 10:50:20 +0530 Subject: [PATCH 13/26] Remove inferred auth providers --- .../src/main/ballerina/auth/jwt_issuer.bal | 20 +++++ .../auth/jwt_supported_auth_provider_util.bal | 76 ------------------- .../jwt_supported_config_auth_provider.bal | 38 ---------- .../auth/jwt_supported_ldap_auth_provider.bal | 56 -------------- .../main/ballerina/http/auth/authz_filter.bal | 13 +++- .../main/ballerina/http/client_endpoint.bal | 14 +++- .../ballerina/http/http_secure_client.bal | 57 ++++++++++---- .../ballerina/runtime/invocation-context.bal | 4 +- .../ballerinalang/test/auth/AuthBaseTest.java | 2 +- .../auth/authclients/oauth-client.bal | 10 +-- .../15_jwt_authenticated_jwt_client_test.bal | 55 ++++++++++++++ 11 files changed, 147 insertions(+), 198 deletions(-) delete mode 100644 stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal delete mode 100644 stdlib/auth/src/main/ballerina/auth/jwt_supported_config_auth_provider.bal delete mode 100644 stdlib/auth/src/main/ballerina/auth/jwt_supported_ldap_auth_provider.bal create mode 100644 tests/ballerina-integration-test/src/test/resources/auth/authservices/15_jwt_authenticated_jwt_client_test.bal diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal b/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal index 5cacbd32fcd5..fdead8ef3d13 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal @@ -138,3 +138,23 @@ function convertIntArrayToJson(int[] arrayToConvert) returns (json) { } return jsonPayload; } + +# Represents authentication provider configurations that supports generating JWT for client interactions. +# +# + issuer - Expected JWT token issuer +# + audience - Expected JWT token audience +# + expTime - Expiry time for newly issued JWT tokens +# + keyStore - Keystore containing the signing key +# + keyAlias - Key alias for signing newly issued JWT tokens +# + keyPassword - Key password for signing newly issued JWT tokens +# + signingAlg - Signing algorithm for signing newly issued JWT tokens +public type InferredJwtIssuerConfig record { + string issuer; + string[] audience; + int expTime = 300; + crypto:KeyStore keyStore; + string keyAlias; + string keyPassword; + JwtSigningAlgorithm signingAlg = RS256; + !...; +}; diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal b/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal deleted file mode 100644 index a57f8edf572e..000000000000 --- a/stdlib/auth/src/main/ballerina/auth/jwt_supported_auth_provider_util.bal +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) 2018 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. - -import ballerina/crypto; -import ballerina/internal; -import ballerina/runtime; -import ballerina/system; -import ballerina/time; - -# Represents authentication provider configurations that supports generating JWT for client interactions. -# -# + issuer - Expected JWT token issuer -# + audience - Expected JWT token audience -# + expTime - Expiry time for newly issued JWT tokens -# + keyStore - Keystore containing the signing key -# + keyAlias - Key alias for signing newly issued JWT tokens -# + keyPassword - Key password for signing newly issued JWT tokens -# + signingAlg - Signing algorithm for signing newly issued JWT tokens -public type InferredJwtIssuerConfig record { - string issuer = "ballerina"; - string audience = "ballerina"; - int expTime = 300; - crypto:KeyStore keyStore; - string keyAlias; - string keyPassword; - JwtSigningAlgorithm signingAlg = RS256; - !...; -}; - -# Sets the jwt access token to the AuthenticationContext -# -# + username - user name -# + authConfig - authentication provider configurations that supports generating JWT for client interactions -function setAuthToken(string username, InferredJwtIssuerConfig authConfig) { - JwtHeader header = createHeader(authConfig); - JwtPayload payload = createPayload(username, authConfig); - var token = issueJwt(header, payload, authConfig.keyStore, authConfig.keyAlias, authConfig.keyPassword); - if (token is string) { - runtime:AuthenticationContext authenticationContext = runtime:getInvocationContext().authenticationContext; - authenticationContext.scheme = "jwt"; - authenticationContext.authToken = token; - } -} - -function createHeader(InferredJwtIssuerConfig authConfig) returns (JwtHeader) { - JwtHeader header = { alg: authConfig.signingAlg, typ: "JWT" }; - return header; -} - -function createPayload(string username, InferredJwtIssuerConfig authConfig) returns (JwtPayload) { - string audList = authConfig.audience; - string[] audience = audList.split(" "); - JwtPayload payload = { - sub: username, - iss: authConfig.issuer, - exp: time:currentTime().time / 1000 + authConfig.expTime, - iat: time:currentTime().time / 1000, - nbf: time:currentTime().time / 1000, - jti: system:uuid(), - aud: audience - }; - return payload; -} diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_supported_config_auth_provider.bal b/stdlib/auth/src/main/ballerina/auth/jwt_supported_config_auth_provider.bal deleted file mode 100644 index 2d91b82aed8c..000000000000 --- a/stdlib/auth/src/main/ballerina/auth/jwt_supported_config_auth_provider.bal +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) 2018 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. - -public type ConfigJwtAuthProvider object { - - public InferredJwtIssuerConfig inferredJwtIssuerConfig; - public ConfigAuthStoreProvider configAuthProvider = new; - - public function __init(InferredJwtIssuerConfig inferredJwtIssuerConfig) { - self.inferredJwtIssuerConfig = inferredJwtIssuerConfig; - } - - public function authenticate(string username, string password) returns boolean { - boolean isAuthenticated = self.configAuthProvider.authenticate(username, password); - if (isAuthenticated){ - setAuthToken(username, self.inferredJwtIssuerConfig); - } - return isAuthenticated; - } - - public function getScopes(string username) returns string[] { - return self.configAuthProvider.getScopes(username); - } - -}; diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_supported_ldap_auth_provider.bal b/stdlib/auth/src/main/ballerina/auth/jwt_supported_ldap_auth_provider.bal deleted file mode 100644 index 1af50438310b..000000000000 --- a/stdlib/auth/src/main/ballerina/auth/jwt_supported_ldap_auth_provider.bal +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2018 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. - -# Represents LDAP authentication provider that supports generating JWT for client interactions -# -# + inferredJwtIssuerConfig - JWT configurations -# + ldapAuthProvider - LDAP auth store provider -public type LdapJwtAuthProvider object { - - public InferredJwtIssuerConfig inferredJwtIssuerConfig; - public LdapAuthStoreProvider ldapAuthProvider; - - # Provides authentication based on the configured LDAP user store - # - # + inferredJwtIssuerConfig - Configuration for JWT token propagation - # + ldapAuthProvider - LDAP auth store provider - public function __init(InferredJwtIssuerConfig inferredJwtIssuerConfig, - LdapAuthStoreProvider ldapAuthProvider) { - self.inferredJwtIssuerConfig = inferredJwtIssuerConfig; - self.ldapAuthProvider = ldapAuthProvider; - } - - # Authenticate with username and password using LDAP user store - # - # + username - user name - # + password - password - # + return - true if authentication is a success, else false - public function authenticate(string username, string password) returns boolean { - boolean isAuthenticated = self.ldapAuthProvider.authenticate(username, password); - if (isAuthenticated){ - setAuthToken(username, self.inferredJwtIssuerConfig); - } - return isAuthenticated; - } - - # Reads the scope(s) for the user with the given username from LDAP user store - # - # + username - user name - # + return - array of groups for the user denoted by the username - public function getScopes(string username) returns string[] { - return self.ldapAuthProvider.getScopes(username); - } -}; diff --git a/stdlib/http/src/main/ballerina/http/auth/authz_filter.bal b/stdlib/http/src/main/ballerina/http/auth/authz_filter.bal index cdc9b11d6a6a..f835e0c3e9c4 100644 --- a/stdlib/http/src/main/ballerina/http/auth/authz_filter.bal +++ b/stdlib/http/src/main/ballerina/http/auth/authz_filter.bal @@ -50,11 +50,16 @@ public type AuthzFilter object { string[]? scopes = getScopesForResource(resourceLevelAuthAnn, serviceLevelAuthAnn); boolean authorized; if (scopes is string[]) { - if (self.authzHandler.canHandle(request)) { - authorized = self.authzHandler.handle(runtime:getInvocationContext().principal.username, - context.serviceName, context.resourceName, request.method, scopes); + if (scopes.length() > 0) { + if (self.authzHandler.canHandle(request)) { + authorized = self.authzHandler.handle(runtime:getInvocationContext().principal.username, + context.serviceName, context.resourceName, request.method, scopes); + } else { + authorized = false; + } } else { - authorized = false; + // scopes are not defined, no need to authorize + authorized = true; } } else { // scopes are not defined, no need to authorize diff --git a/stdlib/http/src/main/ballerina/http/client_endpoint.bal b/stdlib/http/src/main/ballerina/http/client_endpoint.bal index a69e3e012ada..eb6e981cd93e 100644 --- a/stdlib/http/src/main/ballerina/http/client_endpoint.bal +++ b/stdlib/http/src/main/ballerina/http/client_endpoint.bal @@ -336,11 +336,14 @@ public type ConnectionThrottling record { # # + scheme - Authentication scheme # + basicAuthConfig - Configuration for BasicAuth scheme -# + oAuth2Config - Configuration for OAuth2 scheme +# + oAuth2AuthConfig - Configuration for OAuth2 scheme +# + jwtAuthConfig - Configuration for JWT scheme. If inbound authentication is JWT, sends the same JWT with client +# invocation, unless reissuing is configured using InferredJwtIssuerConfig. public type AuthConfig record { OutboundAuthScheme scheme; BasicAuthConfig basicAuthConfig?; - OAuth2AuthConfig oAuth2Config?; + OAuth2AuthConfig oAuth2AuthConfig?; + JwtAuthConfig jwtAuthConfig?; !...; }; @@ -380,6 +383,13 @@ public type OAuth2AuthConfig record { !...; }; +# JwtAuthConfig record can be used to configure JWT based authentication used by the HTTP endpoint. +# +# + inferredJwtIssuerConfig - JWT issuer configuration used to issue JWT with specific configuration +public type JwtAuthConfig record { + auth:InferredJwtIssuerConfig inferredJwtIssuerConfig?; +}; + function initialize(string serviceUrl, ClientEndpointConfig config) returns Client|error { boolean httpClientRequired = false; string url = serviceUrl; diff --git a/stdlib/http/src/main/ballerina/http/http_secure_client.bal b/stdlib/http/src/main/ballerina/http/http_secure_client.bal index 76f8f1de6d4f..e7a7e24945de 100644 --- a/stdlib/http/src/main/ballerina/http/http_secure_client.bal +++ b/stdlib/http/src/main/ballerina/http/http_secure_client.bal @@ -315,12 +315,12 @@ function generateSecureRequest(Request req, ClientEndpointConfig config) returns req.setHeader(AUTH_HEADER, AUTH_SCHEME_BASIC + WHITE_SPACE + token); } } else if (auth.scheme == OAUTH2) { - var oAuth2Config = auth["oAuth2Config"]; - if (oAuth2Config is ()) { + var oAuth2AuthConfig = auth["oAuth2AuthConfig"]; + if (oAuth2AuthConfig is ()) { error e = error("OAuth2 config not provided"); panic e; } else { - string accessToken = oAuth2Config.accessToken; + string accessToken = oAuth2AuthConfig.accessToken; if (accessToken == EMPTY_STRING) { return updateRequestAndConfig(req, config); } else { @@ -328,9 +328,38 @@ function generateSecureRequest(Request req, ClientEndpointConfig config) returns } } } else if (auth.scheme == JWT_AUTH) { - string authToken = runtime:getInvocationContext().authenticationContext.authToken; + var jwtAuthConfig = auth["jwtAuthConfig"]; + string authToken = EMPTY_STRING; + if (jwtAuthConfig is ()) { + authToken = runtime:getInvocationContext().authenticationContext.authToken; + } else { + var jwtIssuerConfig = jwtAuthConfig["inferredJwtIssuerConfig"]; + if (jwtIssuerConfig is ()) { + authToken = runtime:getInvocationContext().authenticationContext.authToken; + } else { + auth:JwtHeader header = { alg: jwtIssuerConfig.signingAlg, typ: "JWT" }; + auth:JwtPayload payload = { + sub: runtime:getInvocationContext().principal.username, + iss: jwtIssuerConfig.issuer, + exp: time:currentTime().time / 1000 + jwtIssuerConfig.expTime, + iat: time:currentTime().time / 1000, + nbf: time:currentTime().time / 1000, + jti: system:uuid(), + aud: jwtIssuerConfig.audience + }; + var token = auth:issueJwt(header, payload, jwtIssuerConfig.keyStore, jwtIssuerConfig.keyAlias, + jwtIssuerConfig.keyPassword); + // TODO: cache the token per-user per-client and reuse it + if (token is string) { + authToken = token; + } else { + return token; + } + } + } if (authToken == EMPTY_STRING) { - error err = error(HTTP_ERROR_CODE, { message: "Authentication token is not set at invocation context" }); + error err = error(HTTP_ERROR_CODE, { message: "JWT was not used during inbound authentication. + Provide InferredJwtIssuerConfig to issue new token." }); return err; } req.setHeader(AUTH_HEADER, AUTH_SCHEME_BEARER + WHITE_SPACE + authToken); @@ -350,7 +379,7 @@ function generateSecureRequest(Request req, ClientEndpointConfig config) returns function updateRequestAndConfig(Request req, ClientEndpointConfig config) returns ()|error { string accessToken = check getAccessTokenFromRefreshToken(config); req.setHeader(AUTH_HEADER, AUTH_SCHEME_BEARER + WHITE_SPACE + accessToken); - OAuth2AuthConfig? authConfig = config.auth.oAuth2Config; + OAuth2AuthConfig? authConfig = config.auth.oAuth2AuthConfig; if (authConfig is OAuth2AuthConfig) { authConfig.accessToken = accessToken; } @@ -363,13 +392,13 @@ function updateRequestAndConfig(Request req, ClientEndpointConfig config) return # + return - AccessToken received from the authorization server or `error` if error occured during HTTP client invocation function getAccessTokenFromRefreshToken(ClientEndpointConfig config) returns string|error { Client refreshTokenClient; - var oAuth2Config = config.auth.oAuth2Config; - if (oAuth2Config is OAuth2AuthConfig) { - string refreshToken = oAuth2Config.refreshToken; - string clientId = oAuth2Config.clientId; - string clientSecret = oAuth2Config.clientSecret; - string refreshUrl = oAuth2Config.refreshUrl; - string[] scopes = oAuth2Config.scopes; + var oAuth2AuthConfig = config.auth.oAuth2AuthConfig; + if (oAuth2AuthConfig is OAuth2AuthConfig) { + string refreshToken = oAuth2AuthConfig.refreshToken; + string clientId = oAuth2AuthConfig.clientId; + string clientSecret = oAuth2AuthConfig.clientSecret; + string refreshUrl = oAuth2AuthConfig.refreshUrl; + string[] scopes = oAuth2AuthConfig.scopes; if (refreshToken == EMPTY_STRING || clientId == EMPTY_STRING || clientSecret == EMPTY_STRING || refreshUrl == EMPTY_STRING) { @@ -391,7 +420,7 @@ function getAccessTokenFromRefreshToken(ClientEndpointConfig config) returns str if (scopeString != EMPTY_STRING) { textPayload = textPayload + "&scope=" + scopeString.trim(); } - if (oAuth2Config.credentialBearer == AUTH_HEADER_BEARER) { + if (oAuth2AuthConfig.credentialBearer == AUTH_HEADER_BEARER) { string clientIdSecret = clientId + ":" + clientSecret; refreshTokenRequest.addHeader(AUTH_HEADER, AUTH_SCHEME_BASIC + WHITE_SPACE + encoding:encodeBase64(clientIdSecret.toByteArray("UTF-8"))); diff --git a/stdlib/runtime/src/main/ballerina/runtime/invocation-context.bal b/stdlib/runtime/src/main/ballerina/runtime/invocation-context.bal index 880a036d192f..2637d8da45c8 100644 --- a/stdlib/runtime/src/main/ballerina/runtime/invocation-context.bal +++ b/stdlib/runtime/src/main/ballerina/runtime/invocation-context.bal @@ -22,8 +22,8 @@ # + attributes - Context attributes. public type InvocationContext record { string id; - Principal principal; - AuthenticationContext authenticationContext; + Principal principal?; + AuthenticationContext authenticationContext?; map attributes; !...; }; diff --git a/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/AuthBaseTest.java b/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/AuthBaseTest.java index 249c2cef9a44..3b3e07a5833d 100644 --- a/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/AuthBaseTest.java +++ b/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/AuthBaseTest.java @@ -37,7 +37,7 @@ public class AuthBaseTest extends BaseTest { @BeforeGroups(value = "auth-test", alwaysRun = true) public void start() throws Exception { int[] requiredPorts = new int[]{9090, 9091, 9092, 9093, 9094, 9095, 9096, 9097, 9098, 9099, 9100, 9101, 9102, - 9190, 9191, 9192, 9193, 9194}; + 9103, 9190, 9191, 9192, 9193, 9194}; embeddedDirectoryServer = new EmbeddedDirectoryServer(); embeddedDirectoryServer.startLdapServer(9389); diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authclients/oauth-client.bal b/tests/ballerina-integration-test/src/test/resources/auth/authclients/oauth-client.bal index 880936da11ac..e0b0e8557e9e 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authclients/oauth-client.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authclients/oauth-client.bal @@ -19,7 +19,7 @@ import ballerina/http; http:Client clientEP1 = new("https://localhost:9095/foo", config = { auth: { scheme: http:OAUTH2, - oAuth2Config: { + oAuth2AuthConfig: { refreshToken: "5Aep861..zRMyCurAUgnwQaEjnCVqxK2utna7Mm4nb9UamD7BW50R2huecjSaLlv5mT1z_TViZ", clientId: "3MVG9YDQS5WtC11paU2WcQjBB3L5w4gz52uriT8ksZ3nUVjKvrfQMrU4uvZohTftxStwNEW4cfStBEGRxRL68", clientSecret: "9205371918321623741", @@ -32,7 +32,7 @@ http:Client clientEP1 = new("https://localhost:9095/foo", config = { http:Client clientEP2 = new("https://localhost:9095/foo", config = { auth: { scheme: http:OAUTH2, - oAuth2Config: { + oAuth2AuthConfig: { refreshToken: "5Aep861..zRMyCurAUgnwQaEjnCVqxK2utna7Mm4nb9UamD7BW50R2huecjSaLlv5mT1z_TViZ", clientId: "3MVG9YDQS5WtC11paU2WcQjBB3L5w4gz52uriT8ksZ3nUVjKvrfQMrU4uvZohTftxStwNEW4cfStBEGRxRL68", clientSecret: "9205371918321623741", @@ -45,7 +45,7 @@ http:Client clientEP2 = new("https://localhost:9095/foo", config = { http:Client clientEP3 = new("https://localhost:9095/foo", config = { auth: { scheme: http:OAUTH2, - oAuth2Config: { + oAuth2AuthConfig: { refreshToken: "5Aep861..zRMyCurAUgnwQaEjnCVqxK2utna7Mm4nb9UamD7BW50R2huecjSaLlv5mT1z_TViZ", clientId: "3MVG9YDQS5WtC11paU2WcQjBB3L5w4gz52uriT8ksZ3nUVjKvrfQMrU4uvZohTftxStwNEW4cfStBEGRxRL68", clientSecret: "9205371918321623741", @@ -57,7 +57,7 @@ http:Client clientEP3 = new("https://localhost:9095/foo", config = { http:Client clientEP4 = new("https://localhost:9095/foo", config = { auth: { scheme: http:OAUTH2, - oAuth2Config : { + oAuth2AuthConfig : { refreshToken: "5Aep861..zRMyCurAUgnwQaEjnCVqxK2utna7Mm4nb9UamD7BW50R2huecjSaLlv5mT1z_TViZ", clientId: "3MVG9YDQS5WtC11paU2WcQjBB3L5w4gz52uriT8ksZ3nUVjKvrfQMrU4uvZohTftxStwNEW4cfStBEGRxRL68", clientSecret: "9205371918321623741", @@ -70,7 +70,7 @@ http:Client clientEP4 = new("https://localhost:9095/foo", config = { http:Client clientEP5 = new("https://localhost:9095/foo", config = { auth: { scheme: http:OAUTH2, - oAuth2Config: { + oAuth2AuthConfig: { refreshToken: "5Aep861..zRMyCurAUgnwQaEjnCVqxK2utna7Mm4nb9UamD7BW50R2huecjSaLlv5mT1z_TViZ", clientId: "3MVG9YDQS5WtC11paU2WcQjBB3L5w4gz52uriT8ksZ3nUVjKvrfQMrU4uvZohTftxStwNEW4cfStBEGRxRL68", clientSecret: "9205371918321623741", diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/15_jwt_authenticated_jwt_client_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/15_jwt_authenticated_jwt_client_test.bal new file mode 100644 index 000000000000..392a31f745c4 --- /dev/null +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/15_jwt_authenticated_jwt_client_test.bal @@ -0,0 +1,55 @@ +// Copyright (c) 2019 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. + +import ballerina/http; + +http:AuthProvider jwtAuthProvider15 = { + scheme: http:JWT_AUTH, + jwtAuthProviderConfig: { + issuer:"ballerina", + audience: "ballerina.io", + certificateAlias: "cert", + validateCertificate: false, + trustStore: { + path: "../../../src/test/resources/auth/testtruststore.p12", + password: "ballerina" + } + } +}; + +listener http:Listener listener15 = new(9103, config = { + authProviders:[jwtAuthProvider15], + secureSocket: { + keyStore: { + path: "${ballerina.home}/bre/security/ballerinaKeystore.p12", + password: "ballerina" + } + } +}); + +http:Client clientEPJwt1 = new("https://localhost:9095/foo", config = { + auth: { + scheme: http:JWT_AUTH + } +}); + +service echo15 on listener15 { + + resource function test15 (http:Caller caller, http:Request req) { + var resp = clientEPJwt1->get("/bar"); + _ = caller -> respond(()); + } +} From 9730d71c82664da4b3fb0539cc9cab6b5501738f Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Wed, 13 Feb 2019 11:17:58 +0530 Subject: [PATCH 14/26] Remove Jwt supported auth provider --- .../auth/config_auth_store_provider.bal | 13 ++++++- .../auth/ldap_auth_store_provider.bal | 2 - .../test-src/config_auth_provider_test.bal | 12 +++--- .../main/ballerina/http/service_endpoint.bal | 39 +++++++------------ .../auth/authn-handler-chain-test.bal | 2 +- .../test-src/auth/authn-handler-test.bal | 8 ++-- 6 files changed, 35 insertions(+), 41 deletions(-) diff --git a/stdlib/auth/src/main/ballerina/auth/config_auth_store_provider.bal b/stdlib/auth/src/main/ballerina/auth/config_auth_store_provider.bal index 4a24708797ce..6582429a75ca 100644 --- a/stdlib/auth/src/main/ballerina/auth/config_auth_store_provider.bal +++ b/stdlib/auth/src/main/ballerina/auth/config_auth_store_provider.bal @@ -21,15 +21,24 @@ const string CONFIG_USER_SECTION = "b7a.users"; # Represents configurations that required for Config auth store. # -# + inferredJwtIssuerConfig - Inferred JWT issuer configuration used to generate JWT for client invocations public type ConfigAuthProviderConfig record { - InferredJwtIssuerConfig? inferredJwtIssuerConfig = (); !...; }; # Represents Ballerina configuration file based auth store provider +# +# + configAuthProviderConfig - Config auth store configurations public type ConfigAuthStoreProvider object { + public ConfigAuthProviderConfig configAuthProviderConfig; + + # Create an Config auth store with the given configurations. + # + # + configAuthProviderConfig - Config auth store configurations + public function __init(ConfigAuthProviderConfig configAuthProviderConfig) { + self.configAuthProviderConfig = configAuthProviderConfig; + } + # Attempts to authenticate with username and password # # + user - user name diff --git a/stdlib/auth/src/main/ballerina/auth/ldap_auth_store_provider.bal b/stdlib/auth/src/main/ballerina/auth/ldap_auth_store_provider.bal index 4caa2c093227..42fb1b72b83d 100644 --- a/stdlib/auth/src/main/ballerina/auth/ldap_auth_store_provider.bal +++ b/stdlib/auth/src/main/ballerina/auth/ldap_auth_store_provider.bal @@ -41,7 +41,6 @@ import ballerina/runtime; # + retryAttempts - Retry the authentication request if a timeout happened # + secureClientSocket - The SSL configurations for the ldap client socket. This needs to be configured in order to # communicate through ldaps. -# + inferredJwtIssuerConfig - Inferred JWT issuer configuration used to generate JWT for client invocations public type LdapAuthProviderConfig record { string domainName; string connectionURL; @@ -64,7 +63,6 @@ public type LdapAuthProviderConfig record { int readTimeout = 60000; int retryAttempts = 0; SecureClientSocket? secureClientSocket = (); - InferredJwtIssuerConfig? inferredJwtIssuerConfig = (); !...; }; diff --git a/stdlib/auth/src/test/resources/test-src/config_auth_provider_test.bal b/stdlib/auth/src/test/resources/test-src/config_auth_provider_test.bal index 7f9d8fd64e1f..65cc7dc94ac4 100644 --- a/stdlib/auth/src/test/resources/test-src/config_auth_provider_test.bal +++ b/stdlib/auth/src/test/resources/test-src/config_auth_provider_test.bal @@ -1,31 +1,31 @@ import ballerina/auth; function testCreateConfigAuthProvider() returns (auth:ConfigAuthStoreProvider) { - auth:ConfigAuthStoreProvider configAuthStoreProvider = new; + auth:ConfigAuthStoreProvider configAuthStoreProvider = new({}); return configAuthStoreProvider; } function testAuthenticationOfNonExistingUser() returns (boolean) { - auth:ConfigAuthStoreProvider configAuthStoreProvider = new; + auth:ConfigAuthStoreProvider configAuthStoreProvider = new({}); return configAuthStoreProvider.authenticate("amila", "abc"); } function testAuthenticationOfNonExistingPassword() returns (boolean) { - auth:ConfigAuthStoreProvider configAuthStoreProvider = new; + auth:ConfigAuthStoreProvider configAuthStoreProvider = new({}); return configAuthStoreProvider.authenticate("isuru", "xxy"); } function testAuthentication() returns (boolean) { - auth:ConfigAuthStoreProvider configAuthStoreProvider = new; + auth:ConfigAuthStoreProvider configAuthStoreProvider = new({}); return configAuthStoreProvider.authenticate("isuru", "xxx"); } function testReadScopesOfNonExistingUser() returns (string[]) { - auth:ConfigAuthStoreProvider configAuthStoreProvider = new; + auth:ConfigAuthStoreProvider configAuthStoreProvider = new({}); return configAuthStoreProvider.getScopes("amila"); } function testReadScopesOfUser() returns (string[]) { - auth:ConfigAuthStoreProvider configAuthStoreProvider = new; + auth:ConfigAuthStoreProvider configAuthStoreProvider = new({}); return configAuthStoreProvider.getScopes("ishara"); } diff --git a/stdlib/http/src/main/ballerina/http/service_endpoint.bal b/stdlib/http/src/main/ballerina/http/service_endpoint.bal index d85de6e5b45d..db434bd7c435 100644 --- a/stdlib/http/src/main/ballerina/http/service_endpoint.bal +++ b/stdlib/http/src/main/ballerina/http/service_endpoint.bal @@ -307,7 +307,13 @@ function createAuthFiltersForSecureListener(ServiceEndpointConfiguration config, panic e; } } else if (provider.authStoreProvider == CONFIG_AUTH_STORE) { - auth:ConfigAuthStoreProvider configAuthStoreProvider = new; + var configAuthProviderConfig = provider.configAuthProviderConfig; + auth:ConfigAuthStoreProvider configAuthStoreProvider; + if (configAuthProviderConfig is auth:ConfigAuthProviderConfig) { + configAuthStoreProvider = new(configAuthProviderConfig); + } else { + configAuthStoreProvider = new({}); + } authStoreProvider = configAuthStoreProvider; } else { error configError = error("Unsupported auth store provider"); @@ -323,42 +329,23 @@ HttpAuthzHandler authzHandler = new(authStoreProvider, positiveAuthzCache, negat return authFilters; } -function createBasicAuthHandler() returns HttpAuthnHandler { - auth:ConfigAuthStoreProvider configAuthStoreProvider = new; - auth:AuthStoreProvider authStoreProvider = configAuthStoreProvider; - HttpBasicAuthnHandler basicAuthHandler = new(authStoreProvider); - return basicAuthHandler; -} - function createAuthHandler(AuthProvider authProvider, string instanceId) returns HttpAuthnHandler { if (authProvider.scheme == BASIC_AUTH) { auth:AuthStoreProvider authStoreProvider = new; if (authProvider.authStoreProvider == CONFIG_AUTH_STORE) { var configAuthProviderConfig = authProvider.configAuthProviderConfig; - boolean authStoreProviderInitialized = false; + auth:ConfigAuthStoreProvider configAuthStoreProvider; if (configAuthProviderConfig is auth:ConfigAuthProviderConfig) { - var inferredJwtIssuerConfig = configAuthProviderConfig.inferredJwtIssuerConfig; - if (inferredJwtIssuerConfig is auth:InferredJwtIssuerConfig) { - auth:ConfigJwtAuthProvider configAuthProvider = new(inferredJwtIssuerConfig); - authStoreProvider = configAuthProvider; - authStoreProviderInitialized = true; - } - } - if (!authStoreProviderInitialized) { - auth:ConfigAuthStoreProvider configAuthStoreProvider = new; - authStoreProvider = configAuthStoreProvider; + configAuthStoreProvider = new(configAuthProviderConfig); + } else { + configAuthStoreProvider = new({}); } + authStoreProvider = configAuthStoreProvider; } else if (authProvider.authStoreProvider == LDAP_AUTH_STORE) { var ldapAuthProviderConfig = authProvider.ldapAuthProviderConfig; if (ldapAuthProviderConfig is auth:LdapAuthProviderConfig) { auth:LdapAuthStoreProvider ldapAuthStoreProvider = new(ldapAuthProviderConfig, instanceId); - var inferredJwtIssuerConfig = ldapAuthProviderConfig.inferredJwtIssuerConfig; - if (inferredJwtIssuerConfig is auth:InferredJwtIssuerConfig) { - auth:LdapJwtAuthProvider ldapAuthProvider = new(inferredJwtIssuerConfig, ldapAuthStoreProvider); - authStoreProvider = ldapAuthProvider; - } else { - authStoreProvider = ldapAuthStoreProvider; - } + authStoreProvider = ldapAuthStoreProvider; } else { error e = error("LDAP auth provider config not provided"); panic e; diff --git a/stdlib/http/src/test/resources/test-src/auth/authn-handler-chain-test.bal b/stdlib/http/src/test/resources/test-src/auth/authn-handler-chain-test.bal index 08152c3a187f..085a929f9f3e 100644 --- a/stdlib/http/src/test/resources/test-src/auth/authn-handler-chain-test.bal +++ b/stdlib/http/src/test/resources/test-src/auth/authn-handler-chain-test.bal @@ -60,7 +60,7 @@ function createRequest() returns (http:Request) { } function createBasicAuthnHandler() returns (http:HttpAuthnHandler) { - auth:ConfigAuthStoreProvider configAuthStoreProvider = new(); + auth:ConfigAuthStoreProvider configAuthStoreProvider = new({}); auth:AuthStoreProvider authStoreProvider = configAuthStoreProvider; http:HttpBasicAuthnHandler basicAuthnHandler = new(authStoreProvider); return basicAuthnHandler; diff --git a/stdlib/http/src/test/resources/test-src/auth/authn-handler-test.bal b/stdlib/http/src/test/resources/test-src/auth/authn-handler-test.bal index 6e7aa747c13e..fded7d7fcf85 100644 --- a/stdlib/http/src/test/resources/test-src/auth/authn-handler-test.bal +++ b/stdlib/http/src/test/resources/test-src/auth/authn-handler-test.bal @@ -2,7 +2,7 @@ import ballerina/auth; import ballerina/http; function testCanHandleHttpBasicAuthWithoutHeader() returns (boolean) { - auth:ConfigAuthStoreProvider configAuthStoreProvider = new; + auth:ConfigAuthStoreProvider configAuthStoreProvider = new({}); auth:AuthStoreProvider authStoreProvider = configAuthStoreProvider; http:HttpBasicAuthnHandler handler = new(authStoreProvider); http:Request inRequest = createRequest(); @@ -12,7 +12,7 @@ function testCanHandleHttpBasicAuthWithoutHeader() returns (boolean) { } function testCanHandleHttpBasicAuth() returns (boolean) { - auth:ConfigAuthStoreProvider configAuthStoreProvider = new; + auth:ConfigAuthStoreProvider configAuthStoreProvider = new({}); auth:AuthStoreProvider authStoreProvider = configAuthStoreProvider; http:HttpBasicAuthnHandler handler = new(authStoreProvider); http:Request inRequest = createRequest(); @@ -22,7 +22,7 @@ function testCanHandleHttpBasicAuth() returns (boolean) { } function testHandleHttpBasicAuthFailure() returns (boolean) { - auth:ConfigAuthStoreProvider configAuthStoreProvider = new; + auth:ConfigAuthStoreProvider configAuthStoreProvider = new({}); auth:AuthStoreProvider authStoreProvider = configAuthStoreProvider; http:HttpBasicAuthnHandler handler = new(authStoreProvider); http:Request inRequest = createRequest(); @@ -32,7 +32,7 @@ function testHandleHttpBasicAuthFailure() returns (boolean) { } function testHandleHttpBasicAuth() returns (boolean) { - auth:ConfigAuthStoreProvider configAuthStoreProvider = new; + auth:ConfigAuthStoreProvider configAuthStoreProvider = new({}); auth:AuthStoreProvider authStoreProvider = configAuthStoreProvider; http:HttpBasicAuthnHandler handler = new(authStoreProvider); http:Request inRequest = createRequest(); From 17b10b71ec812b48c46fbf1aab914d465fec552a Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Wed, 13 Feb 2019 12:53:20 +0530 Subject: [PATCH 15/26] Add and reorganize jwt auth integration tests --- .../ballerinalang/test/auth/AuthBaseTest.java | 2 +- .../test/auth/NoTokenPropagationTest.java | 45 ------- .../test/auth/TokenPropagationTest.java | 66 +++++++++- ...config_inheritance_auth_disabling_test.bal | 16 +++ .../02_authn_config_inheritance_test.bal | 16 +++ .../03_authz_config_inheritance_test.bal | 16 +++ .../04_resource_level_auth_config_test.bal | 16 +++ .../05_service_level_auth_config_test.bal | 16 +++ ...=> 10_token_propagation_disabled_test.bal} | 23 +++- ... 11_token_propagation_basic_auth_test.bal} | 54 +++++--- ..._secure_service_wrong_provider_id_test.bal | 16 +++ .../15_jwt_authenticated_jwt_client_test.bal | 55 --------- .../15_token_propagation_jwt_test.bal | 103 ++++++++++++++++ ...6_token_propagation_jwt_reissuing_test.bal | 115 ++++++++++++++++++ ...ropagation_jwt_reissuing_negative_test.bal | 115 ++++++++++++++++++ 15 files changed, 552 insertions(+), 122 deletions(-) delete mode 100644 tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/NoTokenPropagationTest.java rename tests/ballerina-integration-test/src/test/resources/auth/authservices/{10_secure_service_no_token_propagation_test.bal => 10_token_propagation_disabled_test.bal} (71%) rename tests/ballerina-integration-test/src/test/resources/auth/authservices/{11_secure_service_token_propagation_test.bal => 11_token_propagation_basic_auth_test.bal} (58%) delete mode 100644 tests/ballerina-integration-test/src/test/resources/auth/authservices/15_jwt_authenticated_jwt_client_test.bal create mode 100644 tests/ballerina-integration-test/src/test/resources/auth/authservices/15_token_propagation_jwt_test.bal create mode 100644 tests/ballerina-integration-test/src/test/resources/auth/authservices/16_token_propagation_jwt_reissuing_test.bal create mode 100644 tests/ballerina-integration-test/src/test/resources/auth/authservices/17_token_propagation_jwt_reissuing_negative_test.bal diff --git a/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/AuthBaseTest.java b/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/AuthBaseTest.java index 3b3e07a5833d..d2275170d3ef 100644 --- a/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/AuthBaseTest.java +++ b/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/AuthBaseTest.java @@ -37,7 +37,7 @@ public class AuthBaseTest extends BaseTest { @BeforeGroups(value = "auth-test", alwaysRun = true) public void start() throws Exception { int[] requiredPorts = new int[]{9090, 9091, 9092, 9093, 9094, 9095, 9096, 9097, 9098, 9099, 9100, 9101, 9102, - 9103, 9190, 9191, 9192, 9193, 9194}; + 9103, 9104, 9105, 9106, 9107, 9108, 9190, 9191, 9192, 9193, 9194}; embeddedDirectoryServer = new EmbeddedDirectoryServer(); embeddedDirectoryServer.startLdapServer(9389); diff --git a/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/NoTokenPropagationTest.java b/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/NoTokenPropagationTest.java deleted file mode 100644 index e25a9be03520..000000000000 --- a/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/NoTokenPropagationTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2018, 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.ballerinalang.test.auth; - -import org.ballerinalang.test.util.HttpResponse; -import org.ballerinalang.test.util.HttpsClientRequest; -import org.testng.Assert; -import org.testng.annotations.Test; - -import java.util.HashMap; -import java.util.Map; - -/** - * Test cases for verifying no token propagation scenario. - */ -@Test(groups = "auth-test") -public class NoTokenPropagationTest extends AuthBaseTest { - - @Test(description = "No JWT Token propagation, authn failure test") - public void testTokenPropagationSuccess() throws Exception { - Map headers = new HashMap<>(); - headers.put("Authorization", "Basic aXN1cnU6eHh4"); - HttpResponse response = HttpsClientRequest.doGet(serverInstance.getServiceURLHttps(9190, "passthrough"), - headers, serverInstance.getServerHome()); - Assert.assertNotNull(response); - Assert.assertEquals(response.getResponseCode(), 401, "Response code mismatched"); - } -} diff --git a/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/TokenPropagationTest.java b/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/TokenPropagationTest.java index f37d082c2f79..47031054df05 100644 --- a/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/TokenPropagationTest.java +++ b/tests/ballerina-integration-test/src/test/java/org/ballerinalang/test/auth/TokenPropagationTest.java @@ -16,7 +16,6 @@ * under the License. */ - package org.ballerinalang.test.auth; import org.ballerinalang.test.util.HttpResponse; @@ -33,8 +32,8 @@ @Test(groups = "auth-test") public class TokenPropagationTest extends AuthBaseTest { - @Test(description = "With JWT Token propagation, authn success test") - public void testTokenPropagationSuccess() throws Exception { + @Test(description = "Test JWT Token propagation with basic auth as the inbound authentication mechanism") + public void testTokenPropagationWithBasicAuthInbound() throws Exception { Map headers = new HashMap<>(); headers.put("Authorization", "Basic aXN1cnU6eHh4"); HttpResponse response = HttpsClientRequest.doGet(serverInstance.getServiceURLHttps(9192, "passthrough"), @@ -42,4 +41,65 @@ public void testTokenPropagationSuccess() throws Exception { Assert.assertNotNull(response); Assert.assertEquals(response.getResponseCode(), 200, "Response code mismatched"); } + + @Test(description = "Test behaviour when JWT Token propagation is disabled, resulting in authn failure") + public void testWithoutTokenPropagation() throws Exception { + Map headers = new HashMap<>(); + headers.put("Authorization", "Basic aXN1cnU6eHh4"); + HttpResponse response = HttpsClientRequest.doGet(serverInstance.getServiceURLHttps(9190, "passthrough"), + headers, serverInstance.getServerHome()); + Assert.assertNotNull(response); + Assert.assertEquals(response.getResponseCode(), 401, "Response code mismatched"); + } + + @Test(description = "Test JWT Token propagation with JWT auth as the inbound authentication mechanism, without " + + "token reissuing") + public void testTokenPropagationWithJwtAuthInbound() throws Exception { + Map headers = new HashMap<>(); + headers.put("Authorization", "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiYWxsZXJpbmEiLCJpc3M" + + "iOiJleGFtcGxlMSIsImV4cCI6MjgxODQxNTAxOSwiaWF0IjoxNTI0NTc1MDE5LCJqdGkiOiJmNWFkZWQ1MDU4NWM0NmYyYjh" + + "jYTIzM2QwYzJhM2M5ZCIsImF1ZCI6WyJiYWxsZXJpbmEiLCJiYWxsZXJpbmEub3JnIiwiYmFsbGVyaW5hLmlvIl0sInNjb3B" + + "lIjoiaGVsbG8ifQ.W_lvdp_o3o7MBVWeumg2fvIVSFWoGl9OFv2qyAz_g2afJwUWrFYOUd-1rj9lebZrQzqTd6RRX65MVF3G" + + "ksSeIT9McZxjPiSX1FR-nIUTcJ9anaoQVEKo3OpkIPzd_4_95CpHXF1MaW18ww5h_NShQnUrN7myrBfc-UbHsqC1YEBAM2M-" + + "3NMs8jjgcZHfZ1JjomZCjd5eUXz8R5Vl46uAlSbFAmxAfY1T-31qUB93eCL2iJfDc70OK2txohryntw9h-OePwQULJN0Eiwp" + + "oI60HQFFlgC1g_crPIDakBTiEITrbO3OzrNeCQFBN-Ji4BTXq97TulCIRNneDLCUBSRE1A"); + HttpResponse response = HttpsClientRequest.doGet(serverInstance.getServiceURLHttps(9103, "passthrough"), + headers, serverInstance.getServerHome()); + Assert.assertNotNull(response); + Assert.assertEquals(response.getResponseCode(), 200, "Response code mismatched"); + } + + @Test(description = "Test JWT Token propagation with JWT auth as the inbound authentication mechanism, with " + + "token reissuing") + public void testTokenPropagationWithJwtAuthInboundAndTokenReissuing() throws Exception { + Map headers = new HashMap<>(); + headers.put("Authorization", "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiYWxsZXJpbmEiLCJpc3M" + + "iOiJleGFtcGxlMSIsImV4cCI6MjgxODQxNTAxOSwiaWF0IjoxNTI0NTc1MDE5LCJqdGkiOiJmNWFkZWQ1MDU4NWM0NmYyYjh" + + "jYTIzM2QwYzJhM2M5ZCIsImF1ZCI6WyJiYWxsZXJpbmEiLCJiYWxsZXJpbmEub3JnIiwiYmFsbGVyaW5hLmlvIl0sInNjb3B" + + "lIjoiaGVsbG8ifQ.W_lvdp_o3o7MBVWeumg2fvIVSFWoGl9OFv2qyAz_g2afJwUWrFYOUd-1rj9lebZrQzqTd6RRX65MVF3G" + + "ksSeIT9McZxjPiSX1FR-nIUTcJ9anaoQVEKo3OpkIPzd_4_95CpHXF1MaW18ww5h_NShQnUrN7myrBfc-UbHsqC1YEBAM2M-" + + "3NMs8jjgcZHfZ1JjomZCjd5eUXz8R5Vl46uAlSbFAmxAfY1T-31qUB93eCL2iJfDc70OK2txohryntw9h-OePwQULJN0Eiwp" + + "oI60HQFFlgC1g_crPIDakBTiEITrbO3OzrNeCQFBN-Ji4BTXq97TulCIRNneDLCUBSRE1A"); + HttpResponse response = HttpsClientRequest.doGet(serverInstance.getServiceURLHttps(9105, "passthrough"), + headers, serverInstance.getServerHome()); + Assert.assertNotNull(response); + Assert.assertEquals(response.getResponseCode(), 200, "Response code mismatched"); + } + + @Test(description = "Negative test for JWT Token propagation with JWT auth as the inbound authentication " + + "mechanism, with token reissuing. Newly issued token's issuer is not what is expected.") + public void testTokenPropagationWithJwtAuthInboundAndTokenReissuingNegative() throws Exception { + Map headers = new HashMap<>(); + headers.put("Authorization", "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJiYWxsZXJpbmEiLCJpc3M" + + "iOiJleGFtcGxlMSIsImV4cCI6MjgxODQxNTAxOSwiaWF0IjoxNTI0NTc1MDE5LCJqdGkiOiJmNWFkZWQ1MDU4NWM0NmYyYjh" + + "jYTIzM2QwYzJhM2M5ZCIsImF1ZCI6WyJiYWxsZXJpbmEiLCJiYWxsZXJpbmEub3JnIiwiYmFsbGVyaW5hLmlvIl0sInNjb3B" + + "lIjoiaGVsbG8ifQ.W_lvdp_o3o7MBVWeumg2fvIVSFWoGl9OFv2qyAz_g2afJwUWrFYOUd-1rj9lebZrQzqTd6RRX65MVF3G" + + "ksSeIT9McZxjPiSX1FR-nIUTcJ9anaoQVEKo3OpkIPzd_4_95CpHXF1MaW18ww5h_NShQnUrN7myrBfc-UbHsqC1YEBAM2M-" + + "3NMs8jjgcZHfZ1JjomZCjd5eUXz8R5Vl46uAlSbFAmxAfY1T-31qUB93eCL2iJfDc70OK2txohryntw9h-OePwQULJN0Eiwp" + + "oI60HQFFlgC1g_crPIDakBTiEITrbO3OzrNeCQFBN-Ji4BTXq97TulCIRNneDLCUBSRE1A"); + HttpResponse response = HttpsClientRequest.doGet(serverInstance.getServiceURLHttps(9107, "passthrough"), + headers, serverInstance.getServerHome()); + Assert.assertNotNull(response); + Assert.assertEquals(response.getResponseCode(), 401, "Response code mismatched"); + } } diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/01_authn_config_inheritance_auth_disabling_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/01_authn_config_inheritance_auth_disabling_test.bal index 393e935afbe6..57f85092fd0e 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/01_authn_config_inheritance_auth_disabling_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/01_authn_config_inheritance_auth_disabling_test.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2018 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. + import ballerina/http; http:AuthProvider basicAuthProvider01 = { diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/02_authn_config_inheritance_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/02_authn_config_inheritance_test.bal index ab023cf8d569..f60985ed3561 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/02_authn_config_inheritance_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/02_authn_config_inheritance_test.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2018 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. + import ballerina/http; http:AuthProvider basicAuthProvider02 = { diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/03_authz_config_inheritance_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/03_authz_config_inheritance_test.bal index 4c08e0331206..731c01c7fdde 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/03_authz_config_inheritance_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/03_authz_config_inheritance_test.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2018 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. + import ballerina/http; http:AuthProvider basicAuthProvider03 = { diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/04_resource_level_auth_config_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/04_resource_level_auth_config_test.bal index 0d67adeb2dae..bcf9795490e3 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/04_resource_level_auth_config_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/04_resource_level_auth_config_test.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2018 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. + import ballerina/http; http:AuthProvider basicAuthProvider04 = { diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/05_service_level_auth_config_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/05_service_level_auth_config_test.bal index b0cec735296b..c4f7bbf98d7f 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/05_service_level_auth_config_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/05_service_level_auth_config_test.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2018 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. + import ballerina/http; http:AuthProvider basicAuthProvider05 = { diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/10_secure_service_no_token_propagation_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/10_token_propagation_disabled_test.bal similarity index 71% rename from tests/ballerina-integration-test/src/test/resources/auth/authservices/10_secure_service_no_token_propagation_test.bal rename to tests/ballerina-integration-test/src/test/resources/auth/authservices/10_token_propagation_disabled_test.bal index ce1ba34f1940..39d2ebabf810 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/10_secure_service_no_token_propagation_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/10_token_propagation_disabled_test.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2018 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. + import ballerina/http; // token propagation is set to false by default @@ -31,8 +47,11 @@ service passthroughService on listener10_1 { if (response is http:Response) { _ = caller->respond(response); } else if (response is error) { - json errMsg = { "error": "error occurred while invoking the service" }; - _ = caller->respond(errMsg); + http:Response resp = new; + json errMsg = { "error": "error occurred while invoking the service: " + response.reason() }; + resp.statusCode = 500; + resp.setPayload(errMsg); + _ = caller->respond(resp); } } } diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/11_secure_service_token_propagation_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/11_token_propagation_basic_auth_test.bal similarity index 58% rename from tests/ballerina-integration-test/src/test/resources/auth/authservices/11_secure_service_token_propagation_test.bal rename to tests/ballerina-integration-test/src/test/resources/auth/authservices/11_token_propagation_basic_auth_test.bal index 907000e39adf..9da1b1339744 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/11_secure_service_token_propagation_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/11_token_propagation_basic_auth_test.bal @@ -1,19 +1,24 @@ +// Copyright (c) 2018 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. + import ballerina/http; http:AuthProvider basicAuthProvider11 = { scheme: http:BASIC_AUTH, - authStoreProvider: http:CONFIG_AUTH_STORE, - configAuthProviderConfig: { - inferredJwtIssuerConfig: { - issuer: "ballerina", - keyAlias: "ballerina", - keyPassword: "ballerina", - keyStore: { - path: "${ballerina.home}/bre/security/ballerinaKeystore.p12", - password: "ballerina" - } - } - } + authStoreProvider: http:CONFIG_AUTH_STORE }; listener http:Listener listener11 = new(9192, config = { @@ -26,8 +31,22 @@ listener http:Listener listener11 = new(9192, config = { } }); -http:Client nyseEP03 = new("http://localhost:9193", config = { - auth: { scheme: http:JWT_AUTH } +http:Client nyseEP03 = new("https://localhost:9193", config = { + auth: { + scheme: http:JWT_AUTH, + jwtAuthConfig: { + inferredJwtIssuerConfig: { + issuer: "ballerina", + audience: ["ballerina"], + keyAlias: "ballerina", + keyPassword: "ballerina", + keyStore: { + path: "${ballerina.home}/bre/security/ballerinaKeystore.p12", + password: "ballerina" + } + } + } + } }); @http:ServiceConfig { basePath: "/passthrough" } @@ -42,8 +61,11 @@ service passthroughService03 on listener11 { if (response is http:Response) { _ = caller->respond(response); } else if (response is error) { - json errMsg = { "error": "error occurred while invoking the service" }; - _ = caller->respond(errMsg); + http:Response resp = new; + json errMsg = { "error": "error occurred while invoking the service: " + response.reason() }; + resp.statusCode = 500; + resp.setPayload(errMsg); + _ = caller->respond(resp); } } } diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/12_secure_service_wrong_provider_id_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/12_secure_service_wrong_provider_id_test.bal index f9db42f64aaf..8d361897225b 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/12_secure_service_wrong_provider_id_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/12_secure_service_wrong_provider_id_test.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2018 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. + import ballerina/http; http:AuthProvider basicAuthProvider12 = { diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/15_jwt_authenticated_jwt_client_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/15_jwt_authenticated_jwt_client_test.bal deleted file mode 100644 index 392a31f745c4..000000000000 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/15_jwt_authenticated_jwt_client_test.bal +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright (c) 2019 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. - -import ballerina/http; - -http:AuthProvider jwtAuthProvider15 = { - scheme: http:JWT_AUTH, - jwtAuthProviderConfig: { - issuer:"ballerina", - audience: "ballerina.io", - certificateAlias: "cert", - validateCertificate: false, - trustStore: { - path: "../../../src/test/resources/auth/testtruststore.p12", - password: "ballerina" - } - } -}; - -listener http:Listener listener15 = new(9103, config = { - authProviders:[jwtAuthProvider15], - secureSocket: { - keyStore: { - path: "${ballerina.home}/bre/security/ballerinaKeystore.p12", - password: "ballerina" - } - } -}); - -http:Client clientEPJwt1 = new("https://localhost:9095/foo", config = { - auth: { - scheme: http:JWT_AUTH - } -}); - -service echo15 on listener15 { - - resource function test15 (http:Caller caller, http:Request req) { - var resp = clientEPJwt1->get("/bar"); - _ = caller -> respond(()); - } -} diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/15_token_propagation_jwt_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/15_token_propagation_jwt_test.bal new file mode 100644 index 000000000000..e4ea7e85f60b --- /dev/null +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/15_token_propagation_jwt_test.bal @@ -0,0 +1,103 @@ +// Copyright (c) 2019 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. + +import ballerina/http; + +http:AuthProvider basicAuthProvider15 = { + scheme: http:JWT_AUTH, + jwtAuthProviderConfig: { + issuer: "example1", + audience: "ballerina", + certificateAlias: "ballerina", + trustStore: { + path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", + password: "ballerina" + } + } +}; + +listener http:Listener listener15_1 = new(9103, config = { + authProviders: [basicAuthProvider15], + secureSocket: { + keyStore: { + path: "${ballerina.home}/bre/security/ballerinaKeystore.p12", + password: "ballerina" + } + } +}); + +http:Client nyseEP15 = new("https://localhost:9104", config = { + auth: { + scheme: http:JWT_AUTH + } +}); + +@http:ServiceConfig { basePath: "/passthrough" } +service passthroughService15 on listener15_1 { + + @http:ResourceConfig { + methods: ["GET"], + path: "/" + } + resource function passthrough(http:Caller caller, http:Request clientRequest) { + var response = nyseEP15->get("/nyseStock/stocks", message = untaint clientRequest); + if (response is http:Response) { + _ = caller->respond(response); + } else if (response is error) { + http:Response resp = new; + json errMsg = { "error": "error occurred while invoking the service: " + response.reason() }; + resp.statusCode = 500; + resp.setPayload(errMsg); + _ = caller->respond(resp); + } + } +} + +http:AuthProvider jwtAuthProvider15 = { + scheme: http:JWT_AUTH, + jwtAuthProviderConfig: { + issuer: "example1", + audience: "ballerina", + certificateAlias: "ballerina", + trustStore: { + path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", + password: "ballerina" + } + } +}; + +listener http:Listener listener15_2 = new(9104, config = { + authProviders: [jwtAuthProvider15], + secureSocket: { + keyStore: { + path: "${ballerina.home}/bre/security/ballerinaKeystore.p12", + password: "ballerina" + } + } + }); + +@http:ServiceConfig { basePath: "/nyseStock" } +service nyseStockQuote15 on listener15_2 { + + @http:ResourceConfig { + methods: ["GET"], + path: "/stocks" + } + resource function stocks(http:Caller caller, http:Request clientRequest) { + json payload = { "exchange": "nyse", "name": "IBM", "value": "127.50" }; + _ = caller->respond(payload); + } +} diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/16_token_propagation_jwt_reissuing_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/16_token_propagation_jwt_reissuing_test.bal new file mode 100644 index 000000000000..467dc912a24b --- /dev/null +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/16_token_propagation_jwt_reissuing_test.bal @@ -0,0 +1,115 @@ +// Copyright (c) 2019 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. + +import ballerina/http; + +http:AuthProvider basicAuthProvider16 = { + scheme: http:JWT_AUTH, + jwtAuthProviderConfig: { + issuer: "example1", + audience: "ballerina", + certificateAlias: "ballerina", + trustStore: { + path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", + password: "ballerina" + } + } +}; + +listener http:Listener listener16_1 = new(9105, config = { + authProviders: [basicAuthProvider16], + secureSocket: { + keyStore: { + path: "${ballerina.home}/bre/security/ballerinaKeystore.p12", + password: "ballerina" + } + } +}); + +http:Client nyseEP16 = new("https://localhost:9106", config = { + auth: { + scheme: http:JWT_AUTH, + jwtAuthConfig: { + inferredJwtIssuerConfig: { + issuer: "example2", + audience: ["ballerina"], + keyAlias: "ballerina", + keyPassword: "ballerina", + keyStore: { + path: "${ballerina.home}/bre/security/ballerinaKeystore.p12", + password: "ballerina" + } + } + } + } +}); + +@http:ServiceConfig { basePath: "/passthrough" } +service passthroughService16 on listener16_1 { + + @http:ResourceConfig { + methods: ["GET"], + path: "/" + } + resource function passthrough(http:Caller caller, http:Request clientRequest) { + var response = nyseEP16->get("/nyseStock/stocks", message = untaint clientRequest); + if (response is http:Response) { + _ = caller->respond(response); + } else if (response is error) { + http:Response resp = new; + json errMsg = { "error": "error occurred while invoking the service: " + response.reason() }; + resp.statusCode = 500; + resp.setPayload(errMsg); + _ = caller->respond(resp); + } + } +} + +http:AuthProvider jwtAuthProvider16 = { + scheme: http:JWT_AUTH, + jwtAuthProviderConfig: { + issuer: "example2", + audience: "ballerina", + certificateAlias: "ballerina", + trustStore: { + path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", + password: "ballerina" + } + } +}; + +listener http:Listener listener16_2 = new(9106, config = { + authProviders: [jwtAuthProvider16], + secureSocket: { + keyStore: { + path: "${ballerina.home}/bre/security/ballerinaKeystore.p12", + password: "ballerina" + } + } + }); + +@http:ServiceConfig { basePath: "/nyseStock" } +service nyseStockQuote16 on listener16_2 { + + @http:ResourceConfig { + methods: ["GET"], + path: "/stocks" + } + resource function stocks(http:Caller caller, http:Request clientRequest) { + json payload = { "exchange": "nyse", "name": "IBM", "value": "127.50" }; + _ = caller->respond(payload); + } +} diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/17_token_propagation_jwt_reissuing_negative_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/17_token_propagation_jwt_reissuing_negative_test.bal new file mode 100644 index 000000000000..ed62df425d0a --- /dev/null +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/17_token_propagation_jwt_reissuing_negative_test.bal @@ -0,0 +1,115 @@ +// Copyright (c) 2019 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. + +import ballerina/http; + +http:AuthProvider basicAuthProvider17 = { + scheme: http:JWT_AUTH, + jwtAuthProviderConfig: { + issuer: "example1", + audience: "ballerina", + certificateAlias: "ballerina", + trustStore: { + path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", + password: "ballerina" + } + } +}; + +listener http:Listener listener17_1 = new(9107, config = { + authProviders: [basicAuthProvider17], + secureSocket: { + keyStore: { + path: "${ballerina.home}/bre/security/ballerinaKeystore.p12", + password: "ballerina" + } + } +}); + +http:Client nyseEP17 = new("https://localhost:9108", config = { + auth: { + scheme: http:JWT_AUTH, + jwtAuthConfig: { + inferredJwtIssuerConfig: { + issuer: "ballerina", + audience: ["ballerina"], + keyAlias: "ballerina", + keyPassword: "ballerina", + keyStore: { + path: "${ballerina.home}/bre/security/ballerinaKeystore.p12", + password: "ballerina" + } + } + } + } +}); + +@http:ServiceConfig { basePath: "/passthrough" } +service passthroughService17 on listener17_1 { + + @http:ResourceConfig { + methods: ["GET"], + path: "/" + } + resource function passthrough(http:Caller caller, http:Request clientRequest) { + var response = nyseEP17->get("/nyseStock/stocks", message = untaint clientRequest); + if (response is http:Response) { + _ = caller->respond(response); + } else if (response is error) { + http:Response resp = new; + json errMsg = { "error": "error occurred while invoking the service: " + response.reason() }; + resp.statusCode = 500; + resp.setPayload(errMsg); + _ = caller->respond(resp); + } + } +} + +http:AuthProvider jwtAuthProvider17 = { + scheme: http:JWT_AUTH, + jwtAuthProviderConfig: { + issuer: "example2aaaaaaaaaaaaaa", + audience: "ballerina", + certificateAlias: "ballerina", + trustStore: { + path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", + password: "ballerina" + } + } +}; + +listener http:Listener listener17_2 = new(9108, config = { + authProviders: [jwtAuthProvider17], + secureSocket: { + keyStore: { + path: "${ballerina.home}/bre/security/ballerinaKeystore.p12", + password: "ballerina" + } + } + }); + +@http:ServiceConfig { basePath: "/nyseStock" } +service nyseStockQuote17 on listener17_2 { + + @http:ResourceConfig { + methods: ["GET"], + path: "/stocks" + } + resource function stocks(http:Caller caller, http:Request clientRequest) { + json payload = { "exchange": "nyse", "name": "IBM", "value": "127.50" }; + _ = caller->respond(payload); + } +} From f1049e8243f486f3132ca8c4e6c14b6f1a8eeeab Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Fri, 15 Feb 2019 09:31:45 +0530 Subject: [PATCH 16/26] Remove unnecessary else condition --- stdlib/auth/src/main/ballerina/auth/jwt_validator.bal | 3 --- 1 file changed, 3 deletions(-) diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal b/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal index 4f012247ec95..ffd651965c70 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal @@ -75,9 +75,6 @@ public function validateJwt(string jwtToken, JWTValidatorConfig config) returns error jwtError = error(AUTH_ERROR_CODE, { message : "Invalid JWT token" }); return jwtError; } - } else { - error jwtError = error(AUTH_ERROR_CODE, { message : "Invalid JWT token" }); - return jwtError; } } From 9fb707d8bf75af09f62024804ff3328367def887 Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Fri, 15 Feb 2019 12:50:49 +0530 Subject: [PATCH 17/26] Fix language server test failures --- .../source/testgen/module2/services.bal | 8 +- .../packageimport/packageImport.json | 67 +++++++++-- .../service/serviceBodyCompletion5.json | 67 +++++++++-- .../service/serviceEndpointBind4.json | 67 +++++++++-- .../toplevel/globalVarDefPackageContent.json | 67 +++++++++-- .../src/main/ballerina/auth/jwt_issuer.bal | 4 +- .../src/main/ballerina/auth/jwt_validator.bal | 14 +-- .../src/main/ballerina/http/auth/utils.bal | 8 ++ .../main/ballerina/http/service_endpoint.bal | 108 +++++++++--------- 9 files changed, 309 insertions(+), 101 deletions(-) diff --git a/language-server/modules/langserver-core/src/test/resources/command/source/testgen/module2/services.bal b/language-server/modules/langserver-core/src/test/resources/command/source/testgen/module2/services.bal index cfe669f392fb..9f92d6b7c5e3 100644 --- a/language-server/modules/langserver-core/src/test/resources/command/source/testgen/module2/services.bal +++ b/language-server/modules/langserver-core/src/test/resources/command/source/testgen/module2/services.bal @@ -33,13 +33,13 @@ service wssService on securedListener2 { } http:AuthProvider basicAuthProvider = { - scheme: "basic", - authStoreProvider: "config" + scheme: http:BASIC_AUTH, + authStoreProvider: http:CONFIG_AUTH_STORE }; http:AuthProvider basicAuthProvider2 = { - scheme: "basic", - authStoreProvider: "config" + scheme: http:BASIC_AUTH, + authStoreProvider: http:CONFIG_AUTH_STORE }; listener http:Listener securedListener = new(9090, config = { diff --git a/language-server/modules/langserver-core/src/test/resources/completion/packageimport/packageImport.json b/language-server/modules/langserver-core/src/test/resources/completion/packageimport/packageImport.json index cebe40b24577..849f4ab652ab 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/packageimport/packageImport.json +++ b/language-server/modules/langserver-core/src/test/resources/completion/packageimport/packageImport.json @@ -263,6 +263,36 @@ "sortText":"171", "insertText":"AuthConfig" }, + { + "label":"BasicAuthConfig", + "kind":"Class", + "detail":"BType", + "documentation":{ + "left":"BasicAuthConfig record can be used to configure Basic Authentication used by the HTTP endpoint.\n" + }, + "sortText":"171", + "insertText":"BasicAuthConfig" + }, + { + "label":"OAuth2AuthConfig", + "kind":"Class", + "detail":"BType", + "documentation":{ + "left":"OAuth2AuthConfig record can be used to configure OAuth2 based authentication used by the HTTP endpoint.\n" + }, + "sortText":"171", + "insertText":"OAuth2AuthConfig" + }, + { + "label":"JwtAuthConfig", + "kind":"Class", + "detail":"BType", + "documentation":{ + "left":"JwtAuthConfig record can be used to configure JWT based authentication used by the HTTP endpoint.\n" + }, + "sortText":"171", + "insertText":"JwtAuthConfig" + }, { "label":"HttpTimeoutError", "kind":"Class", @@ -880,6 +910,36 @@ "sortText":"171", "insertText":"WebSocketClient" }, + { + "label":"InboundAuthScheme", + "kind":"Enum", + "detail":"BType", + "documentation":{ + "left":"Inbound authentication schemes." + }, + "sortText":"171", + "insertText":"InboundAuthScheme" + }, + { + "label":"OutboundAuthScheme", + "kind":"Enum", + "detail":"BType", + "documentation":{ + "left":"Outbound authentication schemes." + }, + "sortText":"171", + "insertText":"OutboundAuthScheme" + }, + { + "label":"AuthStoreProvider", + "kind":"Enum", + "detail":"BType", + "documentation":{ + "left":"Authentication storage providers for BasicAuth scheme." + }, + "sortText":"171", + "insertText":"AuthStoreProvider" + }, { "label":"CachingPolicy", "kind":"Enum", @@ -930,13 +990,6 @@ "sortText":"171", "insertText":"RedirectCode" }, - { - "label":"AuthScheme", - "kind":"Enum", - "detail":"BType", - "sortText":"171", - "insertText":"AuthScheme" - }, { "label":"CredentialBearer", "kind":"Enum", diff --git a/language-server/modules/langserver-core/src/test/resources/completion/service/serviceBodyCompletion5.json b/language-server/modules/langserver-core/src/test/resources/completion/service/serviceBodyCompletion5.json index 502ef6afcb4a..6eb3979b2b78 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/service/serviceBodyCompletion5.json +++ b/language-server/modules/langserver-core/src/test/resources/completion/service/serviceBodyCompletion5.json @@ -263,6 +263,36 @@ "sortText":"171", "insertText":"AuthConfig" }, + { + "label":"BasicAuthConfig", + "kind":"Class", + "detail":"BType", + "documentation":{ + "left":"BasicAuthConfig record can be used to configure Basic Authentication used by the HTTP endpoint.\n" + }, + "sortText":"171", + "insertText":"BasicAuthConfig" + }, + { + "label":"OAuth2AuthConfig", + "kind":"Class", + "detail":"BType", + "documentation":{ + "left":"OAuth2AuthConfig record can be used to configure OAuth2 based authentication used by the HTTP endpoint.\n" + }, + "sortText":"171", + "insertText":"OAuth2AuthConfig" + }, + { + "label":"JwtAuthConfig", + "kind":"Class", + "detail":"BType", + "documentation":{ + "left":"JwtAuthConfig record can be used to configure JWT based authentication used by the HTTP endpoint.\n" + }, + "sortText":"171", + "insertText":"JwtAuthConfig" + }, { "label":"HttpTimeoutError", "kind":"Class", @@ -880,6 +910,36 @@ "sortText":"171", "insertText":"WebSocketClient" }, + { + "label":"InboundAuthScheme", + "kind":"Enum", + "detail":"BType", + "documentation":{ + "left":"Inbound authentication schemes." + }, + "sortText":"171", + "insertText":"InboundAuthScheme" + }, + { + "label":"OutboundAuthScheme", + "kind":"Enum", + "detail":"BType", + "documentation":{ + "left":"Outbound authentication schemes." + }, + "sortText":"171", + "insertText":"OutboundAuthScheme" + }, + { + "label":"AuthStoreProvider", + "kind":"Enum", + "detail":"BType", + "documentation":{ + "left":"Authentication storage providers for BasicAuth scheme." + }, + "sortText":"171", + "insertText":"AuthStoreProvider" + }, { "label":"CachingPolicy", "kind":"Enum", @@ -930,13 +990,6 @@ "sortText":"171", "insertText":"RedirectCode" }, - { - "label":"AuthScheme", - "kind":"Enum", - "detail":"BType", - "sortText":"171", - "insertText":"AuthScheme" - }, { "label":"CredentialBearer", "kind":"Enum", diff --git a/language-server/modules/langserver-core/src/test/resources/completion/service/serviceEndpointBind4.json b/language-server/modules/langserver-core/src/test/resources/completion/service/serviceEndpointBind4.json index 71b4274dd7b7..6fddddc4e59b 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/service/serviceEndpointBind4.json +++ b/language-server/modules/langserver-core/src/test/resources/completion/service/serviceEndpointBind4.json @@ -263,6 +263,36 @@ "sortText":"170", "insertText":"AuthConfig" }, + { + "label":"BasicAuthConfig", + "kind":"Class", + "detail":"BType", + "documentation":{ + "left":"BasicAuthConfig record can be used to configure Basic Authentication used by the HTTP endpoint.\n" + }, + "sortText":"170", + "insertText":"BasicAuthConfig" + }, + { + "label":"OAuth2AuthConfig", + "kind":"Class", + "detail":"BType", + "documentation":{ + "left":"OAuth2AuthConfig record can be used to configure OAuth2 based authentication used by the HTTP endpoint.\n" + }, + "sortText":"170", + "insertText":"OAuth2AuthConfig" + }, + { + "label":"JwtAuthConfig", + "kind":"Class", + "detail":"BType", + "documentation":{ + "left":"JwtAuthConfig record can be used to configure JWT based authentication used by the HTTP endpoint.\n" + }, + "sortText":"170", + "insertText":"JwtAuthConfig" + }, { "label":"HttpTimeoutError", "kind":"Class", @@ -880,6 +910,36 @@ "sortText":"170", "insertText":"WebSocketClient" }, + { + "label":"InboundAuthScheme", + "kind":"Enum", + "detail":"BType", + "documentation":{ + "left":"Inbound authentication schemes." + }, + "sortText":"171", + "insertText":"InboundAuthScheme" + }, + { + "label":"OutboundAuthScheme", + "kind":"Enum", + "detail":"BType", + "documentation":{ + "left":"Outbound authentication schemes." + }, + "sortText":"171", + "insertText":"OutboundAuthScheme" + }, + { + "label":"AuthStoreProvider", + "kind":"Enum", + "detail":"BType", + "documentation":{ + "left":"Authentication storage providers for BasicAuth scheme." + }, + "sortText":"171", + "insertText":"AuthStoreProvider" + }, { "label":"CachingPolicy", "kind":"Enum", @@ -930,13 +990,6 @@ "sortText":"170", "insertText":"RedirectCode" }, - { - "label":"AuthScheme", - "kind":"Enum", - "detail":"BType", - "sortText":"170", - "insertText":"AuthScheme" - }, { "label":"CredentialBearer", "kind":"Enum", diff --git a/language-server/modules/langserver-core/src/test/resources/completion/toplevel/globalVarDefPackageContent.json b/language-server/modules/langserver-core/src/test/resources/completion/toplevel/globalVarDefPackageContent.json index d3f7e613aa54..80f0c38e9f16 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/toplevel/globalVarDefPackageContent.json +++ b/language-server/modules/langserver-core/src/test/resources/completion/toplevel/globalVarDefPackageContent.json @@ -263,6 +263,36 @@ "sortText":"170", "insertText":"AuthConfig" }, + { + "label":"BasicAuthConfig", + "kind":"Class", + "detail":"BType", + "documentation":{ + "left":"BasicAuthConfig record can be used to configure Basic Authentication used by the HTTP endpoint.\n" + }, + "sortText":"170", + "insertText":"BasicAuthConfig" + }, + { + "label":"OAuth2AuthConfig", + "kind":"Class", + "detail":"BType", + "documentation":{ + "left":"OAuth2AuthConfig record can be used to configure OAuth2 based authentication used by the HTTP endpoint.\n" + }, + "sortText":"170", + "insertText":"OAuth2AuthConfig" + }, + { + "label":"JwtAuthConfig", + "kind":"Class", + "detail":"BType", + "documentation":{ + "left":"JwtAuthConfig record can be used to configure JWT based authentication used by the HTTP endpoint.\n" + }, + "sortText":"170", + "insertText":"JwtAuthConfig" + }, { "label":"HttpTimeoutError", "kind":"Class", @@ -880,6 +910,36 @@ "sortText":"170", "insertText":"WebSocketClient" }, + { + "label":"InboundAuthScheme", + "kind":"Enum", + "detail":"BType", + "documentation":{ + "left":"Inbound authentication schemes." + }, + "sortText":"171", + "insertText":"InboundAuthScheme" + }, + { + "label":"OutboundAuthScheme", + "kind":"Enum", + "detail":"BType", + "documentation":{ + "left":"Outbound authentication schemes." + }, + "sortText":"171", + "insertText":"OutboundAuthScheme" + }, + { + "label":"AuthStoreProvider", + "kind":"Enum", + "detail":"BType", + "documentation":{ + "left":"Authentication storage providers for BasicAuth scheme." + }, + "sortText":"171", + "insertText":"AuthStoreProvider" + }, { "label":"CachingPolicy", "kind":"Enum", @@ -930,13 +990,6 @@ "sortText":"170", "insertText":"RedirectCode" }, - { - "label":"AuthScheme", - "kind":"Enum", - "detail":"BType", - "sortText":"170", - "insertText":"AuthScheme" - }, { "label":"CredentialBearer", "kind":"Enum", diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal b/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal index fdead8ef3d13..1ebcbbc3b8c4 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal @@ -68,7 +68,7 @@ function buildHeaderString(JwtHeader header) returns (string|error) { headerJson = addMapToJson(headerJson, customClaims); } string headerValInString = headerJson.toString(); - string encodedPayload = encoding:encodeBase64(headerValInString.toByteArray("UTF-8")); + string encodedPayload = encoding:encodeBase64Url(headerValInString.toByteArray("UTF-8")); return encodedPayload; } @@ -96,7 +96,7 @@ function buildPayloadString(JwtPayload payload) returns (string|error) { payloadJson = addMapToJson(payloadJson, customClaims); } string payloadInString = payloadJson.toString(); - return encoding:encodeBase64(payloadInString.toByteArray("UTF-8")); + return encoding:encodeBase64Url(payloadInString.toByteArray("UTF-8")); } function addMapToJson(json inJson, map mapToConvert) returns (json) { diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal b/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal index ffd651965c70..f5062b013754 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal @@ -59,8 +59,6 @@ public function validateJwt(string jwtToken, JWTValidatorConfig config) returns var decodedJwt = parseJWT(encodedJWTComponents); if (decodedJwt is (JwtHeader, JwtPayload)) { (header, payload) = decodedJwt; - } else if (decodedJwt is error) { - return decodedJwt; } else { return decodedJwt; } @@ -107,9 +105,9 @@ function parseJWT(string[] encodedJWTComponents) returns ((JwtHeader, JwtPayload function getDecodedJWTComponents(string[] encodedJWTComponents) returns ((json, json)|error) { string jwtHeader = encoding:byteArrayToString(check - encoding:decodeBase64(urlDecode(encodedJWTComponents[0]))); + encoding:decodeBase64Url(encodedJWTComponents[0])); string jwtPayload = encoding:byteArrayToString(check - encoding:decodeBase64(urlDecode(encodedJWTComponents[1]))); + encoding:decodeBase64Url(encodedJWTComponents[1])); json jwtHeaderJson = {}; json jwtPayloadJson = {}; @@ -125,7 +123,7 @@ function getDecodedJWTComponents(string[] encodedJWTComponents) returns ((json, var jsonPayload = reader.readJson(); if (jsonPayload is json) { jwtPayloadJson = jsonPayload; - } else if (jsonPayload is error) { + } else { return jsonPayload; } return (jwtHeaderJson, jwtPayloadJson); @@ -349,9 +347,3 @@ function convertToStringArray(json jsonData) returns (string[]) { } return outData; } - -function urlDecode(string encodedString) returns (string) { - string decodedString = encodedString.replaceAll("-", "+"); - decodedString = decodedString.replaceAll("_", "/"); - return decodedString; -} diff --git a/stdlib/http/src/main/ballerina/http/auth/utils.bal b/stdlib/http/src/main/ballerina/http/auth/utils.bal index 4596c1ca88e5..b9a4619d4587 100644 --- a/stdlib/http/src/main/ballerina/http/auth/utils.bal +++ b/stdlib/http/src/main/ballerina/http/auth/utils.bal @@ -27,17 +27,25 @@ public const string AUTH_SCHEME_BASIC = "Basic"; # Bearer authentication scheme. public const string AUTH_SCHEME_BEARER = "Bearer"; +# Inbound authentication schemes. public type InboundAuthScheme BASIC_AUTH|JWT_AUTH; +# Outbound authentication schemes. public type OutboundAuthScheme BASIC_AUTH|OAUTH2|JWT_AUTH; +# Basic authentication scheme. public const BASIC_AUTH = "BASIC_AUTH"; +# OAuth2 authentication scheme. public const OAUTH2 = "OAUTH2"; +# JWT authentication scheme. public const JWT_AUTH = "JWT_AUTH"; +# Authentication storage providers for BasicAuth scheme. public type AuthStoreProvider CONFIG_AUTH_STORE|LDAP_AUTH_STORE; +# Configuration file based authentication storage. public const CONFIG_AUTH_STORE = "CONFIG_AUTH_STORE"; +# LDAP based authentication storage. public const LDAP_AUTH_STORE = "LDAP_AUTH_STORE"; # Extracts the basic authentication header value from the request. diff --git a/stdlib/http/src/main/ballerina/http/service_endpoint.bal b/stdlib/http/src/main/ballerina/http/service_endpoint.bal index db434bd7c435..f72cbfecc60a 100644 --- a/stdlib/http/src/main/ballerina/http/service_endpoint.bal +++ b/stdlib/http/src/main/ballerina/http/service_endpoint.bal @@ -74,7 +74,7 @@ public type Listener object { public function Listener.init(ServiceEndpointConfiguration c) { self.config = c; var providers = self.config.authProviders; - if (providers is AuthProvider[]) { + if (providers.length() > 0) { var secureSocket = self.config.secureSocket; if (secureSocket is ServiceSecureSocket) { addAuthFiltersForSecureListener(self.config, self.instanceId); @@ -152,7 +152,7 @@ public type ServiceEndpointConfiguration record { Filter[] filters = []; int timeoutMillis = DEFAULT_LISTENER_TIMEOUT; int maxPipelinedRequests = MAX_PIPELINED_REQUESTS; - AuthProvider[]? authProviders = (); + AuthProvider?[] authProviders = []; AuthCacheConfig positiveAuthzCache = {}; AuthCacheConfig negativeAuthzCache = {}; !...; @@ -209,7 +209,7 @@ public type AuthCacheConfig record { }; # Configuration for authentication providers. - +# # + id - Authentication provider instance id # + scheme - Authentication scheme # + authStoreProvider - Authentication store provider (Config, LDAP, etc.) implementation @@ -266,66 +266,62 @@ function addAuthFiltersForSecureListener(ServiceEndpointConfiguration config, st function createAuthFiltersForSecureListener(ServiceEndpointConfiguration config, string instanceId) returns (Filter[]) { // parse and create authentication handlers AuthHandlerRegistry registry = new; - AuthProvider[] authProviderList = []; Filter[] authFilters = []; - - var providers = config.authProviders; - if (providers is AuthProvider[]) { - authProviderList = providers; - } else { - return authFilters; - } - - foreach var provider in authProviderList { - if (provider.id.length() > 0) { - registry.add(provider.id, createAuthHandler(provider, instanceId)); - } else { - string providerId = system:uuid(); - registry.add(providerId, createAuthHandler(provider, instanceId)); - } - } - - AuthnHandlerChain authnHandlerChain = new(registry); - AuthnFilter authnFilter = new(authnHandlerChain); - cache:Cache positiveAuthzCache = new(expiryTimeMillis = config.positiveAuthzCache.expiryTimeMillis, - capacity = config.positiveAuthzCache.capacity, - evictionFactor = config.positiveAuthzCache.evictionFactor); - cache:Cache negativeAuthzCache = new(expiryTimeMillis = config.negativeAuthzCache.expiryTimeMillis, - capacity = config.negativeAuthzCache.capacity, - evictionFactor = config.negativeAuthzCache.evictionFactor); - auth:AuthStoreProvider authStoreProvider = new; - - foreach var provider in authProviderList { - if (provider.scheme == BASIC_AUTH) { - if (provider.authStoreProvider == LDAP_AUTH_STORE) { - var ldapAuthProviderConfig = provider.ldapAuthProviderConfig; - if (ldapAuthProviderConfig is auth:LdapAuthProviderConfig) { - auth:LdapAuthStoreProvider ldapAuthStoreProvider = new(ldapAuthProviderConfig, instanceId); - authStoreProvider = ldapAuthStoreProvider; + var authProviderList = config.authProviders; + if (authProviderList is AuthProvider[]) { + if (authProviderList.length() > 0) { + foreach var provider in authProviderList { + if (provider.id.length() > 0) { + registry.add(provider.id, createAuthHandler(provider, instanceId)); } else { - error e = error("LDAP auth provider config not provided"); - panic e; + string providerId = system:uuid(); + registry.add(providerId, createAuthHandler(provider, instanceId)); } - } else if (provider.authStoreProvider == CONFIG_AUTH_STORE) { - var configAuthProviderConfig = provider.configAuthProviderConfig; - auth:ConfigAuthStoreProvider configAuthStoreProvider; - if (configAuthProviderConfig is auth:ConfigAuthProviderConfig) { - configAuthStoreProvider = new(configAuthProviderConfig); - } else { - configAuthStoreProvider = new({}); + } + + AuthnHandlerChain authnHandlerChain = new(registry); + AuthnFilter authnFilter = new(authnHandlerChain); + cache:Cache positiveAuthzCache = new(expiryTimeMillis = config.positiveAuthzCache.expiryTimeMillis, + capacity = config.positiveAuthzCache.capacity, + evictionFactor = config.positiveAuthzCache.evictionFactor); + cache:Cache negativeAuthzCache = new(expiryTimeMillis = config.negativeAuthzCache.expiryTimeMillis, + capacity = config.negativeAuthzCache.capacity, + evictionFactor = config.negativeAuthzCache.evictionFactor); + auth:AuthStoreProvider authStoreProvider = new; + + foreach var provider in authProviderList { + if (provider.scheme == BASIC_AUTH) { + if (provider.authStoreProvider == LDAP_AUTH_STORE) { + var ldapAuthProviderConfig = provider.ldapAuthProviderConfig; + if (ldapAuthProviderConfig is auth:LdapAuthProviderConfig) { + auth:LdapAuthStoreProvider ldapAuthStoreProvider = new(ldapAuthProviderConfig, instanceId); + authStoreProvider = ldapAuthStoreProvider; + } else { + error e = error("LDAP auth provider config not provided"); + panic e; + } + } else if (provider.authStoreProvider == CONFIG_AUTH_STORE) { + var configAuthProviderConfig = provider.configAuthProviderConfig; + auth:ConfigAuthStoreProvider configAuthStoreProvider; + if (configAuthProviderConfig is auth:ConfigAuthProviderConfig) { + configAuthStoreProvider = new(configAuthProviderConfig); + } else { + configAuthStoreProvider = new({}); + } + authStoreProvider = configAuthStoreProvider; + } else { + error configError = error("Unsupported auth store provider"); + panic configError; + } } - authStoreProvider = configAuthStoreProvider; - } else { - error configError = error("Unsupported auth store provider"); - panic configError; } + + HttpAuthzHandler authzHandler = new(authStoreProvider, positiveAuthzCache, negativeAuthzCache); + AuthzFilter authzFilter = new(authzHandler); + authFilters[0] = authnFilter; + authFilters[1] = authzFilter; } } - -HttpAuthzHandler authzHandler = new(authStoreProvider, positiveAuthzCache, negativeAuthzCache); - AuthzFilter authzFilter = new(authzHandler); - authFilters[0] = authnFilter; - authFilters[1] = authzFilter; return authFilters; } From 918c3b49ba56742a85c3dea5212289f081ac1656 Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Wed, 20 Feb 2019 11:41:38 +0530 Subject: [PATCH 18/26] Make issuer and validator flexible --- .../main/ballerina/auth/jwt_auth_provider.bal | 8 +- .../src/main/ballerina/auth/jwt_common.bal | 13 +- .../src/main/ballerina/auth/jwt_issuer.bal | 109 +++++++++---- .../src/main/ballerina/auth/jwt_validator.bal | 151 +++++++++++------- .../stdlib/auth/jwt/JWTAuthenticatorTest.java | 4 +- .../stdlib/auth/jwt/JwtTest.java | 111 ++++++++++++- .../test-src/jwt-authenticator-test.bal | 11 +- .../test/resources/test-src/jwt/jwt-test.bal | 121 +++++++++++++- .../ballerina/http/http_secure_client.bal | 8 +- 9 files changed, 424 insertions(+), 112 deletions(-) diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_auth_provider.bal b/stdlib/auth/src/main/ballerina/auth/jwt_auth_provider.bal index 120bb4dd33d3..f164082062e6 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_auth_provider.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_auth_provider.bal @@ -123,11 +123,11 @@ const string AUTH_TYPE_JWT = "jwt"; # + certificateAlias - Token signed key alias # + validateCertificate - Validate public key certificate notBefore and notAfter periods public type JWTAuthProviderConfig record { - string issuer; - string audience; + string issuer?; + string[] audience?; int clockSkew = 0; - crypto:TrustStore trustStore; - string certificateAlias; + crypto:TrustStore trustStore?; + string certificateAlias?; boolean validateCertificate?; !...; }; diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_common.bal b/stdlib/auth/src/main/ballerina/auth/jwt_common.bal index ca7193d802fd..43534b0d2661 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_common.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_common.bal @@ -15,7 +15,7 @@ // under the License. # The key algorithms supported by crypto module. -public type JwtSigningAlgorithm RS256|RS384|RS512; +public type JwtSigningAlgorithm RS256|RS384|RS512|NONE; # The `RSA-SHA256` algorithm public const RS256 = "RS256"; @@ -26,6 +26,9 @@ public const RS384 = "RS384"; # The `RSA-SHA512` algorithm public const RS512 = "RS512"; +# Unsecured JWTs (no signing) +public const NONE = "NONE"; + //JOSH header parameters const string ALG = "alg"; const string TYP = "typ"; @@ -66,11 +69,11 @@ public type JwtHeader record { # + iat - Issued at, identifies the time at which the JWT was issued # + customClaims - Map of custom claims public type JwtPayload record { - string iss = ""; - string sub = ""; - string[] aud = []; + string iss?; + string sub?; + string[] aud?; string jti?; - int exp = 0; + int exp?; int nbf?; int iat?; map customClaims?; diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal b/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal index 1ebcbbc3b8c4..9b03c216ec3b 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal @@ -17,39 +17,75 @@ import ballerina/crypto; import ballerina/encoding; +# Represents JWT issuer configurations. +# + keyStore - Keystore to be used in JWT signing +# + keyAlias - Signing key alias +# + keyPassword - Signing key password +# + audienceAsArray - Always represent audience as an array (even when there is single audience) +public type JWTIssuerConfig record { + crypto:KeyStore keyStore?; + string keyAlias?; + string keyPassword?; + boolean audienceAsArray = false; + !...; +}; + # Issue a JWT token. # # + header - JwtHeader object # + payload - JwtPayload object -# + keyStore - Keystore to be used in JWT signing -# + keyAlias - Signing key alias -# + keyPassword - Signing key password +# + config - JWT issuer config record # + return - JWT token string or an error if token validation fails -public function issueJwt(JwtHeader header, JwtPayload payload, crypto:KeyStore keyStore, string keyAlias, - string keyPassword) returns (string|error) { +public function issueJwt(JwtHeader header, JwtPayload payload, JWTIssuerConfig? config) returns (string|error) { + boolean audienceAsArray = false; + if (config is JWTIssuerConfig) { + audienceAsArray = config.audienceAsArray; + } string jwtHeader = check buildHeaderString(header); - string jwtPayload = check buildPayloadString(payload); + string jwtPayload = check buildPayloadString(payload, audienceAsArray); string jwtAssertion = jwtHeader + "." + jwtPayload; - var privateKey = check crypto:decodePrivateKey(keyStore = keyStore, keyAlias = keyAlias, keyPassword = keyPassword); - - string signature = ""; - if (header.alg == RS256) { - signature = encoding:encodeBase64Url(check crypto:signRsaSha256(jwtAssertion.toByteArray("UTF-8"), privateKey)); - } else if (header.alg == RS384) { - signature = encoding:encodeBase64Url(check crypto:signRsaSha384(jwtAssertion.toByteArray("UTF-8"), privateKey)); - } else if (header.alg == RS512) { - signature = encoding:encodeBase64Url(check crypto:signRsaSha512(jwtAssertion.toByteArray("UTF-8"), privateKey)); + if (header.alg == NONE) { + return (jwtAssertion); } else { - error jwtError = error(AUTH_ERROR_CODE, { message : "Unsupported JWS algorithm" }); - return jwtError; + if (config is JWTIssuerConfig) { + var keyStore = config["keyStore"]; + var keyAlias = config["keyAlias"]; + var keyPassword = config["keyPassword"]; + if (keyStore is crypto:KeyStore && keyAlias is string && keyPassword is string) { + var privateKey = check crypto:decodePrivateKey(keyStore = keyStore, keyAlias = keyAlias, + keyPassword = keyPassword); + string signature = ""; + if (header.alg == RS256) { + signature = encoding:encodeBase64Url(check crypto:signRsaSha256(jwtAssertion.toByteArray("UTF-8"), + privateKey)); + } else if (header.alg == RS384) { + signature = encoding:encodeBase64Url(check crypto:signRsaSha384(jwtAssertion.toByteArray("UTF-8"), + privateKey)); + } else if (header.alg == RS512) { + signature = encoding:encodeBase64Url(check crypto:signRsaSha512(jwtAssertion.toByteArray("UTF-8"), + privateKey)); + } else { + error jwtError = error(AUTH_ERROR_CODE, { message : "Unsupported JWS algorithm" }); + return jwtError; + } + return (jwtAssertion + "." + signature); + } else { + error jwtError = error(AUTH_ERROR_CODE, + { message : "Signing JWT requires keyStore, keyAlias and keyPassword" }); + return jwtError; + } + } else { + error jwtError = error(AUTH_ERROR_CODE, + { message : "Signing JWT requires JWTIssuerConfig with keystore information" }); + return jwtError; + } } - return (jwtAssertion + "." + signature); } function buildHeaderString(JwtHeader header) returns (string|error) { json headerJson = {}; if (!validateMandatoryJwtHeaderFields(header)) { - error jwtError = error(AUTH_ERROR_CODE, { message : "Mandatory field signing algorithm(alg) is empty." }); + error jwtError = error(AUTH_ERROR_CODE, { message : "Mandatory field signing algorithm (alg) is empty." }); return jwtError; } if (header.alg == RS256) { @@ -58,6 +94,8 @@ function buildHeaderString(JwtHeader header) returns (string|error) { headerJson[ALG] = "RS384"; } else if (header.alg == RS512) { headerJson[ALG] = "RS512"; + } else if (header.alg == NONE) { + headerJson[ALG] = "none"; } else { error jwtError = error(AUTH_ERROR_CODE, { message : "Unsupported JWS algorithm" }); return jwtError; @@ -72,16 +110,20 @@ function buildHeaderString(JwtHeader header) returns (string|error) { return encodedPayload; } -function buildPayloadString(JwtPayload payload) returns (string|error) { +function buildPayloadString(JwtPayload payload, boolean audienceAsArray) returns (string|error) { json payloadJson = {}; - if (!validateMandatoryFields(payload)) { - error jwtError = error(AUTH_ERROR_CODE, - { message : "Mandatory fields(Issuer, Subject, Expiration time or Audience) are empty." }); - return jwtError; + var sub = payload["sub"]; + if (sub is string) { + payloadJson[SUB] = sub; + } + var iss = payload["iss"]; + if (iss is string) { + payloadJson[ISS] = iss; + } + var exp = payload["exp"]; + if (exp is int) { + payloadJson[EXP] = exp; } - payloadJson[SUB] = payload.sub; - payloadJson[ISS] = payload.iss; - payloadJson[EXP] = payload.exp; var iat = payload["iat"]; if (iat is int) { payloadJson[IAT] = iat; @@ -90,7 +132,18 @@ function buildPayloadString(JwtPayload payload) returns (string|error) { if (jti is string) { payloadJson[JTI] = jti; } - payloadJson[AUD] = convertStringArrayToJson(payload.aud); + var aud = payload["aud"]; + if (aud is string[]) { + if (audienceAsArray) { + payloadJson[AUD] = convertStringArrayToJson(aud); + } else { + if (aud.length() == 1) { + payloadJson[AUD] = aud[0]; + } else { + payloadJson[AUD] = convertStringArrayToJson(aud); + } + } + } var customClaims = payload["customClaims"]; if (customClaims is map) { payloadJson = addMapToJson(payloadJson, customClaims); diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal b/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal index f5062b013754..782c98685a9a 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_validator.bal @@ -29,11 +29,11 @@ import ballerina/time; # + certificateAlias - Token signed public key certificate alias # + validateCertificate - Validate public key certificate notBefore and notAfter periods public type JWTValidatorConfig record { - string issuer; - string audience; + string issuer?; + string[] audience?; int clockSkew = 0; - crypto:TrustStore trustStore; - string certificateAlias; + crypto:TrustStore trustStore?; + string certificateAlias?; boolean validateCertificate?; !...; }; @@ -41,7 +41,7 @@ public type JWTValidatorConfig record { # Validity given JWT string. # # + jwtToken - JWT token that need to validate -# + config - JWTValidatorConfig object +# + config - JWT validator config record # + return - If JWT token is valied return the JWT payload. # An error if token validation fails. public function validateJwt(string jwtToken, JWTValidatorConfig config) returns JwtPayload|error { @@ -78,10 +78,7 @@ public function validateJwt(string jwtToken, JWTValidatorConfig config) returns function getJWTComponents(string jwtToken) returns (string[])|error { string[] jwtComponents = jwtToken.split("\\."); - if (jwtComponents.length() != 3) { - log:printDebug(function() returns string { - return "Invalid JWT token :" + jwtToken; - }); + if (jwtComponents.length() < 2 || jwtComponents.length() > 3) { error jwtError = error(AUTH_ERROR_CODE, { message : "Invalid JWT token" }); return jwtError; } @@ -164,7 +161,7 @@ function parseHeader(json jwtHeaderJson) returns (JwtHeader) { function parsePayload(json jwtPayloadJson) returns (JwtPayload) { string[] aud = []; - JwtPayload jwtPayload = { iss: "", sub: "", aud: aud, exp: 0 }; + JwtPayload jwtPayload = {}; map customClaims = {}; string[] keys = jwtPayloadJson.getKeys(); foreach var key in keys { @@ -220,11 +217,6 @@ function validateJwtRecords(string[] encodedJWTComponents, JwtHeader jwtHeader, { message : "Mandatory field signing algorithm(alg) is empty in the given JSON Web Token." }); return jwtError; } - if (!validateMandatoryFields(jwtPayload)) { - error jwtError = error(AUTH_ERROR_CODE, - { message : "Mandatory fields(Issuer,Subject, Expiration time or Audience) are empty in the given JSON Web Token." }); - return jwtError; - } if (config["validateCertificate"] is ()) { config.validateCertificate = true; } @@ -232,28 +224,40 @@ function validateJwtRecords(string[] encodedJWTComponents, JwtHeader jwtHeader, error jwtError = error(AUTH_ERROR_CODE, { message : "Public key certificate validity period has passed" }); return jwtError; } - var signatureValidationResult = validateSignature(encodedJWTComponents, jwtHeader, config); - if (signatureValidationResult is error) { - error jwtError = error(AUTH_ERROR_CODE, { message : signatureValidationResult.reason() }); - return jwtError; + var trustStore = config["trustStore"]; + if (trustStore is crypto:TrustStore) { + var signatureValidationResult = validateSignature(encodedJWTComponents, jwtHeader, config); + if (signatureValidationResult is error) { + return signatureValidationResult; + } } - if (!validateIssuer(jwtPayload, config)) { - error jwtError = error(AUTH_ERROR_CODE, { message : "JWT contained invalid issuer name : " + jwtPayload.iss }); - return jwtError; + var iss = config["issuer"]; + if (iss is string) { + var issuerStatus = validateIssuer(jwtPayload, config); + if (issuerStatus is error) { + return issuerStatus; + } } - if (!validateAudience(jwtPayload, config)) { - //TODO need to set expected audience or available audience list - error jwtError = error(AUTH_ERROR_CODE, { message : "Invalid audience" }); - return jwtError; + var aud = config["audience"]; + if (aud is string[]) { + var audienceStatus = validateAudience(jwtPayload, config); + if (audienceStatus is error) { + return audienceStatus; + } } - if (!validateExpirationTime(jwtPayload, config)) { - error jwtError = error(AUTH_ERROR_CODE, { message : "JWT token is expired" }); - return jwtError; + var exp = jwtPayload["exp"]; + if (exp is int) { + if (!validateExpirationTime(jwtPayload, config)) { + error jwtError = error(AUTH_ERROR_CODE, { message : "JWT token is expired" }); + return jwtError; + } } - //TODO : Validate nbf field of jwtPayload availability first - if (!validateNotBeforeTime(jwtPayload)) { - error jwtError = error(AUTH_ERROR_CODE, { message : "JWT token is used before Not_Before_Time" }); - return jwtError; + var nbf = jwtPayload["nbf"]; + if (nbf is int) { + if (!validateNotBeforeTime(jwtPayload)) { + error jwtError = error(AUTH_ERROR_CODE, { message : "JWT token is used before Not_Before_Time" }); + return jwtError; + } } //TODO : Need to validate jwt id (jti) and custom claims. return true; @@ -266,13 +270,6 @@ function validateMandatoryJwtHeaderFields(JwtHeader jwtHeader) returns (boolean) return true; } -function validateMandatoryFields(JwtPayload jwtPayload) returns (boolean) { - if (jwtPayload.iss == "" || jwtPayload.sub == "" || jwtPayload.exp == 0 || jwtPayload.aud.length() == 0) { - return false; - } - return true; -} - function validateCertificate(JWTValidatorConfig config) returns boolean|error { crypto:PublicKey publicKey = check crypto:decodePublicKey(keyStore = config.trustStore, keyAlias = config.certificateAlias); @@ -292,33 +289,69 @@ function validateCertificate(JWTValidatorConfig config) returns boolean|error { function validateSignature(string[] encodedJWTComponents, JwtHeader jwtHeader, JWTValidatorConfig config) returns boolean|error { - string assertion = encodedJWTComponents[0] + "." + encodedJWTComponents[1]; - byte[] signPart = check encoding:decodeBase64Url(encodedJWTComponents[2]); - crypto:PublicKey publicKey = check crypto:decodePublicKey(keyStore = config.trustStore, - keyAlias = config.certificateAlias); - if (jwtHeader.alg == RS256) { - return crypto:verifyRsaSha256Signature(assertion.toByteArray("UTF-8"), signPart, publicKey); - } else if (jwtHeader.alg == RS384) { - return crypto:verifyRsaSha384Signature(assertion.toByteArray("UTF-8"), signPart, publicKey); - } else if (jwtHeader.alg == RS512) { - return crypto:verifyRsaSha512Signature(assertion.toByteArray("UTF-8"), signPart, publicKey); - } else { - error jwtError = error(AUTH_ERROR_CODE, { message : "Unsupported JWS algorithm" }); + if (jwtHeader.alg == NONE) { + error jwtError = error(AUTH_ERROR_CODE, { message : "Not a valid JWS. Signature algorithm is NONE." }); return jwtError; + } else { + if (encodedJWTComponents.length() == 2) { + error jwtError = error(AUTH_ERROR_CODE, { message : "Not a valid JWS. Signature is required." }); + return jwtError; + } else { + string assertion = encodedJWTComponents[0] + "." + encodedJWTComponents[1]; + byte[] signPart = check encoding:decodeBase64Url(encodedJWTComponents[2]); + crypto:PublicKey publicKey = check crypto:decodePublicKey(keyStore = config.trustStore, + keyAlias = config.certificateAlias); + if (jwtHeader.alg == RS256) { + return crypto:verifyRsaSha256Signature(assertion.toByteArray("UTF-8"), signPart, publicKey); + } else if (jwtHeader.alg == RS384) { + return crypto:verifyRsaSha384Signature(assertion.toByteArray("UTF-8"), signPart, publicKey); + } else if (jwtHeader.alg == RS512) { + return crypto:verifyRsaSha512Signature(assertion.toByteArray("UTF-8"), signPart, publicKey); + } else { + error jwtError = error(AUTH_ERROR_CODE, { message : "Unsupported JWS algorithm" }); + return jwtError; + } + } } } -function validateIssuer(JwtPayload jwtPayload, JWTValidatorConfig config) returns (boolean) { - return jwtPayload.iss == config.issuer; +function validateIssuer(JwtPayload jwtPayload, JWTValidatorConfig config) returns error? { + var iss = jwtPayload["iss"]; + if (iss is string) { + if(jwtPayload.iss != config.issuer) { + error jwtError = error(AUTH_ERROR_CODE, { message : "JWT contained invalid issuer name : " + + jwtPayload.iss }); + return jwtError; + } + } else { + error jwtError = error(AUTH_ERROR_CODE, { message : "JWT must contain a valid issuer name" }); + return jwtError; + } } -function validateAudience(JwtPayload jwtPayload, JWTValidatorConfig config) returns (boolean) { - foreach var audience in jwtPayload.aud { - if (audience == config.audience) { - return true; +function validateAudience(JwtPayload jwtPayload, JWTValidatorConfig config) returns error? { + var aud = jwtPayload["aud"]; + if (aud is string[]) { + boolean validationStatus = false; + foreach var audiencePayload in jwtPayload.aud { + foreach var audienceConfig in config.audience { + if (audiencePayload == audienceConfig) { + validationStatus = true; + break; + } + } + if (validationStatus) { + break; + } } + if (!validationStatus) { + error jwtError = error(AUTH_ERROR_CODE, { message : "Invalid audience" }); + return jwtError; + } + } else { + error jwtError = error(AUTH_ERROR_CODE, { message : "JWT must contain a valid audience" }); + return jwtError; } - return false; } function validateExpirationTime(JwtPayload jwtPayload, JWTValidatorConfig config) returns (boolean) { diff --git a/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JWTAuthenticatorTest.java b/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JWTAuthenticatorTest.java index 7cc0e29d01d4..566eb3af4f33 100644 --- a/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JWTAuthenticatorTest.java +++ b/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JWTAuthenticatorTest.java @@ -131,8 +131,8 @@ private void testGenerateJwt() { BValue[] inputBValues = {jwtHeader, jwtBody, new BString(keyStorePath)}; BValue[] returns = BRunUtil.invoke(compileResult, "generateJwt", inputBValues); Assert.assertTrue(returns[0] instanceof BString); - Assert.assertTrue(returns[0].stringValue().startsWith("eyJhbGciOiJSUzI1NiIsICJ0eXAiOiJKV1QifQ==.eyJzdWI" + - "iOiJKb2huIiwgImlzcyI6IndzbzIiLCAiZXhwIjozMjQ3NTI1MTE4OTAwMCwgImF1ZCI6WyJiYWxsZXJpbmEiXX0=.")); + Assert.assertTrue(returns[0].stringValue().startsWith("eyJhbGciOiJSUzI1NiIsICJ0eXAiOiJKV1QifQ==.eyJzdWIiO" + + "iJKb2huIiwgImlzcyI6IndzbzIiLCAiZXhwIjozMjQ3NTI1MTE4OTAwMCwgImF1ZCI6ImJhbGxlcmluYSJ9.")); jwtToken = returns[0].stringValue(); } diff --git a/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JwtTest.java b/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JwtTest.java index f861acbdcf71..794e712b9fd2 100644 --- a/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JwtTest.java +++ b/stdlib/auth/src/test/java/org/ballerinalang/stdlib/auth/jwt/JwtTest.java @@ -22,15 +22,20 @@ import org.ballerinalang.launcher.util.BRunUtil; import org.ballerinalang.launcher.util.CompileResult; import org.ballerinalang.model.values.BBoolean; +import org.ballerinalang.model.values.BError; +import org.ballerinalang.model.values.BMap; import org.ballerinalang.model.values.BString; import org.ballerinalang.model.values.BValue; +import org.ballerinalang.stdlib.crypto.Constants; import org.testng.Assert; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import java.io.File; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Base64; /** * Test JWT token creation and verification. @@ -40,11 +45,13 @@ public class JwtTest { private String resourceRoot; private CompileResult compileResult; private String jwtToken; + private String jwtTokenWithoutIssAndSub; + private String jwtTokenWithouAudAndSub; private String keyStorePath; private String trustStorePath; @BeforeClass - public void setup() throws Exception { + public void setup() { keyStorePath = getClass().getClassLoader().getResource( "datafiles/keystore/ballerinaKeystore.p12").getPath(); trustStorePath = getClass().getClassLoader().getResource( @@ -56,18 +63,116 @@ public void setup() throws Exception { } @Test(priority = 1, description = "Test case for issuing JWT token with valid data") - public void testIssueJwt() throws Exception { + public void testIssueJwt() { BValue[] inputBValues = {new BString(keyStorePath)}; BValue[] returns = BRunUtil.invoke(compileResult, "testIssueJwt", inputBValues); Assert.assertTrue(returns[0] instanceof BString); jwtToken = returns[0].stringValue(); + String[] parts = jwtToken.split("\\."); + String header = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); + String payload = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); + Assert.assertEquals("{\"alg\":\"RS256\", \"typ\":\"JWT\"}", header); + Assert.assertTrue(payload.startsWith("{\"sub\":\"John\", \"iss\":\"wso2\", \"")); + Assert.assertTrue(payload.endsWith("\", \"aud\":[\"ballerina\", \"ballerinaSamples\"]}")); + } + + @Test(priority = 1, description = "Test case for issuing JWT token with valid data and a single audience") + public void testIssueJwtWithSingleAud() { + BValue[] inputBValues = {new BString(keyStorePath)}; + BValue[] returns = BRunUtil.invoke(compileResult, "testIssueJwtWithSingleAud", inputBValues); + Assert.assertTrue(returns[0] instanceof BString); + String localJwtToken = returns[0].stringValue(); + String[] parts = localJwtToken.split("\\."); + String header = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); + String payload = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); + Assert.assertEquals("{\"alg\":\"RS256\", \"typ\":\"JWT\"}", header); + Assert.assertTrue(payload.startsWith("{\"sub\":\"John\", \"iss\":\"wso2\", \"")); + Assert.assertTrue(payload.endsWith("\", \"aud\":\"ballerina\"}")); + } + + @Test(priority = 1, description = "Test case for issuing JWT token with valid data and a single audience, " + + "but with audienceAsArray enabled") + public void testIssueJwtWithSingleAudAndAudAsArray() { + BValue[] inputBValues = {new BString(keyStorePath)}; + BValue[] returns = BRunUtil.invoke(compileResult, "testIssueJwtWithSingleAudAndAudAsArray", inputBValues); + Assert.assertTrue(returns[0] instanceof BString); + String localJwtToken = returns[0].stringValue(); + String[] parts = localJwtToken.split("\\."); + String header = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); + String payload = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); + Assert.assertEquals("{\"alg\":\"RS256\", \"typ\":\"JWT\"}", header); + Assert.assertTrue(payload.startsWith("{\"sub\":\"John\", \"iss\":\"wso2\", \"")); + Assert.assertTrue(payload.endsWith("\", \"aud\":[\"ballerina\"]}")); + } + + @Test(priority = 1, description = "Test case for issuing JWT token without issuer or subject") + public void testIssueJwtWithNoIssOrSub() { + BValue[] inputBValues = {new BString(keyStorePath)}; + BValue[] returns = BRunUtil.invoke(compileResult, "testIssueJwtWithNoIssOrSub", inputBValues); + Assert.assertTrue(returns[0] instanceof BString); + jwtTokenWithoutIssAndSub = returns[0].stringValue(); + String[] parts = jwtTokenWithoutIssAndSub.split("\\."); + String header = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); + String payload = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); + Assert.assertEquals("{\"alg\":\"RS256\", \"typ\":\"JWT\"}", header); + Assert.assertTrue(payload.startsWith("{\"exp\":")); + Assert.assertTrue(payload.endsWith("\", \"aud\":[\"ballerina\", \"ballerinaSamples\"]}")); + } + + @Test(priority = 1, description = "Test case for issuing JWT token without issuer or audience") + public void testIssueJwtWithNoAudOrSub() { + BValue[] inputBValues = {new BString(keyStorePath)}; + BValue[] returns = BRunUtil.invoke(compileResult, "testIssueJwtWithNoAudOrSub", inputBValues); + Assert.assertTrue(returns[0] instanceof BString); + jwtTokenWithouAudAndSub = returns[0].stringValue(); + String[] parts = jwtTokenWithouAudAndSub.split("\\."); + String header = new String(Base64.getUrlDecoder().decode(parts[0]), StandardCharsets.UTF_8); + String payload = new String(Base64.getUrlDecoder().decode(parts[1]), StandardCharsets.UTF_8); + Assert.assertEquals("{\"alg\":\"RS256\", \"typ\":\"JWT\"}", header); + Assert.assertTrue(payload.startsWith("{\"sub\":\"John\", \"iss\":\"wso2\", \"exp\":")); + Assert.assertTrue(payload.endsWith("\"}")); } @Test(priority = 2, description = "Test case for validating JWT token") - public void testValidateJwt() throws Exception { + public void testCompleteValidator() { BValue[] inputBValues = {new BString(jwtToken), new BString(trustStorePath)}; BValue[] returns = BRunUtil.invoke(compileResult, "testValidateJwt", inputBValues); Assert.assertTrue((returns[0]) instanceof BBoolean); } + @Test(priority = 2, description = "Test case for validating JWT token without issuer or subject information, " + + "using a validator configured to valudate issuer and subject") + public void testCompleteValidatorWithNoIssOrSubNegative() { + BValue[] inputBValues = {new BString(jwtTokenWithoutIssAndSub), new BString(trustStorePath)}; + BValue[] returns = BRunUtil.invoke(compileResult, "testValidateJwt", inputBValues); + Assert.assertTrue((returns[0]) instanceof BError); + Assert.assertEquals(((BMap) ((BError) returns[0]).getDetails()).get(Constants.MESSAGE).stringValue(), + "JWT must contain a valid issuer name"); + } + + @Test(priority = 2, description = "Test case for validating JWT token without issuer or subject information, " + + "using a validator configured to valudate audience and subject") + public void testCompleteValidatorWithNoAudOrSubNegative() { + BValue[] inputBValues = {new BString(jwtTokenWithouAudAndSub), new BString(trustStorePath)}; + BValue[] returns = BRunUtil.invoke(compileResult, "testValidateJwt", inputBValues); + Assert.assertTrue((returns[0]) instanceof BError); + Assert.assertEquals(((BMap) ((BError) returns[0]).getDetails()).get(Constants.MESSAGE).stringValue(), + "JWT must contain a valid audience"); + } + + @Test(priority = 2, description = "Test case for validating JWT token without issuer or subject information, " + + "using a validator configured not to valudate issuer and subject") + public void testPartialValidatorWithNoIssOrSub() { + BValue[] inputBValues = {new BString(jwtTokenWithoutIssAndSub), new BString(trustStorePath)}; + BValue[] returns = BRunUtil.invoke(compileResult, "testValidateJwtWithNoIssOrSub", inputBValues); + Assert.assertTrue((returns[0]) instanceof BBoolean); + } + + @Test(priority = 2, description = "Test case for validating JWT token without issuer or subject information, " + + "using a validator configured to valudate issuer and subject") + public void testPartialValidatorWithIssAndSub() { + BValue[] inputBValues = {new BString(jwtToken), new BString(trustStorePath)}; + BValue[] returns = BRunUtil.invoke(compileResult, "testValidateJwtWithNoIssOrSub", inputBValues); + Assert.assertTrue((returns[0]) instanceof BBoolean); + } } diff --git a/stdlib/auth/src/test/resources/test-src/jwt-authenticator-test.bal b/stdlib/auth/src/test/resources/test-src/jwt-authenticator-test.bal index 16754a13b1fc..62f84f980235 100644 --- a/stdlib/auth/src/test/resources/test-src/jwt-authenticator-test.bal +++ b/stdlib/auth/src/test/resources/test-src/jwt-authenticator-test.bal @@ -5,7 +5,7 @@ function testJwtAuthenticatorCreationWithCache(string trustStorePath) returns (a crypto:TrustStore trustStore = { path: trustStorePath, password: "ballerina" }; auth:JWTAuthProviderConfig jwtConfig = { issuer: "wso2", - audience: "ballerina", + audience: ["ballerina"], certificateAlias: "ballerina", trustStore: trustStore }; @@ -17,7 +17,7 @@ function testAuthenticationSuccess(string jwtToken, string trustStorePath) retur crypto:TrustStore trustStore = { path: trustStorePath, password: "ballerina" }; auth:JWTAuthProviderConfig jwtConfig = { issuer: "wso2", - audience: "ballerina", + audience: ["ballerina"], certificateAlias: "ballerina", trustStore: trustStore }; @@ -27,7 +27,12 @@ function testAuthenticationSuccess(string jwtToken, string trustStorePath) retur function generateJwt(auth:JwtHeader header, auth:JwtPayload payload, string keyStorePath) returns string|error { crypto:KeyStore keyStore = { path: keyStorePath, password: "ballerina" }; - return auth:issueJwt(header, payload, keyStore, "ballerina", "ballerina"); + auth:JWTIssuerConfig issuerConfig = { + keyStore: keyStore, + keyAlias: "ballerina", + keyPassword: "ballerina" + }; + return auth:issueJwt(header, payload, issuerConfig); } function verifyJwt(string jwt, auth:JWTValidatorConfig config) returns auth:JwtPayload|error { diff --git a/stdlib/auth/src/test/resources/test-src/jwt/jwt-test.bal b/stdlib/auth/src/test/resources/test-src/jwt/jwt-test.bal index fa6f7e05f92a..94414a8bbeb6 100644 --- a/stdlib/auth/src/test/resources/test-src/jwt/jwt-test.bal +++ b/stdlib/auth/src/test/resources/test-src/jwt/jwt-test.bal @@ -1,9 +1,16 @@ -import ballerina/internal; -import ballerina/time; import ballerina/auth; import ballerina/crypto; +import ballerina/internal; +import ballerina/time; + +function testIssueJwt(string keyStorePath) returns (string)|error { + crypto:KeyStore keyStore = { path: keyStorePath, password: "ballerina" }; + auth:JWTIssuerConfig config = { + keyStore: keyStore, + keyAlias: "ballerina", + keyPassword: "ballerina" + }; -function testIssueJwt (string keyStorePath) returns (string)|error { auth:JwtHeader header = {}; header.alg = auth:RS256; header.typ = "JWT"; @@ -15,16 +22,118 @@ function testIssueJwt (string keyStorePath) returns (string)|error { payload.aud = ["ballerina", "ballerinaSamples"]; payload.exp = time:currentTime().time/1000 + 600; + return auth:issueJwt(header, payload, config); +} + +function testIssueJwtWithSingleAud(string keyStorePath) returns (string)|error { + crypto:KeyStore keyStore = { path: keyStorePath, password: "ballerina" }; + auth:JWTIssuerConfig config = { + keyStore: keyStore, + keyAlias: "ballerina", + keyPassword: "ballerina" + }; + + auth:JwtHeader header = {}; + header.alg = auth:RS256; + header.typ = "JWT"; + + auth:JwtPayload payload = {}; + payload.sub = "John"; + payload.iss = "wso2"; + payload.jti = "100078234ba23"; + payload.aud = ["ballerina"]; + payload.exp = time:currentTime().time/1000 + 600; + + return auth:issueJwt(header, payload, config); +} + +function testIssueJwtWithSingleAudAndAudAsArray(string keyStorePath) returns (string)|error { crypto:KeyStore keyStore = { path: keyStorePath, password: "ballerina" }; - return auth:issueJwt(header, payload, keyStore, "ballerina", "ballerina"); + auth:JWTIssuerConfig config = { + keyStore: keyStore, + keyAlias: "ballerina", + keyPassword: "ballerina", + audienceAsArray: true + }; + + auth:JwtHeader header = {}; + header.alg = auth:RS256; + header.typ = "JWT"; + + auth:JwtPayload payload = {}; + payload.sub = "John"; + payload.iss = "wso2"; + payload.jti = "100078234ba23"; + payload.aud = ["ballerina"]; + payload.exp = time:currentTime().time/1000 + 600; + + return auth:issueJwt(header, payload, config); } -function testValidateJwt (string jwtToken, string trustStorePath) returns boolean|error { +function testValidateJwt(string jwtToken, string trustStorePath) returns boolean|error { crypto:TrustStore trustStore = { path: trustStorePath, password: "ballerina" }; auth:JWTValidatorConfig config = { issuer: "wso2", certificateAlias: "ballerina", - audience: "ballerinaSamples", + audience: ["ballerinaSamples"], + clockSkew: 60, + trustStore: trustStore + }; + + var result = auth:validateJwt(jwtToken, config); + if (result is auth:JwtPayload) { + return true; + } else { + return result; + } +} + +function testIssueJwtWithNoIssOrSub(string keyStorePath) returns (string)|error { + crypto:KeyStore keyStore = { path: keyStorePath, password: "ballerina" }; + auth:JWTIssuerConfig config = { + keyStore: keyStore, + keyAlias: "ballerina", + keyPassword: "ballerina" + }; + + auth:JwtHeader header = {}; + header.alg = auth:RS256; + header.typ = "JWT"; + + auth:JwtPayload payload = {}; + payload.jti = "100078234ba23"; + payload.aud = ["ballerina", "ballerinaSamples"]; + payload.exp = time:currentTime().time/1000 + 600; + + return auth:issueJwt(header, payload, config); +} + +function testIssueJwtWithNoAudOrSub(string keyStorePath) returns (string)|error { + crypto:KeyStore keyStore = { path: keyStorePath, password: "ballerina" }; + auth:JWTIssuerConfig config = { + keyStore: keyStore, + keyAlias: "ballerina", + keyPassword: "ballerina" + }; + + auth:JwtHeader header = {}; + header.alg = auth:RS256; + header.typ = "JWT"; + + auth:JwtPayload payload = {}; + payload.sub = "John"; + payload.iss = "wso2"; + payload.jti = "100078234ba23"; + payload.exp = time:currentTime().time/1000 + 600; + + return auth:issueJwt(header, payload, config); +} + +function testValidateJwtWithNoIssOrSub(string jwtToken, string trustStorePath) returns boolean|error { + crypto:TrustStore trustStore = { path: trustStorePath, password: "ballerina" }; + auth:JWTValidatorConfig config = { + certificateAlias: "ballerina", + audience: ["ballerinaSamples"], clockSkew: 60, trustStore: trustStore }; diff --git a/stdlib/http/src/main/ballerina/http/http_secure_client.bal b/stdlib/http/src/main/ballerina/http/http_secure_client.bal index e7a7e24945de..f7c6979344f2 100644 --- a/stdlib/http/src/main/ballerina/http/http_secure_client.bal +++ b/stdlib/http/src/main/ballerina/http/http_secure_client.bal @@ -347,8 +347,12 @@ function generateSecureRequest(Request req, ClientEndpointConfig config) returns jti: system:uuid(), aud: jwtIssuerConfig.audience }; - var token = auth:issueJwt(header, payload, jwtIssuerConfig.keyStore, jwtIssuerConfig.keyAlias, - jwtIssuerConfig.keyPassword); + auth:JWTIssuerConfig issuerConfig = { + keyStore: jwtIssuerConfig.keyStore, + keyAlias: jwtIssuerConfig.keyAlias, + keyPassword: jwtIssuerConfig.keyPassword + }; + var token = auth:issueJwt(header, payload, issuerConfig); // TODO: cache the token per-user per-client and reuse it if (token is string) { authToken = token; From 6968a8ff66d85a6af4191151ea1f72f6b08fa345 Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Wed, 20 Feb 2019 11:47:28 +0530 Subject: [PATCH 19/26] Correct auth tests according to new issuer --- .../ballerinalang/stdlib/auth/JWTAuthnHandlerTest.java | 4 ++-- .../resources/test-src/auth/jwt-authn-handler-test.bal | 9 +++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/stdlib/http/src/test/java/org/ballerinalang/stdlib/auth/JWTAuthnHandlerTest.java b/stdlib/http/src/test/java/org/ballerinalang/stdlib/auth/JWTAuthnHandlerTest.java index e389d3be5a78..60c7c903e9b6 100644 --- a/stdlib/http/src/test/java/org/ballerinalang/stdlib/auth/JWTAuthnHandlerTest.java +++ b/stdlib/http/src/test/java/org/ballerinalang/stdlib/auth/JWTAuthnHandlerTest.java @@ -128,8 +128,8 @@ public void setup() throws Exception { BValue[] inputBValues = {jwtHeader, jwtBody, new BString(keyStorePath)}; BValue[] returns = BRunUtil.invoke(compileResult, "generateJwt", inputBValues); Assert.assertTrue(returns[0] instanceof BString); - Assert.assertTrue(returns[0].stringValue().startsWith("eyJhbGciOiJSUzI1NiIsICJ0eXAiOiJKV1QifQ==.eyJzdWI" + - "iOiJKb2huIiwgImlzcyI6IndzbzIiLCAiZXhwIjozMjQ3NTI1MTE4OTAwMCwgImF1ZCI6WyJiYWxsZXJpbmEiXX0=.")); + Assert.assertTrue(returns[0].stringValue().startsWith("eyJhbGciOiJSUzI1NiIsICJ0eXAiOiJKV1QifQ==.eyJzdWIiO" + + "iJKb2huIiwgImlzcyI6IndzbzIiLCAiZXhwIjozMjQ3NTI1MTE4OTAwMCwgImF1ZCI6ImJhbGxlcmluYSJ9.")); jwtToken = returns[0].stringValue(); } diff --git a/stdlib/http/src/test/resources/test-src/auth/jwt-authn-handler-test.bal b/stdlib/http/src/test/resources/test-src/auth/jwt-authn-handler-test.bal index 1594d95e736d..be8786aa751f 100644 --- a/stdlib/http/src/test/resources/test-src/auth/jwt-authn-handler-test.bal +++ b/stdlib/http/src/test/resources/test-src/auth/jwt-authn-handler-test.bal @@ -49,7 +49,7 @@ function createJwtAuthProvider(string trustStorePath) returns auth:JWTAuthProvid }; auth:JWTAuthProviderConfig jwtConfig = { issuer: "wso2", - audience: "ballerina", + audience: ["ballerina"], certificateAlias: "ballerina", trustStore: trustStore }; @@ -59,7 +59,12 @@ function createJwtAuthProvider(string trustStorePath) returns auth:JWTAuthProvid function generateJwt(auth:JwtHeader header, auth:JwtPayload payload, string keyStorePath) returns string|error { crypto:KeyStore keyStore = { path: keyStorePath, password: "ballerina" }; - return auth:issueJwt(header, payload, keyStore, "ballerina", "ballerina"); + auth:JWTIssuerConfig issuerConfig = { + keyStore: keyStore, + keyAlias: "ballerina", + keyPassword: "ballerina" + }; + return auth:issueJwt(header, payload, issuerConfig); } function verifyJwt(string jwt, auth:JWTValidatorConfig config) returns auth:JwtPayload|error { From 9513f1f6f288104181039f741c494fec04ace336 Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Thu, 21 Feb 2019 11:18:14 +0530 Subject: [PATCH 20/26] Correct auth integration tests --- .../main/ballerina/http/service_endpoint.bal | 23 +++++++++++-------- .../08_authn_with_multiple_providers.bal | 4 ++-- .../09_authn_with_different_scopes.bal | 2 +- .../10_token_propagation_disabled_test.bal | 2 +- .../11_token_propagation_basic_auth_test.bal | 4 ++-- .../13_authn_with_expired_certificate.bal | 2 +- ..._certificate_with_no_expiry_validation.bal | 2 +- .../15_token_propagation_jwt_test.bal | 6 ++--- ...6_token_propagation_jwt_reissuing_test.bal | 6 ++--- ...ropagation_jwt_reissuing_negative_test.bal | 6 ++--- 10 files changed, 30 insertions(+), 27 deletions(-) diff --git a/stdlib/http/src/main/ballerina/http/service_endpoint.bal b/stdlib/http/src/main/ballerina/http/service_endpoint.bal index f72cbfecc60a..29d78487bf79 100644 --- a/stdlib/http/src/main/ballerina/http/service_endpoint.bal +++ b/stdlib/http/src/main/ballerina/http/service_endpoint.bal @@ -73,14 +73,17 @@ public type Listener object { public function Listener.init(ServiceEndpointConfiguration c) { self.config = c; - var providers = self.config.authProviders; - if (providers.length() > 0) { - var secureSocket = self.config.secureSocket; - if (secureSocket is ServiceSecureSocket) { - addAuthFiltersForSecureListener(self.config, self.instanceId); - } else { - error err = error("Secure sockets have not been cofigured in order to enable auth providers."); - panic err; + var authProviders = self.config.authProviders; + if (authProviders is AuthProvider[]) { + var providers = authProviders; + if (providers.length() > 0) { + var secureSocket = self.config.secureSocket; + if (secureSocket is ServiceSecureSocket) { + addAuthFiltersForSecureListener(self.config, self.instanceId); + } else { + error err = error("Secure sockets have not been cofigured in order to enable auth providers."); + panic err; + } } } var err = self.initEndpoint(); @@ -152,7 +155,7 @@ public type ServiceEndpointConfiguration record { Filter[] filters = []; int timeoutMillis = DEFAULT_LISTENER_TIMEOUT; int maxPipelinedRequests = MAX_PIPELINED_REQUESTS; - AuthProvider?[] authProviders = []; + AuthProvider[]? authProviders = (); AuthCacheConfig positiveAuthzCache = {}; AuthCacheConfig negativeAuthzCache = {}; !...; @@ -218,7 +221,7 @@ public type AuthCacheConfig record { # + jwtAuthProviderConfig - JWT auth provider related configurations public type AuthProvider record { string id = ""; - InboundAuthScheme scheme; + InboundAuthScheme? scheme = (); AuthStoreProvider? authStoreProvider = (); auth:LdapAuthProviderConfig? ldapAuthProviderConfig = (); auth:ConfigAuthProviderConfig? configAuthProviderConfig = (); diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/08_authn_with_multiple_providers.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/08_authn_with_multiple_providers.bal index 30572fe9d831..c667b6832487 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/08_authn_with_multiple_providers.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/08_authn_with_multiple_providers.bal @@ -20,7 +20,7 @@ http:AuthProvider jwtAuthProvider1 = { scheme: http:JWT_AUTH, jwtAuthProviderConfig: { issuer: "example1", - audience: "ballerina", + audience: ["ballerina"], certificateAlias: "ballerina", trustStore: { path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", @@ -33,7 +33,7 @@ http:AuthProvider jwtAuthProvider2 = { scheme: http:JWT_AUTH, jwtAuthProviderConfig: { issuer: "example2", - audience: "ballerina", + audience: ["ballerina"], certificateAlias: "ballerina", trustStore: { path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/09_authn_with_different_scopes.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/09_authn_with_different_scopes.bal index 2da76b8b2a59..ff8e1c30e6a5 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/09_authn_with_different_scopes.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/09_authn_with_different_scopes.bal @@ -20,7 +20,7 @@ http:AuthProvider jwtAuthProvider3 = { scheme: http:JWT_AUTH, jwtAuthProviderConfig: { issuer: "ballerina", - audience: "ballerina", + audience: ["ballerina"], certificateAlias: "ballerina", trustStore: { path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/10_token_propagation_disabled_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/10_token_propagation_disabled_test.bal index 61a7f8934576..8f414201bfbb 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/10_token_propagation_disabled_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/10_token_propagation_disabled_test.bal @@ -60,7 +60,7 @@ http:AuthProvider jwtAuthProvider10 = { scheme: http:JWT_AUTH, jwtAuthProviderConfig: { issuer: "ballerina", - audience: "ballerina", + audience: ["ballerina"], certificateAlias: "ballerina", trustStore: { path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/11_token_propagation_basic_auth_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/11_token_propagation_basic_auth_test.bal index 9da1b1339744..123e05f99e22 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/11_token_propagation_basic_auth_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/11_token_propagation_basic_auth_test.bal @@ -60,7 +60,7 @@ service passthroughService03 on listener11 { var response = nyseEP03->get("/nyseStock/stocks", message = untaint clientRequest); if (response is http:Response) { _ = caller->respond(response); - } else if (response is error) { + } else { http:Response resp = new; json errMsg = { "error": "error occurred while invoking the service: " + response.reason() }; resp.statusCode = 500; @@ -74,7 +74,7 @@ http:AuthProvider jwtAuthProvider03 = { scheme: http:JWT_AUTH, jwtAuthProviderConfig: { issuer: "ballerina", - audience: "ballerina", + audience: ["ballerina"], certificateAlias: "ballerina", trustStore: { path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/13_authn_with_expired_certificate.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/13_authn_with_expired_certificate.bal index c02f5cfa5d2d..878ac9984d0d 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/13_authn_with_expired_certificate.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/13_authn_with_expired_certificate.bal @@ -20,7 +20,7 @@ http:AuthProvider jwtAuthProvider4 = { scheme: http:JWT_AUTH, jwtAuthProviderConfig: { issuer:"ballerina", - audience: "ballerina.io", + audience: ["ballerina.io"], certificateAlias: "cert", trustStore: { path: "../../../src/test/resources/auth/testtruststore.p12", diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/14_authn_with_expired_certificate_with_no_expiry_validation.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/14_authn_with_expired_certificate_with_no_expiry_validation.bal index cd4342410a58..59e533eed18f 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/14_authn_with_expired_certificate_with_no_expiry_validation.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/14_authn_with_expired_certificate_with_no_expiry_validation.bal @@ -20,7 +20,7 @@ http:AuthProvider jwtAuthProvider14 = { scheme: http:JWT_AUTH, jwtAuthProviderConfig: { issuer:"ballerina", - audience: "ballerina.io", + audience: ["ballerina.io"], certificateAlias: "cert", validateCertificate: false, trustStore: { diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/15_token_propagation_jwt_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/15_token_propagation_jwt_test.bal index e4ea7e85f60b..150a5ed7d21d 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/15_token_propagation_jwt_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/15_token_propagation_jwt_test.bal @@ -20,7 +20,7 @@ http:AuthProvider basicAuthProvider15 = { scheme: http:JWT_AUTH, jwtAuthProviderConfig: { issuer: "example1", - audience: "ballerina", + audience: ["ballerina"], certificateAlias: "ballerina", trustStore: { path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", @@ -56,7 +56,7 @@ service passthroughService15 on listener15_1 { var response = nyseEP15->get("/nyseStock/stocks", message = untaint clientRequest); if (response is http:Response) { _ = caller->respond(response); - } else if (response is error) { + } else { http:Response resp = new; json errMsg = { "error": "error occurred while invoking the service: " + response.reason() }; resp.statusCode = 500; @@ -70,7 +70,7 @@ http:AuthProvider jwtAuthProvider15 = { scheme: http:JWT_AUTH, jwtAuthProviderConfig: { issuer: "example1", - audience: "ballerina", + audience: ["ballerina"], certificateAlias: "ballerina", trustStore: { path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/16_token_propagation_jwt_reissuing_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/16_token_propagation_jwt_reissuing_test.bal index 467dc912a24b..869eea95be8a 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/16_token_propagation_jwt_reissuing_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/16_token_propagation_jwt_reissuing_test.bal @@ -20,7 +20,7 @@ http:AuthProvider basicAuthProvider16 = { scheme: http:JWT_AUTH, jwtAuthProviderConfig: { issuer: "example1", - audience: "ballerina", + audience: ["ballerina"], certificateAlias: "ballerina", trustStore: { path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", @@ -68,7 +68,7 @@ service passthroughService16 on listener16_1 { var response = nyseEP16->get("/nyseStock/stocks", message = untaint clientRequest); if (response is http:Response) { _ = caller->respond(response); - } else if (response is error) { + } else { http:Response resp = new; json errMsg = { "error": "error occurred while invoking the service: " + response.reason() }; resp.statusCode = 500; @@ -82,7 +82,7 @@ http:AuthProvider jwtAuthProvider16 = { scheme: http:JWT_AUTH, jwtAuthProviderConfig: { issuer: "example2", - audience: "ballerina", + audience: ["ballerina"], certificateAlias: "ballerina", trustStore: { path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/17_token_propagation_jwt_reissuing_negative_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/17_token_propagation_jwt_reissuing_negative_test.bal index ed62df425d0a..a0a0fe5b3e7b 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/17_token_propagation_jwt_reissuing_negative_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/17_token_propagation_jwt_reissuing_negative_test.bal @@ -20,7 +20,7 @@ http:AuthProvider basicAuthProvider17 = { scheme: http:JWT_AUTH, jwtAuthProviderConfig: { issuer: "example1", - audience: "ballerina", + audience: ["ballerina"], certificateAlias: "ballerina", trustStore: { path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", @@ -68,7 +68,7 @@ service passthroughService17 on listener17_1 { var response = nyseEP17->get("/nyseStock/stocks", message = untaint clientRequest); if (response is http:Response) { _ = caller->respond(response); - } else if (response is error) { + } else { http:Response resp = new; json errMsg = { "error": "error occurred while invoking the service: " + response.reason() }; resp.statusCode = 500; @@ -82,7 +82,7 @@ http:AuthProvider jwtAuthProvider17 = { scheme: http:JWT_AUTH, jwtAuthProviderConfig: { issuer: "example2aaaaaaaaaaaaaa", - audience: "ballerina", + audience: ["ballerina"], certificateAlias: "ballerina", trustStore: { path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", From e2a6d556dafa0c767b762f7c1cf59d6781df5489 Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Thu, 21 Feb 2019 11:40:21 +0530 Subject: [PATCH 21/26] Update doc comments --- .../src/main/ballerina/auth/jwt_issuer.bal | 4 +- .../test-src/config_auth_provider_test.bal | 16 +++ .../test-src/jwt-authenticator-test.bal | 16 +++ .../test/resources/test-src/jwt/jwt-test.bal | 16 +++ .../stdlib/crypto/Constants.java | 2 +- .../stdlib/crypto/CryptoUtils.java | 85 +++++++------ .../nativeimpl/ByteArrayToString.java | 3 +- .../encoding/nativeimpl/DecodeBase64.java | 3 +- .../encoding/nativeimpl/DecodeBase64Url.java | 5 +- .../stdlib/encoding/nativeimpl/DecodeHex.java | 3 +- .../encoding/nativeimpl/EncodeBase64.java | 3 +- .../encoding/nativeimpl/EncodeBase64Url.java | 5 +- .../stdlib/encoding/nativeimpl/EncodeHex.java | 3 +- .../dependency-reduced-pom.xml | 117 ------------------ 14 files changed, 109 insertions(+), 172 deletions(-) delete mode 100644 tests/ballerina-integration-test-utils/dependency-reduced-pom.xml diff --git a/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal b/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal index 9b03c216ec3b..fe808c7bc456 100644 --- a/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal +++ b/stdlib/auth/src/main/ballerina/auth/jwt_issuer.bal @@ -18,6 +18,7 @@ import ballerina/crypto; import ballerina/encoding; # Represents JWT issuer configurations. +# # + keyStore - Keystore to be used in JWT signing # + keyAlias - Signing key alias # + keyPassword - Signing key password @@ -30,7 +31,8 @@ public type JWTIssuerConfig record { !...; }; -# Issue a JWT token. +# Issue a JWT token based on provided header and payload. JWT will be signed (JWS) if `keyStore` information is provided +# in the `JWTIssuerConfig` and the `alg` field of `JwtHeader` is not `NONE`. # # + header - JwtHeader object # + payload - JwtPayload object diff --git a/stdlib/auth/src/test/resources/test-src/config_auth_provider_test.bal b/stdlib/auth/src/test/resources/test-src/config_auth_provider_test.bal index 65cc7dc94ac4..a003469842e6 100644 --- a/stdlib/auth/src/test/resources/test-src/config_auth_provider_test.bal +++ b/stdlib/auth/src/test/resources/test-src/config_auth_provider_test.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2018 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. + import ballerina/auth; function testCreateConfigAuthProvider() returns (auth:ConfigAuthStoreProvider) { diff --git a/stdlib/auth/src/test/resources/test-src/jwt-authenticator-test.bal b/stdlib/auth/src/test/resources/test-src/jwt-authenticator-test.bal index 62f84f980235..9fad67669fd6 100644 --- a/stdlib/auth/src/test/resources/test-src/jwt-authenticator-test.bal +++ b/stdlib/auth/src/test/resources/test-src/jwt-authenticator-test.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2018 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. + import ballerina/auth; import ballerina/crypto; diff --git a/stdlib/auth/src/test/resources/test-src/jwt/jwt-test.bal b/stdlib/auth/src/test/resources/test-src/jwt/jwt-test.bal index 94414a8bbeb6..00803675a0dc 100644 --- a/stdlib/auth/src/test/resources/test-src/jwt/jwt-test.bal +++ b/stdlib/auth/src/test/resources/test-src/jwt/jwt-test.bal @@ -1,3 +1,19 @@ +// Copyright (c) 2018 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. + import ballerina/auth; import ballerina/crypto; import ballerina/internal; diff --git a/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/Constants.java b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/Constants.java index d29b585ae45d..68fc10aa9f28 100644 --- a/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/Constants.java +++ b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/Constants.java @@ -21,7 +21,7 @@ /** * Constants related to Ballerina crypto stdlib. * - * @since 0.991.0 + * @since 0.990.3 */ public class Constants { diff --git a/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/CryptoUtils.java b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/CryptoUtils.java index 9179703e013f..5068541936d4 100644 --- a/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/CryptoUtils.java +++ b/stdlib/crypto/src/main/java/org/ballerinalang/stdlib/crypto/CryptoUtils.java @@ -177,43 +177,6 @@ public static BError createCryptoError(Context context, String errMsg) { return BLangVMErrors.createError(context, true, BTypes.typeError, Constants.ENCODING_ERROR_CODE, errorRecord); } - public static String substituteVariables(String value) { - Matcher matcher = varPattern.matcher(value); - boolean found = matcher.find(); - if (!found) { - return value; - } else { - StringBuffer sb = new StringBuffer(); - - do { - String sysPropKey = matcher.group(1); - String sysPropValue = getSystemVariableValue(sysPropKey, null); - if (sysPropValue == null || sysPropValue.length() == 0) { - throw new RuntimeException("System property " + sysPropKey + " is not specified"); - } - - sysPropValue = sysPropValue.replace("\\", "\\\\"); - matcher.appendReplacement(sb, sysPropValue); - } while(matcher.find()); - - matcher.appendTail(sb); - return sb.toString(); - } - } - - public static String getSystemVariableValue(String variableName, String defaultValue) { - String value; - if (System.getProperty(variableName) != null) { - value = System.getProperty(variableName); - } else if (System.getenv(variableName) != null) { - value = System.getenv(variableName); - } else { - value = defaultValue; - } - - return value; - } - /** * Encrypt or decrypt byte array based on RSA algorithm. * @@ -415,4 +378,52 @@ private static String transformAlgorithmPadding(Context context, String algorith } return algorithmPadding; } + + /** + * Replace system property holders in the property values. + * e.g. Replace ${ballerina.home} with value of the ballerina.home system property. + * + * This logic is originally from http-transport-utils. Since, HTTP stdlib depends on http-transport, + * HTTP stdlib directly uses this method form the original utility. This is added here, not to make Auth stdlib + * depend on http-transport. + * + * @param value string value to substitute + * @return String substituted string + */ + public static String substituteVariables(String value) { + Matcher matcher = varPattern.matcher(value); + boolean found = matcher.find(); + if (!found) { + return value; + } else { + StringBuffer sb = new StringBuffer(); + + do { + String sysPropKey = matcher.group(1); + String sysPropValue = getSystemVariableValue(sysPropKey, null); + if (sysPropValue == null || sysPropValue.length() == 0) { + throw new RuntimeException("System property " + sysPropKey + " is not specified"); + } + + sysPropValue = sysPropValue.replace("\\", "\\\\"); + matcher.appendReplacement(sb, sysPropValue); + } while(matcher.find()); + + matcher.appendTail(sb); + return sb.toString(); + } + } + + private static String getSystemVariableValue(String variableName, String defaultValue) { + String value; + if (System.getProperty(variableName) != null) { + value = System.getProperty(variableName); + } else if (System.getenv(variableName) != null) { + value = System.getenv(variableName); + } else { + value = defaultValue; + } + + return value; + } } diff --git a/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/ByteArrayToString.java b/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/ByteArrayToString.java index d3ffb0d7f0ff..e936d60a8372 100644 --- a/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/ByteArrayToString.java +++ b/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/ByteArrayToString.java @@ -36,8 +36,7 @@ * @since 0.980 */ @BallerinaFunction( - orgName = "ballerina", packageName = "encoding", - functionName = "byteArrayToString", + orgName = "ballerina", packageName = "encoding", functionName = "byteArrayToString", args = { @Argument(name = "content", type = TypeKind.ARRAY, elementType = TypeKind.BYTE), @Argument(name = "encoding", type = TypeKind.STRING) diff --git a/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/DecodeBase64.java b/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/DecodeBase64.java index 5a016c1f02fe..f3474bd6eb6a 100644 --- a/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/DecodeBase64.java +++ b/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/DecodeBase64.java @@ -36,8 +36,7 @@ * @since 0.990.3 */ @BallerinaFunction( - orgName = "ballerina", packageName = "encoding", - functionName = "decodeBase64", + orgName = "ballerina", packageName = "encoding", functionName = "decodeBase64", args = { @Argument(name = "input", type = TypeKind.STRING) }, diff --git a/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/DecodeBase64Url.java b/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/DecodeBase64Url.java index 58e9c3b9ded8..4e6bee1d0c92 100644 --- a/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/DecodeBase64Url.java +++ b/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/DecodeBase64Url.java @@ -31,13 +31,12 @@ import java.util.Base64; /** - * Extern function ballerina.encoding:decodeBase64. + * Extern function ballerina.encoding:decodeBase64Url. * * @since 0.991.0 */ @BallerinaFunction( - orgName = "ballerina", packageName = "encoding", - functionName = "decodeBase64Url", + orgName = "ballerina", packageName = "encoding", functionName = "decodeBase64Url", args = { @Argument(name = "input", type = TypeKind.STRING) }, diff --git a/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/DecodeHex.java b/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/DecodeHex.java index e1656ddfa09b..88f1e3a238dd 100644 --- a/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/DecodeHex.java +++ b/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/DecodeHex.java @@ -34,8 +34,7 @@ * @since 0.990.3 */ @BallerinaFunction( - orgName = "ballerina", packageName = "encoding", - functionName = "decodeHex", + orgName = "ballerina", packageName = "encoding", functionName = "decodeHex", args = { @Argument(name = "input", type = TypeKind.STRING) }, diff --git a/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/EncodeBase64.java b/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/EncodeBase64.java index c75165387068..804dfe5b281b 100644 --- a/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/EncodeBase64.java +++ b/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/EncodeBase64.java @@ -36,8 +36,7 @@ * @since 0.990.3 */ @BallerinaFunction( - orgName = "ballerina", packageName = "encoding", - functionName = "encodeBase64", + orgName = "ballerina", packageName = "encoding", functionName = "encodeBase64", args = { @Argument(name = "input", type = TypeKind.ARRAY, elementType = TypeKind.BYTE) }, diff --git a/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/EncodeBase64Url.java b/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/EncodeBase64Url.java index 3d3e20ee094a..84fdac921cbb 100644 --- a/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/EncodeBase64Url.java +++ b/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/EncodeBase64Url.java @@ -31,13 +31,12 @@ import java.util.Base64; /** - * Extern function ballerina.encoding:encodeBase64. + * Extern function ballerina.encoding:encodeBase64Url. * * @since 0.991.0 */ @BallerinaFunction( - orgName = "ballerina", packageName = "encoding", - functionName = "encodeBase64Url", + orgName = "ballerina", packageName = "encoding", functionName = "encodeBase64Url", args = { @Argument(name = "input", type = TypeKind.ARRAY, elementType = TypeKind.BYTE) }, diff --git a/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/EncodeHex.java b/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/EncodeHex.java index 3dd9a8e28a47..0b1cb1340cfc 100644 --- a/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/EncodeHex.java +++ b/stdlib/encoding/src/main/java/org/ballerinalang/stdlib/encoding/nativeimpl/EncodeHex.java @@ -34,8 +34,7 @@ * @since 0.990.3 */ @BallerinaFunction( - orgName = "ballerina", packageName = "encoding", - functionName = "encodeHex", + orgName = "ballerina", packageName = "encoding", functionName = "encodeHex", args = { @Argument(name = "input", type = TypeKind.ARRAY, elementType = TypeKind.BYTE) }, diff --git a/tests/ballerina-integration-test-utils/dependency-reduced-pom.xml b/tests/ballerina-integration-test-utils/dependency-reduced-pom.xml deleted file mode 100644 index c85c4ff66255..000000000000 --- a/tests/ballerina-integration-test-utils/dependency-reduced-pom.xml +++ /dev/null @@ -1,117 +0,0 @@ - - - - ballerina-parent - org.ballerinalang - 0.990.3-SNAPSHOT - ../../pom.xml - - 4.0.0 - ballerina-integration-test-utils - Ballerina - Integration Test Utils - http://ballerina-lang.org - - - - kr.motd.maven - os-maven-plugin - ${os.maven.plugin.version} - - - - - maven-jar-plugin - - - - org.ballerinalang.test.agent.BallerinaServerAgent - - - - - - maven-shade-plugin - 3.1.1 - - - package - - shade - - - - - org.apache.mina:mina-core - org.slf4j:slf4j-api - org.apache.commons:commons-lang3 - org.wso2.transport.http:org.wso2.transport.http.netty - org.wso2.eclipse.osgi:org.eclipse.osgi - org.wso2.eclipse.osgi:org.eclipse.osgi.services - io.netty:netty-common - io.netty:netty-buffer - io.netty:netty-transport - io.netty:netty-handler - io.netty:netty-codec - io.netty:netty-handler-proxy - io.netty:netty-codec-socks - io.netty:netty-codec-http2 - io.netty:netty-resolver - commons-pool.wso2:commons-pool - commons-pool:commons-pool - org.wso2.orbit.org.yaml:snakeyaml - org.bouncycastle:bcprov-jdk15on - org.bouncycastle:bcpkix-jdk15on - io.netty:netty-tcnative-boringssl-static - org.testng:testng - com.beust:jcommander - io.netty:netty-codec-http - io.netty:netty-transport-native-unix-common - META-INF/*.SF - META-INF/*.DSA - META-INF/*.RSA - - - - - - - - - - - org.apache.mina - mina-core - 2.0.16 - compile - - - org.apache.commons - commons-lang3 - 3.5 - compile - - - org.wso2.transport.http - org.wso2.transport.http.netty - 6.0.259 - compile - - - org.testng - testng - 6.13.1 - compile - - - io.netty - netty-codec-http - 4.1.19.Final - compile - - - - spotbugs-exclude.xml - **/generated/**, - **/generated-sources/** - - From 1d9444751225204100a631475c5d7ffe6f6c5f99 Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Fri, 22 Feb 2019 14:31:51 +0530 Subject: [PATCH 22/26] Correct auth examples --- .../secured_client_with_basic_auth.bal | 10 ++++++---- .../secured_client_with_jwt_auth.bal | 16 +++++++++------- .../secured_client_with_oauth2.bal | 12 +++++++----- .../secured_service_with_basic_auth.bal | 4 ++-- .../secured_service_with_basic_auth_test.bal | 18 ++++++++++++------ .../secured_service_with_jwt.bal | 16 +++++++++------- .../main/ballerina/http/http_secure_client.bal | 2 +- 7 files changed, 46 insertions(+), 32 deletions(-) diff --git a/examples/secured-client-with-basic-auth/secured_client_with_basic_auth.bal b/examples/secured-client-with-basic-auth/secured_client_with_basic_auth.bal index 42423bc8d7e2..0a59b18a74c5 100644 --- a/examples/secured-client-with-basic-auth/secured_client_with_basic_auth.bal +++ b/examples/secured-client-with-basic-auth/secured_client_with_basic_auth.bal @@ -8,8 +8,10 @@ import ballerina/log; http:Client httpEndpoint = new("https://localhost:9090", config = { auth: { scheme: http:BASIC_AUTH, - username: "tom", - password: "1234" + basicAuthConfig: { + username: "tom", + password: "1234" + } } }); @@ -30,8 +32,8 @@ public function main() { // Create a basic authentication provider with the relevant configurations. http:AuthProvider basicAuthProvider = { - scheme: "basic", - authStoreProvider: "config" + scheme: http:BASIC_AUTH, + authStoreProvider: http:CONFIG_AUTH_STORE }; listener http:Listener ep = new(9090, config = { diff --git a/examples/secured-client-with-jwt-auth/secured_client_with_jwt_auth.bal b/examples/secured-client-with-jwt-auth/secured_client_with_jwt_auth.bal index 88f9109140fc..bfdb2143c183 100644 --- a/examples/secured-client-with-jwt-auth/secured_client_with_jwt_auth.bal +++ b/examples/secured-client-with-jwt-auth/secured_client_with_jwt_auth.bal @@ -39,13 +39,15 @@ public function main() { // Create a JWT authentication provider with the relevant configurations. http:AuthProvider jwtAuthProvider = { - scheme: "jwt", - issuer: "ballerina", - audience: "ballerina.io", - certificateAlias: "ballerina", - trustStore: { - path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", - password: "ballerina" + scheme: http:JWT_AUTH, + jwtAuthProviderConfig: { + issuer: "ballerina", + audience: ["ballerina.io"], + certificateAlias: "ballerina", + trustStore: { + path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", + password: "ballerina" + } } }; diff --git a/examples/secured-client-with-oauth2/secured_client_with_oauth2.bal b/examples/secured-client-with-oauth2/secured_client_with_oauth2.bal index 48500b18053f..37002d9d4a46 100644 --- a/examples/secured-client-with-oauth2/secured_client_with_oauth2.bal +++ b/examples/secured-client-with-oauth2/secured_client_with_oauth2.bal @@ -8,11 +8,13 @@ import ballerina/log; http:Client httpEndpoint = new("https://www.googleapis.com/tasks/v1", config = { auth: { scheme: http:OAUTH2, - accessToken: "ya29.GlufBimE7JZdiB_FpFtZn7p1WMtloVeMlqiYXDGF97068VvJCyK8rEFqBBkxT10E0qudipwxTjJTkU4we0hbOcHKjNTXz6JTEZYoRVn7F3-0O_bL9g71Rwek7TFI", - clientId: "833478926540-va43h2lhdhfc06i9eivlmaehl3o5uk1i.apps.googleusercontent.com", - clientSecret: "4ZsV4gwSuIoRdy6TKUXTanlw", - refreshToken: "1/XUtrd8DaeoopmX5xpIvGdXY09VAY6_h8fVVj9xCaKJE", - refreshUrl: "https://www.googleapis.com/oauth2/v4/token" + oAuth2AuthConfig: { + accessToken: "ya29.GlufBimE7JZdiB_FpFtZn7p1WMtloVeMlqiYXDGF97068VvJCyK8rEFqBBkxT10E0qudipwxTjJTkU4we0hbOcHKjNTXz6JTEZYoRVn7F3-0O_bL9g71Rwek7TFI", + clientId: "833478926540-va43h2lhdhfc06i9eivlmaehl3o5uk1i.apps.googleusercontent.com", + clientSecret: "4ZsV4gwSuIoRdy6TKUXTanlw", + refreshToken: "1/XUtrd8DaeoopmX5xpIvGdXY09VAY6_h8fVVj9xCaKJE", + refreshUrl: "https://www.googleapis.com/oauth2/v4/token" + } } }); diff --git a/examples/secured-service-with-basic-auth/secured_service_with_basic_auth.bal b/examples/secured-service-with-basic-auth/secured_service_with_basic_auth.bal index 8444d442e66d..28dcf9597293 100644 --- a/examples/secured-service-with-basic-auth/secured_service_with_basic_auth.bal +++ b/examples/secured-service-with-basic-auth/secured_service_with_basic_auth.bal @@ -1,8 +1,8 @@ import ballerina/http; http:AuthProvider basicAuthProvider = { - scheme: "basic", - authStoreProvider: "config" + scheme: http:BASIC_AUTH, + authStoreProvider: http:CONFIG_AUTH_STORE }; // The endpoint used here is `http:Listener`, which by default tries to diff --git a/examples/secured-service-with-basic-auth/tests/secured_service_with_basic_auth_test.bal b/examples/secured-service-with-basic-auth/tests/secured_service_with_basic_auth_test.bal index 64c8c62f55cb..a6ad2da6a9e3 100644 --- a/examples/secured-service-with-basic-auth/tests/secured_service_with_basic_auth_test.bal +++ b/examples/secured-service-with-basic-auth/tests/secured_service_with_basic_auth_test.bal @@ -23,8 +23,10 @@ function testAuthSuccess() { http:Client httpEndpoint = new("https://localhost:9090", config = { auth: { scheme: http:BASIC_AUTH, - username: "tom", - password: "password1" + basicAuthConfig: { + username: "tom", + password: "password1" + } } }); // Send a `GET` request to the specified endpoint. @@ -42,8 +44,10 @@ function testAuthnFailure() { http:Client httpEndpoint = new("https://localhost:9090", config = { auth: { scheme: http:BASIC_AUTH, - username: "tom", - password: "password" + basicAuthConfig: { + username: "tom", + password: "password" + } } }); // Send a `GET` request to the specified endpoint. @@ -61,8 +65,10 @@ function testAuthzFailure() { http:Client httpEndpoint = new("https://localhost:9090", config = { auth: { scheme: http:BASIC_AUTH, - username: "dick", - password: "password2" + basicAuthConfig: { + username: "dick", + password: "password2" + } } }); // Send a `GET` request to the specified endpoint diff --git a/examples/secured-service-with-jwt/secured_service_with_jwt.bal b/examples/secured-service-with-jwt/secured_service_with_jwt.bal index c5063aec357e..0269b25edcea 100644 --- a/examples/secured-service-with-jwt/secured_service_with_jwt.bal +++ b/examples/secured-service-with-jwt/secured_service_with_jwt.bal @@ -3,13 +3,15 @@ import ballerina/http; // Create a JWT authentication provider with the relevant configuration // parameters. http:AuthProvider jwtAuthProvider = { - scheme:"jwt", - issuer:"ballerina", - audience: "ballerina.io", - certificateAlias: "ballerina", - trustStore: { - path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", - password: "ballerina" + scheme: http:JWT_AUTH, + jwtAuthProviderConfig: { + issuer:"ballerina", + audience: ["ballerina.io"], + certificateAlias: "ballerina", + trustStore: { + path: "${ballerina.home}/bre/security/ballerinaTruststore.p12", + password: "ballerina" + } } }; // The endpoint used here is `http:Listener`. The JWT authentication diff --git a/stdlib/http/src/main/ballerina/http/http_secure_client.bal b/stdlib/http/src/main/ballerina/http/http_secure_client.bal index f7c6979344f2..de0e02622480 100644 --- a/stdlib/http/src/main/ballerina/http/http_secure_client.bal +++ b/stdlib/http/src/main/ballerina/http/http_secure_client.bal @@ -460,7 +460,7 @@ function getAccessTokenFromRefreshToken(ClientEndpointConfig config) returns str # + return - Whether the client should retry or not function isRetryRequired(Response response, ClientEndpointConfig config) returns boolean { var scheme = config.auth.scheme; - if (scheme is InboundAuthScheme) { + if (scheme is OutboundAuthScheme) { if (scheme == OAUTH2 && response.statusCode == UNAUTHORIZED_401) { return true; } From 7598ec401045f8807b4be8efb13edbf0943c0051 Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Fri, 22 Feb 2019 14:36:30 +0530 Subject: [PATCH 23/26] Correct encoding doc comment --- stdlib/encoding/src/main/ballerina/encoding/encoding.bal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/encoding/src/main/ballerina/encoding/encoding.bal b/stdlib/encoding/src/main/ballerina/encoding/encoding.bal index 62594d6118d5..8f05f417a6bf 100644 --- a/stdlib/encoding/src/main/ballerina/encoding/encoding.bal +++ b/stdlib/encoding/src/main/ballerina/encoding/encoding.bal @@ -43,7 +43,7 @@ public extern function encodeBase64Url(byte[] input) returns string; # Decode Base64 URL encoded `string` into byte array. # # + input - Value to be decoded -# + return - Decoded output or error if input is not a valid Base64 value +# + return - Decoded output or error if input is not a valid Base64 URL encoded value public extern function decodeBase64Url(string input) returns byte[]|error; # Returns the Hex encoded `string` value of the given byte array. From 7ddb4652d81427344840b172ed78c9e32402b8b0 Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Fri, 22 Feb 2019 15:44:20 +0530 Subject: [PATCH 24/26] Remove NoTokenPropagationTest --- tests/ballerina-integration-test/src/test/resources/testng.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/ballerina-integration-test/src/test/resources/testng.xml b/tests/ballerina-integration-test/src/test/resources/testng.xml index 887eeeadf28b..90755fd2311a 100644 --- a/tests/ballerina-integration-test/src/test/resources/testng.xml +++ b/tests/ballerina-integration-test/src/test/resources/testng.xml @@ -111,7 +111,6 @@ - From a8a45786d22464db30b505f2c3ef93b409b9d2e4 Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Fri, 22 Feb 2019 17:43:42 +0530 Subject: [PATCH 25/26] Update websub integration tests --- .../source/actionInvocationSuggestion1.bal | 6 +++-- .../source/actionInvocationSuggestion2.bal | 6 +++-- .../source/errorLiftingSuggestions1.bal | 6 +++-- .../advanced_services/01_websub_publisher.bal | 10 ++++---- ..._subscribers_at_basic_auth_secured_hub.bal | 24 ++++++++++++------- ...subscribers_at_persistence_enabled_hub.bal | 12 ++++++---- 6 files changed, 42 insertions(+), 22 deletions(-) diff --git a/language-server/modules/langserver-core/src/test/resources/completion/function/source/actionInvocationSuggestion1.bal b/language-server/modules/langserver-core/src/test/resources/completion/function/source/actionInvocationSuggestion1.bal index a00d6f44d260..4baf25dc6cda 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/function/source/actionInvocationSuggestion1.bal +++ b/language-server/modules/langserver-core/src/test/resources/completion/function/source/actionInvocationSuggestion1.bal @@ -4,8 +4,10 @@ http:ClientEndpointConfig conf = { url: "https://postman-echo.com/basic-auth", auth: { scheme: http:BASIC_AUTH, - username: "postman", - password: "password" + basicAuthConfig: { + username: "postman", + password: "password" + } } }; diff --git a/language-server/modules/langserver-core/src/test/resources/completion/function/source/actionInvocationSuggestion2.bal b/language-server/modules/langserver-core/src/test/resources/completion/function/source/actionInvocationSuggestion2.bal index c3b29e4dec72..5e7ad93d8855 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/function/source/actionInvocationSuggestion2.bal +++ b/language-server/modules/langserver-core/src/test/resources/completion/function/source/actionInvocationSuggestion2.bal @@ -4,8 +4,10 @@ http:ClientEndpointConfig conf = { url: "https://postman-echo.com/basic-auth", auth: { scheme: http:BASIC_AUTH, - username: "postman", - password: "password" + basicAuthConfig: { + username: "postman", + password: "password" + } } }; diff --git a/language-server/modules/langserver-core/src/test/resources/completion/function/source/errorLiftingSuggestions1.bal b/language-server/modules/langserver-core/src/test/resources/completion/function/source/errorLiftingSuggestions1.bal index 04e1eff15a55..84100dc68ad4 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/function/source/errorLiftingSuggestions1.bal +++ b/language-server/modules/langserver-core/src/test/resources/completion/function/source/errorLiftingSuggestions1.bal @@ -5,8 +5,10 @@ http:ClientEndpointConfig conf = { url: "https://postman-echo.com/basic-auth", auth: { scheme: http:BASIC_AUTH, - username: "postman", - password: "password" + basicAuthConfig: { + username: "postman", + password: "password" + } } }; diff --git a/tests/ballerina-integration-test/src/test/resources/websub/advanced_services/01_websub_publisher.bal b/tests/ballerina-integration-test/src/test/resources/websub/advanced_services/01_websub_publisher.bal index 13d20aef119b..eaa129164296 100644 --- a/tests/ballerina-integration-test/src/test/resources/websub/advanced_services/01_websub_publisher.bal +++ b/tests/ballerina-integration-test/src/test/resources/websub/advanced_services/01_websub_publisher.bal @@ -26,8 +26,8 @@ const string WEBSUB_PERSISTENCE_TOPIC_TWO = "http://two.persistence.topic.com"; const string WEBSUB_TOPIC_ONE = "http://one.websub.topic.com"; http:AuthProvider basicAuthProvider = { - scheme: "basic", - authStoreProvider: "config" + scheme: http:BASIC_AUTH, + authStoreProvider: http:CONFIG_AUTH_STORE }; http:ServiceEndpointConfiguration hubListenerConfig = { @@ -51,8 +51,10 @@ listener http:Listener publisherServiceEP = new http:Listener(8080); websub:Client websubHubClientEP = new websub:Client(webSubHub.hubUrl, config = { auth: { scheme: http:BASIC_AUTH, - username: "peter", - password: "pqr" + basicAuthConfig: { + username: "peter", + password: "pqr" + } } }); diff --git a/tests/ballerina-integration-test/src/test/resources/websub/test_subscribers_at_basic_auth_secured_hub.bal b/tests/ballerina-integration-test/src/test/resources/websub/test_subscribers_at_basic_auth_secured_hub.bal index bdcb9f018e55..30442335d091 100644 --- a/tests/ballerina-integration-test/src/test/resources/websub/test_subscribers_at_basic_auth_secured_hub.bal +++ b/tests/ballerina-integration-test/src/test/resources/websub/test_subscribers_at_basic_auth_secured_hub.bal @@ -30,8 +30,10 @@ listener websub:Listener websubEP = new websub:Listener(8484); subscriptionClientConfig: { auth: { scheme: http:BASIC_AUTH, - username: "tom", - password: "1234" + basicAuthConfig: { + username: "tom", + password: "1234" + } } } } @@ -50,8 +52,10 @@ service websubSubscriber on websubEP { subscriptionClientConfig: { auth: { scheme: http:BASIC_AUTH, - username: "tom", - password: "4321" + basicAuthConfig: { + username: "tom", + password: "4321" + } } } } @@ -70,8 +74,10 @@ service websubSubscriberTwo on websubEP { subscriptionClientConfig: { auth: { scheme: http:BASIC_AUTH, - username: "mary", - password: "xyz" + basicAuthConfig: { + username: "mary", + password: "xyz" + } } } } @@ -90,8 +96,10 @@ service websubSubscriberThree on websubEP { subscriptionClientConfig: { auth: { scheme: http:BASIC_AUTH, - username: "tom", - password: "1234" + basicAuthConfig: { + username: "tom", + password: "1234" + } } } } diff --git a/tests/ballerina-integration-test/src/test/resources/websub/test_subscribers_at_persistence_enabled_hub.bal b/tests/ballerina-integration-test/src/test/resources/websub/test_subscribers_at_persistence_enabled_hub.bal index b25a3eefc610..a50171d2c2fe 100644 --- a/tests/ballerina-integration-test/src/test/resources/websub/test_subscribers_at_persistence_enabled_hub.bal +++ b/tests/ballerina-integration-test/src/test/resources/websub/test_subscribers_at_persistence_enabled_hub.bal @@ -30,8 +30,10 @@ listener websub:Listener websubEP = new websub:Listener(8383); subscriptionClientConfig: { auth: { scheme: http:BASIC_AUTH, - username: "tom", - password: "1234" + basicAuthConfig: { + username: "tom", + password: "1234" + } } } } @@ -50,8 +52,10 @@ service websubSubscriber on websubEP { subscriptionClientConfig: { auth: { scheme: http:BASIC_AUTH, - username: "tom", - password: "1234" + basicAuthConfig: { + username: "tom", + password: "1234" + } } } } From 5c581d92f06f1b68b4bcda42e48ba580dc7c1d48 Mon Sep 17 00:00:00 2001 From: Ayoma Wijethunga Date: Mon, 25 Feb 2019 10:59:34 +0530 Subject: [PATCH 26/26] Fix lang-server test & use union for config --- .../secured_client_with_basic_auth.bal | 2 +- .../secured_client_with_jwt_auth.bal | 2 +- .../secured_client_with_oauth2.bal | 2 +- .../secured_service_with_basic_auth_test.bal | 6 +- .../secured_service_with_jwt.bal | 2 +- .../function/actionInvocationSuggestion1.json | 2 +- .../function/actionInvocationSuggestion2.json | 2 +- .../function/errorLiftingSuggestions1.json | 2 +- .../source/actionInvocationSuggestion1.bal | 2 +- .../source/actionInvocationSuggestion2.bal | 4 +- .../source/errorLiftingSuggestions1.bal | 2 +- .../main/ballerina/http/client_endpoint.bal | 16 ++---- .../ballerina/http/http_secure_client.bal | 55 ++++++++++--------- .../main/ballerina/http/service_endpoint.bal | 35 +++++------- .../auth/authclients/oauth-client.bal | 10 ++-- .../08_authn_with_multiple_providers.bal | 4 +- .../09_authn_with_different_scopes.bal | 2 +- .../10_token_propagation_disabled_test.bal | 2 +- .../11_token_propagation_basic_auth_test.bal | 4 +- .../13_authn_with_expired_certificate.bal | 2 +- ..._certificate_with_no_expiry_validation.bal | 2 +- .../15_token_propagation_jwt_test.bal | 4 +- ...6_token_propagation_jwt_reissuing_test.bal | 6 +- ...ropagation_jwt_reissuing_negative_test.bal | 6 +- .../ldap_auth_store_authorization_test.bal | 2 +- .../authservices/ldap_auth_store_test.bal | 2 +- .../advanced_services/01_websub_publisher.bal | 2 +- ..._subscribers_at_basic_auth_secured_hub.bal | 8 +-- ...subscribers_at_persistence_enabled_hub.bal | 4 +- 29 files changed, 92 insertions(+), 102 deletions(-) diff --git a/examples/secured-client-with-basic-auth/secured_client_with_basic_auth.bal b/examples/secured-client-with-basic-auth/secured_client_with_basic_auth.bal index 0a59b18a74c5..aeb4f8b1b266 100644 --- a/examples/secured-client-with-basic-auth/secured_client_with_basic_auth.bal +++ b/examples/secured-client-with-basic-auth/secured_client_with_basic_auth.bal @@ -8,7 +8,7 @@ import ballerina/log; http:Client httpEndpoint = new("https://localhost:9090", config = { auth: { scheme: http:BASIC_AUTH, - basicAuthConfig: { + config: { username: "tom", password: "1234" } diff --git a/examples/secured-client-with-jwt-auth/secured_client_with_jwt_auth.bal b/examples/secured-client-with-jwt-auth/secured_client_with_jwt_auth.bal index bfdb2143c183..4395edef8b21 100644 --- a/examples/secured-client-with-jwt-auth/secured_client_with_jwt_auth.bal +++ b/examples/secured-client-with-jwt-auth/secured_client_with_jwt_auth.bal @@ -40,7 +40,7 @@ public function main() { // Create a JWT authentication provider with the relevant configurations. http:AuthProvider jwtAuthProvider = { scheme: http:JWT_AUTH, - jwtAuthProviderConfig: { + config: { issuer: "ballerina", audience: ["ballerina.io"], certificateAlias: "ballerina", diff --git a/examples/secured-client-with-oauth2/secured_client_with_oauth2.bal b/examples/secured-client-with-oauth2/secured_client_with_oauth2.bal index 37002d9d4a46..3c1495c5b2d4 100644 --- a/examples/secured-client-with-oauth2/secured_client_with_oauth2.bal +++ b/examples/secured-client-with-oauth2/secured_client_with_oauth2.bal @@ -8,7 +8,7 @@ import ballerina/log; http:Client httpEndpoint = new("https://www.googleapis.com/tasks/v1", config = { auth: { scheme: http:OAUTH2, - oAuth2AuthConfig: { + config: { accessToken: "ya29.GlufBimE7JZdiB_FpFtZn7p1WMtloVeMlqiYXDGF97068VvJCyK8rEFqBBkxT10E0qudipwxTjJTkU4we0hbOcHKjNTXz6JTEZYoRVn7F3-0O_bL9g71Rwek7TFI", clientId: "833478926540-va43h2lhdhfc06i9eivlmaehl3o5uk1i.apps.googleusercontent.com", clientSecret: "4ZsV4gwSuIoRdy6TKUXTanlw", diff --git a/examples/secured-service-with-basic-auth/tests/secured_service_with_basic_auth_test.bal b/examples/secured-service-with-basic-auth/tests/secured_service_with_basic_auth_test.bal index a6ad2da6a9e3..22370939dabe 100644 --- a/examples/secured-service-with-basic-auth/tests/secured_service_with_basic_auth_test.bal +++ b/examples/secured-service-with-basic-auth/tests/secured_service_with_basic_auth_test.bal @@ -23,7 +23,7 @@ function testAuthSuccess() { http:Client httpEndpoint = new("https://localhost:9090", config = { auth: { scheme: http:BASIC_AUTH, - basicAuthConfig: { + config: { username: "tom", password: "password1" } @@ -44,7 +44,7 @@ function testAuthnFailure() { http:Client httpEndpoint = new("https://localhost:9090", config = { auth: { scheme: http:BASIC_AUTH, - basicAuthConfig: { + config: { username: "tom", password: "password" } @@ -65,7 +65,7 @@ function testAuthzFailure() { http:Client httpEndpoint = new("https://localhost:9090", config = { auth: { scheme: http:BASIC_AUTH, - basicAuthConfig: { + config: { username: "dick", password: "password2" } diff --git a/examples/secured-service-with-jwt/secured_service_with_jwt.bal b/examples/secured-service-with-jwt/secured_service_with_jwt.bal index 0269b25edcea..8d56319f722a 100644 --- a/examples/secured-service-with-jwt/secured_service_with_jwt.bal +++ b/examples/secured-service-with-jwt/secured_service_with_jwt.bal @@ -4,7 +4,7 @@ import ballerina/http; // parameters. http:AuthProvider jwtAuthProvider = { scheme: http:JWT_AUTH, - jwtAuthProviderConfig: { + config: { issuer:"ballerina", audience: ["ballerina.io"], certificateAlias: "ballerina", diff --git a/language-server/modules/langserver-core/src/test/resources/completion/function/actionInvocationSuggestion1.json b/language-server/modules/langserver-core/src/test/resources/completion/function/actionInvocationSuggestion1.json index 295f4e913adc..3e7316175e53 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/function/actionInvocationSuggestion1.json +++ b/language-server/modules/langserver-core/src/test/resources/completion/function/actionInvocationSuggestion1.json @@ -1,6 +1,6 @@ { "position": { - "line": 13, + "line": 15, "character": 16 }, "source": "function/source/actionInvocationSuggestion1.bal", diff --git a/language-server/modules/langserver-core/src/test/resources/completion/function/actionInvocationSuggestion2.json b/language-server/modules/langserver-core/src/test/resources/completion/function/actionInvocationSuggestion2.json index 4db84858257a..8a15627a42d1 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/function/actionInvocationSuggestion2.json +++ b/language-server/modules/langserver-core/src/test/resources/completion/function/actionInvocationSuggestion2.json @@ -1,6 +1,6 @@ { "position": { - "line": 13, + "line": 15, "character": 20 }, "source": "function/source/actionInvocationSuggestion2.bal", diff --git a/language-server/modules/langserver-core/src/test/resources/completion/function/errorLiftingSuggestions1.json b/language-server/modules/langserver-core/src/test/resources/completion/function/errorLiftingSuggestions1.json index 4c5daf6a4954..a1b21b6dbb51 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/function/errorLiftingSuggestions1.json +++ b/language-server/modules/langserver-core/src/test/resources/completion/function/errorLiftingSuggestions1.json @@ -1,6 +1,6 @@ { "position": { - "line": 16, + "line": 18, "character": 27 }, "source": "function/source/errorLiftingSuggestions1.bal", diff --git a/language-server/modules/langserver-core/src/test/resources/completion/function/source/actionInvocationSuggestion1.bal b/language-server/modules/langserver-core/src/test/resources/completion/function/source/actionInvocationSuggestion1.bal index 4baf25dc6cda..de7fff653fde 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/function/source/actionInvocationSuggestion1.bal +++ b/language-server/modules/langserver-core/src/test/resources/completion/function/source/actionInvocationSuggestion1.bal @@ -4,7 +4,7 @@ http:ClientEndpointConfig conf = { url: "https://postman-echo.com/basic-auth", auth: { scheme: http:BASIC_AUTH, - basicAuthConfig: { + config: { username: "postman", password: "password" } diff --git a/language-server/modules/langserver-core/src/test/resources/completion/function/source/actionInvocationSuggestion2.bal b/language-server/modules/langserver-core/src/test/resources/completion/function/source/actionInvocationSuggestion2.bal index 5e7ad93d8855..3d8a415d3307 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/function/source/actionInvocationSuggestion2.bal +++ b/language-server/modules/langserver-core/src/test/resources/completion/function/source/actionInvocationSuggestion2.bal @@ -4,10 +4,10 @@ http:ClientEndpointConfig conf = { url: "https://postman-echo.com/basic-auth", auth: { scheme: http:BASIC_AUTH, - basicAuthConfig: { + config: { username: "postman", password: "password" - } + } } }; diff --git a/language-server/modules/langserver-core/src/test/resources/completion/function/source/errorLiftingSuggestions1.bal b/language-server/modules/langserver-core/src/test/resources/completion/function/source/errorLiftingSuggestions1.bal index 84100dc68ad4..ecad7b511fb3 100644 --- a/language-server/modules/langserver-core/src/test/resources/completion/function/source/errorLiftingSuggestions1.bal +++ b/language-server/modules/langserver-core/src/test/resources/completion/function/source/errorLiftingSuggestions1.bal @@ -5,7 +5,7 @@ http:ClientEndpointConfig conf = { url: "https://postman-echo.com/basic-auth", auth: { scheme: http:BASIC_AUTH, - basicAuthConfig: { + config: { username: "postman", password: "password" } diff --git a/stdlib/http/src/main/ballerina/http/client_endpoint.bal b/stdlib/http/src/main/ballerina/http/client_endpoint.bal index 3788040f3091..3f3478fc685d 100644 --- a/stdlib/http/src/main/ballerina/http/client_endpoint.bal +++ b/stdlib/http/src/main/ballerina/http/client_endpoint.bal @@ -322,15 +322,10 @@ public type ProxyConfig record { # AuthConfig record can be used to configure the authentication mechanism used by the HTTP endpoint. # # + scheme - Authentication scheme -# + basicAuthConfig - Configuration for BasicAuth scheme -# + oAuth2AuthConfig - Configuration for OAuth2 scheme -# + jwtAuthConfig - Configuration for JWT scheme. If inbound authentication is JWT, sends the same JWT with client -# invocation, unless reissuing is configured using InferredJwtIssuerConfig. +# + config - Configuration related to the selected authenticator. public type AuthConfig record { OutboundAuthScheme scheme; - BasicAuthConfig basicAuthConfig?; - OAuth2AuthConfig oAuth2AuthConfig?; - JwtAuthConfig jwtAuthConfig?; + BasicAuthConfig|OAuth2AuthConfig|JwtAuthConfig config?; !...; }; @@ -339,8 +334,8 @@ public type AuthConfig record { # + username - Username for Basic authentication # + password - Password for Basic authentication public type BasicAuthConfig record { - string username = ""; - string password = ""; + string username; + string password; !...; }; @@ -374,7 +369,8 @@ public type OAuth2AuthConfig record { # # + inferredJwtIssuerConfig - JWT issuer configuration used to issue JWT with specific configuration public type JwtAuthConfig record { - auth:InferredJwtIssuerConfig inferredJwtIssuerConfig?; + auth:InferredJwtIssuerConfig inferredJwtIssuerConfig; + !...; }; function initialize(string serviceUrl, ClientEndpointConfig config) returns Client|error { diff --git a/stdlib/http/src/main/ballerina/http/http_secure_client.bal b/stdlib/http/src/main/ballerina/http/http_secure_client.bal index de0e02622480..375916337ca6 100644 --- a/stdlib/http/src/main/ballerina/http/http_secure_client.bal +++ b/stdlib/http/src/main/ballerina/http/http_secure_client.bal @@ -302,38 +302,37 @@ public function createHttpSecureClient(string url, ClientEndpointConfig config) function generateSecureRequest(Request req, ClientEndpointConfig config) returns ()|error { var auth = config.auth; if (auth is AuthConfig) { + var authConfig = auth["config"]; if (auth.scheme == BASIC_AUTH) { - var basicAuthConfig = auth["basicAuthConfig"]; - if (basicAuthConfig is ()) { - error e = error("Basic auth config not provided"); - panic e; - } else { - string username = basicAuthConfig.username; - string password = basicAuthConfig.password; + if (authConfig is BasicAuthConfig) { + string username = authConfig.username; + string password = authConfig.password; string str = username + ":" + password; string token = encoding:encodeBase64(str.toByteArray("UTF-8")); req.setHeader(AUTH_HEADER, AUTH_SCHEME_BASIC + WHITE_SPACE + token); + } else { + error e = error("Basic auth config not provided"); + panic e; } } else if (auth.scheme == OAUTH2) { - var oAuth2AuthConfig = auth["oAuth2AuthConfig"]; - if (oAuth2AuthConfig is ()) { - error e = error("OAuth2 config not provided"); - panic e; - } else { - string accessToken = oAuth2AuthConfig.accessToken; + if (authConfig is OAuth2AuthConfig) { + string accessToken = authConfig.accessToken; if (accessToken == EMPTY_STRING) { return updateRequestAndConfig(req, config); } else { req.setHeader(AUTH_HEADER, AUTH_SCHEME_BEARER + WHITE_SPACE + accessToken); } + } else { + error e = error("OAuth2 config not provided"); + panic e; } } else if (auth.scheme == JWT_AUTH) { - var jwtAuthConfig = auth["jwtAuthConfig"]; string authToken = EMPTY_STRING; - if (jwtAuthConfig is ()) { - authToken = runtime:getInvocationContext().authenticationContext.authToken; - } else { - var jwtIssuerConfig = jwtAuthConfig["inferredJwtIssuerConfig"]; + if (authConfig is OAuth2AuthConfig || authConfig is BasicAuthConfig) { + error e = error("JWT auth config not provided"); + panic e; + } else if (authConfig is JwtAuthConfig) { + var jwtIssuerConfig = authConfig["inferredJwtIssuerConfig"]; if (jwtIssuerConfig is ()) { authToken = runtime:getInvocationContext().authenticationContext.authToken; } else { @@ -360,6 +359,8 @@ function generateSecureRequest(Request req, ClientEndpointConfig config) returns return token; } } + } else { + authToken = runtime:getInvocationContext().authenticationContext.authToken; } if (authToken == EMPTY_STRING) { error err = error(HTTP_ERROR_CODE, { message: "JWT was not used during inbound authentication. @@ -383,7 +384,7 @@ function generateSecureRequest(Request req, ClientEndpointConfig config) returns function updateRequestAndConfig(Request req, ClientEndpointConfig config) returns ()|error { string accessToken = check getAccessTokenFromRefreshToken(config); req.setHeader(AUTH_HEADER, AUTH_SCHEME_BEARER + WHITE_SPACE + accessToken); - OAuth2AuthConfig? authConfig = config.auth.oAuth2AuthConfig; + var authConfig = config.auth.config; if (authConfig is OAuth2AuthConfig) { authConfig.accessToken = accessToken; } @@ -396,13 +397,13 @@ function updateRequestAndConfig(Request req, ClientEndpointConfig config) return # + return - AccessToken received from the authorization server or `error` if error occured during HTTP client invocation function getAccessTokenFromRefreshToken(ClientEndpointConfig config) returns string|error { Client refreshTokenClient; - var oAuth2AuthConfig = config.auth.oAuth2AuthConfig; - if (oAuth2AuthConfig is OAuth2AuthConfig) { - string refreshToken = oAuth2AuthConfig.refreshToken; - string clientId = oAuth2AuthConfig.clientId; - string clientSecret = oAuth2AuthConfig.clientSecret; - string refreshUrl = oAuth2AuthConfig.refreshUrl; - string[] scopes = oAuth2AuthConfig.scopes; + var authConfig = config.auth.config; + if (authConfig is OAuth2AuthConfig) { + string refreshToken = authConfig.refreshToken; + string clientId = authConfig.clientId; + string clientSecret = authConfig.clientSecret; + string refreshUrl = authConfig.refreshUrl; + string[] scopes = authConfig.scopes; if (refreshToken == EMPTY_STRING || clientId == EMPTY_STRING || clientSecret == EMPTY_STRING || refreshUrl == EMPTY_STRING) { @@ -424,7 +425,7 @@ function getAccessTokenFromRefreshToken(ClientEndpointConfig config) returns str if (scopeString != EMPTY_STRING) { textPayload = textPayload + "&scope=" + scopeString.trim(); } - if (oAuth2AuthConfig.credentialBearer == AUTH_HEADER_BEARER) { + if (authConfig.credentialBearer == AUTH_HEADER_BEARER) { string clientIdSecret = clientId + ":" + clientSecret; refreshTokenRequest.addHeader(AUTH_HEADER, AUTH_SCHEME_BASIC + WHITE_SPACE + encoding:encodeBase64(clientIdSecret.toByteArray("UTF-8"))); diff --git a/stdlib/http/src/main/ballerina/http/service_endpoint.bal b/stdlib/http/src/main/ballerina/http/service_endpoint.bal index 29d78487bf79..43402c5f165b 100644 --- a/stdlib/http/src/main/ballerina/http/service_endpoint.bal +++ b/stdlib/http/src/main/ballerina/http/service_endpoint.bal @@ -216,16 +216,12 @@ public type AuthCacheConfig record { # + id - Authentication provider instance id # + scheme - Authentication scheme # + authStoreProvider - Authentication store provider (Config, LDAP, etc.) implementation -# + ldapAuthProviderConfig - LDAP auth provider related configurations -# + configAuthProviderConfig - Config auth provider related configurations -# + jwtAuthProviderConfig - JWT auth provider related configurations +# + config - Configuration related to the selected authentication provider. public type AuthProvider record { string id = ""; InboundAuthScheme? scheme = (); AuthStoreProvider? authStoreProvider = (); - auth:LdapAuthProviderConfig? ldapAuthProviderConfig = (); - auth:ConfigAuthProviderConfig? configAuthProviderConfig = (); - auth:JWTAuthProviderConfig? jwtAuthProviderConfig = (); + auth:LdapAuthProviderConfig|auth:ConfigAuthProviderConfig|auth:JWTAuthProviderConfig? config = (); !...; }; @@ -293,21 +289,20 @@ function createAuthFiltersForSecureListener(ServiceEndpointConfiguration config, auth:AuthStoreProvider authStoreProvider = new; foreach var provider in authProviderList { + var authProviderConfig = provider.config; if (provider.scheme == BASIC_AUTH) { if (provider.authStoreProvider == LDAP_AUTH_STORE) { - var ldapAuthProviderConfig = provider.ldapAuthProviderConfig; - if (ldapAuthProviderConfig is auth:LdapAuthProviderConfig) { - auth:LdapAuthStoreProvider ldapAuthStoreProvider = new(ldapAuthProviderConfig, instanceId); + if (authProviderConfig is auth:LdapAuthProviderConfig) { + auth:LdapAuthStoreProvider ldapAuthStoreProvider = new(authProviderConfig, instanceId); authStoreProvider = ldapAuthStoreProvider; } else { error e = error("LDAP auth provider config not provided"); panic e; } } else if (provider.authStoreProvider == CONFIG_AUTH_STORE) { - var configAuthProviderConfig = provider.configAuthProviderConfig; auth:ConfigAuthStoreProvider configAuthStoreProvider; - if (configAuthProviderConfig is auth:ConfigAuthProviderConfig) { - configAuthStoreProvider = new(configAuthProviderConfig); + if (authProviderConfig is auth:ConfigAuthProviderConfig) { + configAuthStoreProvider = new(authProviderConfig); } else { configAuthStoreProvider = new({}); } @@ -329,21 +324,20 @@ function createAuthFiltersForSecureListener(ServiceEndpointConfiguration config, } function createAuthHandler(AuthProvider authProvider, string instanceId) returns HttpAuthnHandler { + var authProviderConfig = authProvider.config; if (authProvider.scheme == BASIC_AUTH) { auth:AuthStoreProvider authStoreProvider = new; if (authProvider.authStoreProvider == CONFIG_AUTH_STORE) { - var configAuthProviderConfig = authProvider.configAuthProviderConfig; auth:ConfigAuthStoreProvider configAuthStoreProvider; - if (configAuthProviderConfig is auth:ConfigAuthProviderConfig) { - configAuthStoreProvider = new(configAuthProviderConfig); + if (authProviderConfig is auth:ConfigAuthProviderConfig) { + configAuthStoreProvider = new(authProviderConfig); } else { configAuthStoreProvider = new({}); } authStoreProvider = configAuthStoreProvider; } else if (authProvider.authStoreProvider == LDAP_AUTH_STORE) { - var ldapAuthProviderConfig = authProvider.ldapAuthProviderConfig; - if (ldapAuthProviderConfig is auth:LdapAuthProviderConfig) { - auth:LdapAuthStoreProvider ldapAuthStoreProvider = new(ldapAuthProviderConfig, instanceId); + if (authProviderConfig is auth:LdapAuthProviderConfig) { + auth:LdapAuthStoreProvider ldapAuthStoreProvider = new(authProviderConfig, instanceId); authStoreProvider = ldapAuthStoreProvider; } else { error e = error("LDAP auth provider config not provided"); @@ -356,9 +350,8 @@ function createAuthHandler(AuthProvider authProvider, string instanceId) returns HttpBasicAuthnHandler basicAuthHandler = new(authStoreProvider); return basicAuthHandler; } else if (authProvider.scheme == JWT_AUTH){ - var jwtAuthProviderConfig = authProvider.jwtAuthProviderConfig; - if (jwtAuthProviderConfig is auth:JWTAuthProviderConfig) { - auth:JWTAuthProvider jwtAuthProvider = new(jwtAuthProviderConfig); + if (authProviderConfig is auth:JWTAuthProviderConfig) { + auth:JWTAuthProvider jwtAuthProvider = new(authProviderConfig); HttpJwtAuthnHandler jwtAuthnHandler = new(jwtAuthProvider); return jwtAuthnHandler; } else { diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authclients/oauth-client.bal b/tests/ballerina-integration-test/src/test/resources/auth/authclients/oauth-client.bal index e0b0e8557e9e..06f382030c22 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authclients/oauth-client.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authclients/oauth-client.bal @@ -19,7 +19,7 @@ import ballerina/http; http:Client clientEP1 = new("https://localhost:9095/foo", config = { auth: { scheme: http:OAUTH2, - oAuth2AuthConfig: { + config: { refreshToken: "5Aep861..zRMyCurAUgnwQaEjnCVqxK2utna7Mm4nb9UamD7BW50R2huecjSaLlv5mT1z_TViZ", clientId: "3MVG9YDQS5WtC11paU2WcQjBB3L5w4gz52uriT8ksZ3nUVjKvrfQMrU4uvZohTftxStwNEW4cfStBEGRxRL68", clientSecret: "9205371918321623741", @@ -32,7 +32,7 @@ http:Client clientEP1 = new("https://localhost:9095/foo", config = { http:Client clientEP2 = new("https://localhost:9095/foo", config = { auth: { scheme: http:OAUTH2, - oAuth2AuthConfig: { + config: { refreshToken: "5Aep861..zRMyCurAUgnwQaEjnCVqxK2utna7Mm4nb9UamD7BW50R2huecjSaLlv5mT1z_TViZ", clientId: "3MVG9YDQS5WtC11paU2WcQjBB3L5w4gz52uriT8ksZ3nUVjKvrfQMrU4uvZohTftxStwNEW4cfStBEGRxRL68", clientSecret: "9205371918321623741", @@ -45,7 +45,7 @@ http:Client clientEP2 = new("https://localhost:9095/foo", config = { http:Client clientEP3 = new("https://localhost:9095/foo", config = { auth: { scheme: http:OAUTH2, - oAuth2AuthConfig: { + config: { refreshToken: "5Aep861..zRMyCurAUgnwQaEjnCVqxK2utna7Mm4nb9UamD7BW50R2huecjSaLlv5mT1z_TViZ", clientId: "3MVG9YDQS5WtC11paU2WcQjBB3L5w4gz52uriT8ksZ3nUVjKvrfQMrU4uvZohTftxStwNEW4cfStBEGRxRL68", clientSecret: "9205371918321623741", @@ -57,7 +57,7 @@ http:Client clientEP3 = new("https://localhost:9095/foo", config = { http:Client clientEP4 = new("https://localhost:9095/foo", config = { auth: { scheme: http:OAUTH2, - oAuth2AuthConfig : { + config : { refreshToken: "5Aep861..zRMyCurAUgnwQaEjnCVqxK2utna7Mm4nb9UamD7BW50R2huecjSaLlv5mT1z_TViZ", clientId: "3MVG9YDQS5WtC11paU2WcQjBB3L5w4gz52uriT8ksZ3nUVjKvrfQMrU4uvZohTftxStwNEW4cfStBEGRxRL68", clientSecret: "9205371918321623741", @@ -70,7 +70,7 @@ http:Client clientEP4 = new("https://localhost:9095/foo", config = { http:Client clientEP5 = new("https://localhost:9095/foo", config = { auth: { scheme: http:OAUTH2, - oAuth2AuthConfig: { + config: { refreshToken: "5Aep861..zRMyCurAUgnwQaEjnCVqxK2utna7Mm4nb9UamD7BW50R2huecjSaLlv5mT1z_TViZ", clientId: "3MVG9YDQS5WtC11paU2WcQjBB3L5w4gz52uriT8ksZ3nUVjKvrfQMrU4uvZohTftxStwNEW4cfStBEGRxRL68", clientSecret: "9205371918321623741", diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/08_authn_with_multiple_providers.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/08_authn_with_multiple_providers.bal index c667b6832487..5a6db3a242c1 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/08_authn_with_multiple_providers.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/08_authn_with_multiple_providers.bal @@ -18,7 +18,7 @@ import ballerina/http; http:AuthProvider jwtAuthProvider1 = { scheme: http:JWT_AUTH, - jwtAuthProviderConfig: { + config: { issuer: "example1", audience: ["ballerina"], certificateAlias: "ballerina", @@ -31,7 +31,7 @@ http:AuthProvider jwtAuthProvider1 = { http:AuthProvider jwtAuthProvider2 = { scheme: http:JWT_AUTH, - jwtAuthProviderConfig: { + config: { issuer: "example2", audience: ["ballerina"], certificateAlias: "ballerina", diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/09_authn_with_different_scopes.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/09_authn_with_different_scopes.bal index ff8e1c30e6a5..ae9114a64ac2 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/09_authn_with_different_scopes.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/09_authn_with_different_scopes.bal @@ -18,7 +18,7 @@ import ballerina/http; http:AuthProvider jwtAuthProvider3 = { scheme: http:JWT_AUTH, - jwtAuthProviderConfig: { + config: { issuer: "ballerina", audience: ["ballerina"], certificateAlias: "ballerina", diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/10_token_propagation_disabled_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/10_token_propagation_disabled_test.bal index 8f414201bfbb..7c26b5155707 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/10_token_propagation_disabled_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/10_token_propagation_disabled_test.bal @@ -58,7 +58,7 @@ service passthroughService on listener10_1 { http:AuthProvider jwtAuthProvider10 = { scheme: http:JWT_AUTH, - jwtAuthProviderConfig: { + config: { issuer: "ballerina", audience: ["ballerina"], certificateAlias: "ballerina", diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/11_token_propagation_basic_auth_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/11_token_propagation_basic_auth_test.bal index 123e05f99e22..815202ea4166 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/11_token_propagation_basic_auth_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/11_token_propagation_basic_auth_test.bal @@ -34,7 +34,7 @@ listener http:Listener listener11 = new(9192, config = { http:Client nyseEP03 = new("https://localhost:9193", config = { auth: { scheme: http:JWT_AUTH, - jwtAuthConfig: { + config: { inferredJwtIssuerConfig: { issuer: "ballerina", audience: ["ballerina"], @@ -72,7 +72,7 @@ service passthroughService03 on listener11 { http:AuthProvider jwtAuthProvider03 = { scheme: http:JWT_AUTH, - jwtAuthProviderConfig: { + config: { issuer: "ballerina", audience: ["ballerina"], certificateAlias: "ballerina", diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/13_authn_with_expired_certificate.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/13_authn_with_expired_certificate.bal index 878ac9984d0d..0c40d6cf35be 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/13_authn_with_expired_certificate.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/13_authn_with_expired_certificate.bal @@ -18,7 +18,7 @@ import ballerina/http; http:AuthProvider jwtAuthProvider4 = { scheme: http:JWT_AUTH, - jwtAuthProviderConfig: { + config: { issuer:"ballerina", audience: ["ballerina.io"], certificateAlias: "cert", diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/14_authn_with_expired_certificate_with_no_expiry_validation.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/14_authn_with_expired_certificate_with_no_expiry_validation.bal index 59e533eed18f..e0cdc5e20937 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/14_authn_with_expired_certificate_with_no_expiry_validation.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/14_authn_with_expired_certificate_with_no_expiry_validation.bal @@ -18,7 +18,7 @@ import ballerina/http; http:AuthProvider jwtAuthProvider14 = { scheme: http:JWT_AUTH, - jwtAuthProviderConfig: { + config: { issuer:"ballerina", audience: ["ballerina.io"], certificateAlias: "cert", diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/15_token_propagation_jwt_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/15_token_propagation_jwt_test.bal index 150a5ed7d21d..decfe0a9664f 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/15_token_propagation_jwt_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/15_token_propagation_jwt_test.bal @@ -18,7 +18,7 @@ import ballerina/http; http:AuthProvider basicAuthProvider15 = { scheme: http:JWT_AUTH, - jwtAuthProviderConfig: { + config: { issuer: "example1", audience: ["ballerina"], certificateAlias: "ballerina", @@ -68,7 +68,7 @@ service passthroughService15 on listener15_1 { http:AuthProvider jwtAuthProvider15 = { scheme: http:JWT_AUTH, - jwtAuthProviderConfig: { + config: { issuer: "example1", audience: ["ballerina"], certificateAlias: "ballerina", diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/16_token_propagation_jwt_reissuing_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/16_token_propagation_jwt_reissuing_test.bal index 869eea95be8a..cbd2c8cb70ed 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/16_token_propagation_jwt_reissuing_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/16_token_propagation_jwt_reissuing_test.bal @@ -18,7 +18,7 @@ import ballerina/http; http:AuthProvider basicAuthProvider16 = { scheme: http:JWT_AUTH, - jwtAuthProviderConfig: { + config: { issuer: "example1", audience: ["ballerina"], certificateAlias: "ballerina", @@ -42,7 +42,7 @@ listener http:Listener listener16_1 = new(9105, config = { http:Client nyseEP16 = new("https://localhost:9106", config = { auth: { scheme: http:JWT_AUTH, - jwtAuthConfig: { + config: { inferredJwtIssuerConfig: { issuer: "example2", audience: ["ballerina"], @@ -80,7 +80,7 @@ service passthroughService16 on listener16_1 { http:AuthProvider jwtAuthProvider16 = { scheme: http:JWT_AUTH, - jwtAuthProviderConfig: { + config: { issuer: "example2", audience: ["ballerina"], certificateAlias: "ballerina", diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/17_token_propagation_jwt_reissuing_negative_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/17_token_propagation_jwt_reissuing_negative_test.bal index a0a0fe5b3e7b..f322990aa895 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/17_token_propagation_jwt_reissuing_negative_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/17_token_propagation_jwt_reissuing_negative_test.bal @@ -18,7 +18,7 @@ import ballerina/http; http:AuthProvider basicAuthProvider17 = { scheme: http:JWT_AUTH, - jwtAuthProviderConfig: { + config: { issuer: "example1", audience: ["ballerina"], certificateAlias: "ballerina", @@ -42,7 +42,7 @@ listener http:Listener listener17_1 = new(9107, config = { http:Client nyseEP17 = new("https://localhost:9108", config = { auth: { scheme: http:JWT_AUTH, - jwtAuthConfig: { + config: { inferredJwtIssuerConfig: { issuer: "ballerina", audience: ["ballerina"], @@ -80,7 +80,7 @@ service passthroughService17 on listener17_1 { http:AuthProvider jwtAuthProvider17 = { scheme: http:JWT_AUTH, - jwtAuthProviderConfig: { + config: { issuer: "example2aaaaaaaaaaaaaa", audience: ["ballerina"], certificateAlias: "ballerina", diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/ldap_auth_store_authorization_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/ldap_auth_store_authorization_test.bal index 7f225b9273e8..e3d52cf3228a 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/ldap_auth_store_authorization_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/ldap_auth_store_authorization_test.bal @@ -44,7 +44,7 @@ http:AuthProvider authProvider = { id: "basic02", scheme: http:BASIC_AUTH, authStoreProvider: http:LDAP_AUTH_STORE, - ldapAuthProviderConfig: ldapConfig + config: ldapConfig }; listener http:Listener authEP = new(9097, config = { diff --git a/tests/ballerina-integration-test/src/test/resources/auth/authservices/ldap_auth_store_test.bal b/tests/ballerina-integration-test/src/test/resources/auth/authservices/ldap_auth_store_test.bal index 3e148deb059c..0dafb206a570 100644 --- a/tests/ballerina-integration-test/src/test/resources/auth/authservices/ldap_auth_store_test.bal +++ b/tests/ballerina-integration-test/src/test/resources/auth/authservices/ldap_auth_store_test.bal @@ -44,7 +44,7 @@ http:AuthProvider basicAuthProvider = { id: "basic01", scheme: http:BASIC_AUTH, authStoreProvider: http:LDAP_AUTH_STORE, - ldapAuthProviderConfig: ldapAuthProviderConfig + config: ldapAuthProviderConfig }; listener http:Listener ep = new(9096, config = { diff --git a/tests/ballerina-integration-test/src/test/resources/websub/advanced_services/01_websub_publisher.bal b/tests/ballerina-integration-test/src/test/resources/websub/advanced_services/01_websub_publisher.bal index eaa129164296..e1c4334f20f0 100644 --- a/tests/ballerina-integration-test/src/test/resources/websub/advanced_services/01_websub_publisher.bal +++ b/tests/ballerina-integration-test/src/test/resources/websub/advanced_services/01_websub_publisher.bal @@ -51,7 +51,7 @@ listener http:Listener publisherServiceEP = new http:Listener(8080); websub:Client websubHubClientEP = new websub:Client(webSubHub.hubUrl, config = { auth: { scheme: http:BASIC_AUTH, - basicAuthConfig: { + config: { username: "peter", password: "pqr" } diff --git a/tests/ballerina-integration-test/src/test/resources/websub/test_subscribers_at_basic_auth_secured_hub.bal b/tests/ballerina-integration-test/src/test/resources/websub/test_subscribers_at_basic_auth_secured_hub.bal index 30442335d091..8718ec64f425 100644 --- a/tests/ballerina-integration-test/src/test/resources/websub/test_subscribers_at_basic_auth_secured_hub.bal +++ b/tests/ballerina-integration-test/src/test/resources/websub/test_subscribers_at_basic_auth_secured_hub.bal @@ -30,7 +30,7 @@ listener websub:Listener websubEP = new websub:Listener(8484); subscriptionClientConfig: { auth: { scheme: http:BASIC_AUTH, - basicAuthConfig: { + config: { username: "tom", password: "1234" } @@ -52,7 +52,7 @@ service websubSubscriber on websubEP { subscriptionClientConfig: { auth: { scheme: http:BASIC_AUTH, - basicAuthConfig: { + config: { username: "tom", password: "4321" } @@ -74,7 +74,7 @@ service websubSubscriberTwo on websubEP { subscriptionClientConfig: { auth: { scheme: http:BASIC_AUTH, - basicAuthConfig: { + config: { username: "mary", password: "xyz" } @@ -96,7 +96,7 @@ service websubSubscriberThree on websubEP { subscriptionClientConfig: { auth: { scheme: http:BASIC_AUTH, - basicAuthConfig: { + config: { username: "tom", password: "1234" } diff --git a/tests/ballerina-integration-test/src/test/resources/websub/test_subscribers_at_persistence_enabled_hub.bal b/tests/ballerina-integration-test/src/test/resources/websub/test_subscribers_at_persistence_enabled_hub.bal index a50171d2c2fe..5ab77d4bf4a2 100644 --- a/tests/ballerina-integration-test/src/test/resources/websub/test_subscribers_at_persistence_enabled_hub.bal +++ b/tests/ballerina-integration-test/src/test/resources/websub/test_subscribers_at_persistence_enabled_hub.bal @@ -30,7 +30,7 @@ listener websub:Listener websubEP = new websub:Listener(8383); subscriptionClientConfig: { auth: { scheme: http:BASIC_AUTH, - basicAuthConfig: { + config: { username: "tom", password: "1234" } @@ -52,7 +52,7 @@ service websubSubscriber on websubEP { subscriptionClientConfig: { auth: { scheme: http:BASIC_AUTH, - basicAuthConfig: { + config: { username: "tom", password: "1234" }