From f7b9d042b64cec0dcd699464541a6d0117082d86 Mon Sep 17 00:00:00 2001 From: megala21 Date: Thu, 12 Oct 2017 22:42:24 +0530 Subject: [PATCH 1/4] Adding test cases for JWT Authenticator --- .../pom.xml | 5 + .../authenticator/JWTAuthenticator.java | 35 ++++-- .../BaseWebAppAuthenticatorFrameworkTest.java | 25 ++++ .../authenticator/JWTAuthenticatorTest.java | 109 ++++++++++++++++++ .../util/TestTenantIndexingLoader.java | 9 ++ .../util/TestTenantRegistryLoader.java | 11 ++ .../resources/security/client-truststore.jks | Bin 0 -> 102330 bytes .../resources/security/wso2carbon.jks | Bin 0 -> 33497 bytes .../src/test/resources/jwt.properties | 57 +++++++++ .../src/test/resources/testng.xml | 1 + 10 files changed, 240 insertions(+), 12 deletions(-) create mode 100644 components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticatorTest.java create mode 100644 components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestTenantIndexingLoader.java create mode 100644 components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestTenantRegistryLoader.java create mode 100644 components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/carbon-home/repository/resources/security/client-truststore.jks create mode 100644 components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/carbon-home/repository/resources/security/wso2carbon.jks create mode 100644 components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/jwt.properties diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/pom.xml b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/pom.xml index 1339cc580e..045e916fcf 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/pom.xml +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/pom.xml @@ -253,6 +253,11 @@ powermock-api-mockito test + + org.wso2.carbon.devicemgt + org.wso2.carbon.identity.jwt.client.extension + test + diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticator.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticator.java index 99fd36d534..7f9a8bb54c 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticator.java +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticator.java @@ -86,6 +86,12 @@ public class JWTAuthenticator implements WebappAuthenticator { @Override public AuthenticationInfo authenticate(Request request, Response response) { String requestUri = request.getRequestURI(); + SignedJWT jwsObject; + String username; + String tenantDomain; + int tenantId; + String issuer; + AuthenticationInfo authenticationInfo = new AuthenticationInfo(); if (requestUri == null || "".equals(requestUri)) { authenticationInfo.setStatus(Status.CONTINUE); @@ -101,12 +107,17 @@ public class JWTAuthenticator implements WebappAuthenticator { try { String authorizationHeader = request.getHeader(JWT_ASSERTION_HEADER); + jwsObject = SignedJWT.parse(authorizationHeader); + username = jwsObject.getJWTClaimsSet().getStringClaim(SIGNED_JWT_AUTH_USERNAME); + tenantDomain = MultitenantUtils.getTenantDomain(username); + tenantId = Integer.parseInt(jwsObject.getJWTClaimsSet().getStringClaim(SIGNED_JWT_AUTH_TENANT_ID)); + issuer = jwsObject.getJWTClaimsSet().getIssuer(); + } catch (ParseException e) { + log.error("Error occurred while parsing JWT header.", e); + return null; + } + try { - SignedJWT jwsObject = SignedJWT.parse(authorizationHeader); - String username = jwsObject.getJWTClaimsSet().getStringClaim(SIGNED_JWT_AUTH_USERNAME); - String tenantDomain = MultitenantUtils.getTenantDomain(username); - int tenantId = Integer.parseInt(jwsObject.getJWTClaimsSet().getStringClaim(SIGNED_JWT_AUTH_TENANT_ID)); - String issuer = jwsObject.getJWTClaimsSet().getIssuer(); PrivilegedCarbonContext.startTenantFlow(); PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(tenantDomain); PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantId(tenantId); @@ -116,7 +127,7 @@ public class JWTAuthenticator implements WebappAuthenticator { loadTenantRegistry(tenantId); KeyStoreManager keyStoreManager = KeyStoreManager.getInstance(tenantId); if (MultitenantConstants.SUPER_TENANT_DOMAIN_NAME.equals(tenantDomain)) { - String alias = properties.getProperty(issuer); + String alias = properties == null ? null : properties.getProperty(issuer); if (alias != null && !alias.isEmpty()) { ServerConfiguration serverConfig = CarbonUtils.getServerConfiguration(); KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); @@ -139,10 +150,12 @@ public class JWTAuthenticator implements WebappAuthenticator { publicKeyHolder.put(issuerAlias, publicKey); } } - //Get the filesystem keystore default primary certificate - JWSVerifier verifier = new RSASSAVerifier((RSAPublicKey) publicKey); - if (jwsObject.verify(verifier)) { + JWSVerifier verifier = null; + if (publicKey != null) { + verifier = new RSASSAVerifier((RSAPublicKey) publicKey); + } + if (verifier != null && jwsObject.verify(verifier)) { username = MultitenantUtils.getTenantAwareUsername(username); if (tenantId == -1) { log.error("tenantDomain is not valid. username : " + username + ", tenantDomain " + @@ -162,9 +175,7 @@ public class JWTAuthenticator implements WebappAuthenticator { } } catch (UserStoreException e) { log.error("Error occurred while obtaining the user.", e); - } catch (ParseException e) { - log.error("Error occurred while parsing the JWT header.", e); - } catch (Exception e) { + } catch (Exception e) { log.error("Error occurred while verifying the JWT header.", e); } finally { PrivilegedCarbonContext.endTenantFlow(); diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/BaseWebAppAuthenticatorFrameworkTest.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/BaseWebAppAuthenticatorFrameworkTest.java index d4bcaf2e20..fc437004a0 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/BaseWebAppAuthenticatorFrameworkTest.java +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/BaseWebAppAuthenticatorFrameworkTest.java @@ -21,18 +21,25 @@ package org.wso2.carbon.webapp.authenticator.framework; import org.testng.annotations.BeforeSuite; import org.wso2.carbon.CarbonConstants; import org.wso2.carbon.context.PrivilegedCarbonContext; +import org.wso2.carbon.core.internal.CarbonCoreDataHolder; import org.wso2.carbon.device.mgt.core.internal.DeviceManagementDataHolder; import org.wso2.carbon.device.mgt.core.permission.mgt.PermissionUtils; +import org.wso2.carbon.registry.core.config.RegistryContext; import org.wso2.carbon.registry.core.exceptions.RegistryException; +import org.wso2.carbon.registry.core.internal.RegistryDataHolder; import org.wso2.carbon.registry.core.jdbc.realm.InMemoryRealmService; +import org.wso2.carbon.registry.core.service.RegistryService; import org.wso2.carbon.user.api.Permission; import org.wso2.carbon.user.api.UserStoreException; import org.wso2.carbon.user.api.UserStoreManager; import org.wso2.carbon.user.core.service.RealmService; import org.wso2.carbon.utils.multitenancy.MultitenantConstants; import org.wso2.carbon.webapp.authenticator.framework.internal.AuthenticatorFrameworkDataHolder; +import org.wso2.carbon.webapp.authenticator.framework.util.TestTenantIndexingLoader; +import org.wso2.carbon.webapp.authenticator.framework.util.TestTenantRegistryLoader; import java.io.File; +import java.io.InputStream; import java.net.URL; import static org.wso2.carbon.security.SecurityConstants.ADMIN_USER; @@ -56,6 +63,17 @@ public class BaseWebAppAuthenticatorFrameworkTest { .setTenantDomain(org.wso2.carbon.base.MultitenantConstants.SUPER_TENANT_DOMAIN_NAME); PrivilegedCarbonContext.getThreadLocalCarbonContext() .setTenantId(org.wso2.carbon.base.MultitenantConstants.SUPER_TENANT_ID); + CarbonCoreDataHolder.getInstance().setRegistryService(getRegistryService()); + AuthenticatorFrameworkDataHolder.getInstance().setTenantRegistryLoader(new TestTenantRegistryLoader()); + AuthenticatorFrameworkDataHolder.getInstance().setTenantIndexingLoader(new TestTenantIndexingLoader()); + } + + /** + * To get the registry service. + * @return RegistryService + * @throws RegistryException Registry Exception + */ + private RegistryService getRegistryService() throws RegistryException, UserStoreException { RealmService realmService = new InMemoryRealmService(); AuthenticatorFrameworkDataHolder.getInstance().setRealmService(realmService); UserStoreManager userStoreManager = AuthenticatorFrameworkDataHolder.getInstance().getRealmService() @@ -63,5 +81,12 @@ public class BaseWebAppAuthenticatorFrameworkTest { Permission adminPermission = new Permission(PermissionUtils.ADMIN_PERMISSION_REGISTRY_PATH, CarbonConstants.UI_PERMISSION_ACTION); userStoreManager.addRole(ADMIN_ROLE + "t", new String[] { ADMIN_USER }, new Permission[] { adminPermission }); + RegistryDataHolder.getInstance().setRealmService(realmService); + DeviceManagementDataHolder.getInstance().setRealmService(realmService); + InputStream is = BaseWebAppAuthenticatorFrameworkTest.class.getClassLoader() + .getResourceAsStream("carbon-home/repository/conf/registry.xml"); + RegistryContext context = RegistryContext.getBaseInstance(is, realmService); + context.setSetup(true); + return context.getEmbeddedRegistryService(); } } diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticatorTest.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticatorTest.java new file mode 100644 index 0000000000..5ea9b1f88b --- /dev/null +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticatorTest.java @@ -0,0 +1,109 @@ +package org.wso2.carbon.webapp.authenticator.framework.authenticator; + +import org.apache.catalina.connector.Request; +import org.apache.tomcat.util.buf.MessageBytes; +import org.apache.tomcat.util.http.MimeHeaders; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; +import org.wso2.carbon.base.MultitenantConstants; +import org.wso2.carbon.identity.jwt.client.extension.dto.JWTConfig; +import org.wso2.carbon.identity.jwt.client.extension.exception.JWTClientException; +import org.wso2.carbon.identity.jwt.client.extension.util.JWTClientUtil; +import org.wso2.carbon.webapp.authenticator.framework.AuthenticationInfo; +import org.wso2.carbon.webapp.authenticator.framework.internal.AuthenticatorFrameworkDataHolder; +import org.wso2.carbon.webapp.authenticator.framework.util.TestTenantIndexingLoader; +import org.wso2.carbon.webapp.authenticator.framework.util.TestTenantRegistryLoader; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +public class JWTAuthenticatorTest { + private JWTAuthenticator jwtAuthenticator; + private Field headersField; + private final String JWT_HEADER = "X-JWT-Assertion"; + private String jwtToken; + private static final String SIGNED_JWT_AUTH_USERNAME = "http://wso2.org/claims/enduser"; + private static final String SIGNED_JWT_AUTH_TENANT_ID = "http://wso2.org/claims/enduserTenantId"; + private Properties properties; + private final String ISSUER = "wso2.org/products/iot"; + private final String ALIAS = "wso2carbon"; + + @BeforeClass + public void setup() throws NoSuchFieldException, IOException, JWTClientException { + jwtAuthenticator = new JWTAuthenticator(); + properties = new Properties(); + properties.setProperty(ISSUER, ALIAS); + jwtAuthenticator.setProperties(properties); + headersField = org.apache.coyote.Request.class.getDeclaredField("headers"); + headersField.setAccessible(true); + ClassLoader classLoader = getClass().getClassLoader(); + URL resourceUrl = classLoader.getResource("jwt.properties"); + File jwtPropertyFile; + JWTConfig jwtConfig = null; + + if (resourceUrl != null) { + jwtPropertyFile = new File(resourceUrl.getFile()); + Properties jwtConfigProperties = new Properties(); + jwtConfigProperties.load(new FileInputStream(jwtPropertyFile)); + jwtConfig = new JWTConfig(jwtConfigProperties); + } + + Map customClaims = new HashMap<>(); + customClaims.put(SIGNED_JWT_AUTH_USERNAME, "admin"); + customClaims.put(SIGNED_JWT_AUTH_TENANT_ID, String.valueOf(MultitenantConstants.SUPER_TENANT_ID)); + jwtToken = JWTClientUtil.generateSignedJWTAssertion("admin", jwtConfig, false, customClaims); + } + + @Test(description = "This method tests the get methods in the JWTAuthenticator") + public void testGetMethods() { + Assert.assertEquals(jwtAuthenticator.getName(), "JWT", "GetName method returns wrong value"); + Assert.assertNotNull(jwtAuthenticator.getProperties(), "Properties are not properly added to JWT " + + "Authenticator"); + Assert.assertEquals(jwtAuthenticator.getProperties().size(), properties.size(), + "Added properties do not match with retrieved properties"); + Assert.assertNull(jwtAuthenticator.getProperty("test"), "Retrieved a propety that was never added"); + Assert.assertNotNull(jwtAuthenticator.getProperty(ISSUER), ALIAS); + } + + @Test(description = "This method tests the canHandle method under different conditions of request") + public void testHandle() throws IllegalAccessException, NoSuchFieldException { + Request request = new Request(); + org.apache.coyote.Request coyoteRequest = new org.apache.coyote.Request(); + request.setCoyoteRequest(coyoteRequest); + Assert.assertFalse(jwtAuthenticator.canHandle(request)); + MimeHeaders mimeHeaders = new MimeHeaders(); + MessageBytes bytes = mimeHeaders.addValue(JWT_HEADER); + bytes.setString("test"); + headersField.set(coyoteRequest, mimeHeaders); + request.setCoyoteRequest(coyoteRequest); + Assert.assertTrue(jwtAuthenticator.canHandle(request)); + } + + @Test(description = "This method tests authenticate method under the successful condition") + public void testAuthenticate() throws IllegalAccessException, NoSuchFieldException { + Request request = new Request(); + org.apache.coyote.Request coyoteRequest = new org.apache.coyote.Request(); + MimeHeaders mimeHeaders = new MimeHeaders(); + MessageBytes bytes = mimeHeaders.addValue(JWT_HEADER); + bytes.setString(jwtToken); + headersField.set(coyoteRequest, mimeHeaders); + Field uriMB = org.apache.coyote.Request.class.getDeclaredField("uriMB"); + uriMB.setAccessible(true); + bytes = MessageBytes.newInstance(); + bytes.setString("test"); + uriMB.set(coyoteRequest, bytes); + request.setCoyoteRequest(coyoteRequest); + + AuthenticationInfo authenticationInfo = jwtAuthenticator.authenticate(request, null); + Assert.assertNotNull(authenticationInfo.getUsername(), "Proper authentication request is not properly " + + "authenticated by the JWTAuthenticator"); + } +} diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestTenantIndexingLoader.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestTenantIndexingLoader.java new file mode 100644 index 0000000000..12203c35d8 --- /dev/null +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestTenantIndexingLoader.java @@ -0,0 +1,9 @@ +package org.wso2.carbon.webapp.authenticator.framework.util; + +import org.wso2.carbon.registry.indexing.service.TenantIndexingLoader; + +public class TestTenantIndexingLoader implements TenantIndexingLoader { + @Override public void loadTenantIndex(int i) { + + } +} diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestTenantRegistryLoader.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestTenantRegistryLoader.java new file mode 100644 index 0000000000..1656e91bc0 --- /dev/null +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestTenantRegistryLoader.java @@ -0,0 +1,11 @@ +package org.wso2.carbon.webapp.authenticator.framework.util; + +import org.wso2.carbon.registry.core.exceptions.RegistryException; +import org.wso2.carbon.registry.core.service.TenantRegistryLoader; + +public class TestTenantRegistryLoader implements TenantRegistryLoader { + @Override + public void loadTenantRegistry(int i) throws RegistryException { + + } +} diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/carbon-home/repository/resources/security/client-truststore.jks b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/carbon-home/repository/resources/security/client-truststore.jks new file mode 100644 index 0000000000000000000000000000000000000000..3b9fdfb9e84c41196f803dca9d96daa06fe60d1a GIT binary patch literal 102330 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 Rh!iM9rnBJ3075NF{~tu`p4$Kb literal 0 HcmV?d00001 diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/carbon-home/repository/resources/security/wso2carbon.jks b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/carbon-home/repository/resources/security/wso2carbon.jks new file mode 100644 index 0000000000000000000000000000000000000000..b4b6220baec4547220626b5664ef5271e4b1feb0 GIT binary patch literal 33497 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 literal 0 HcmV?d00001 diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/jwt.properties b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/jwt.properties new file mode 100644 index 0000000000..b7be2e296a --- /dev/null +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/jwt.properties @@ -0,0 +1,57 @@ +# +# Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. +# +# WSO2 Inc. licenses this file to you under the Apache License, +# Version 2.0 (the "License"); you may not use this file except +# in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# + +#issuer of the JWT +iss=wso2.org/products/iot + +TokenEndpoint=https://${iot.gateway.host}:${iot.gateway.https.port}/token?tenantDomain=carbon.super + +#audience of JWT claim +#comma seperated values +aud=devicemgt + +#expiration time of JWT (number of minutes from the current time) +exp=1000 + +#issued at time of JWT (number of minutes from the current time) +iat=0 + +#nbf time of JWT (number of minutes from current time) +nbf=0 + +#skew between IDP and issuer(seconds) +skew=0 + +# JWT Id +#jti=token123 + +#KeyStore to cryptographic credentials +KeyStore=target/test-classes/carbon-home/repository/resources/security/wso2carbon.jks + +#Password of the KeyStore +KeyStorePassword=wso2carbon + +#Alias of the SP's private key +PrivateKeyAlias=wso2carbon + +#Private key password to retrieve the private key used to sign +#AuthnRequest and LogoutRequest messages +PrivateKeyPassword=wso2carbon + +#this will be used as the default IDP config if there isn't any config available for tenants. +default-jwt-client=false diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/testng.xml b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/testng.xml index d166d4b15e..2143071379 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/testng.xml +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/testng.xml @@ -31,6 +31,7 @@ + From a20967e855b7bf34e8fbdc7ac7f6a7aedc521957 Mon Sep 17 00:00:00 2001 From: megala21 Date: Fri, 13 Oct 2017 10:19:00 +0530 Subject: [PATCH 2/4] adding addition test cases to JWT Authenticator --- .../pom.xml | 30 ++++-- .../authenticator/JWTAuthenticator.java | 30 +++--- .../authenticator/JWTAuthenticatorTest.java | 100 +++++++++++++++--- ...nticatorFrameworkServiceComponentTest.java | 43 ++++++++ .../src/test/resources/testng.xml | 3 +- pom.xml | 7 ++ 6 files changed, 177 insertions(+), 36 deletions(-) create mode 100644 components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/internal/WebappAuthenticatorFrameworkServiceComponentTest.java diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/pom.xml b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/pom.xml index 7a0459a37b..db6aa3f59d 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/pom.xml +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/pom.xml @@ -167,21 +167,19 @@ org.wso2.tomcat tomcat-servlet-api - - org.wso2.carbon - org.wso2.carbon.logging - org.wso2.carbon org.wso2.carbon.tomcat.ext - org.wso2.carbon.identity.framework org.wso2.carbon.identity.base + + + org.opensaml + xmltooling + + org.wso2.carbon.identity.framework @@ -190,6 +188,12 @@ org.wso2.carbon.identity.inbound.auth.oauth2 org.wso2.carbon.identity.oauth + org.wso2.carbon @@ -258,6 +262,16 @@ org.wso2.carbon.identity.jwt.client.extension test + + org.slf4j + slf4j-nop + test + + + org.apache.sling + org.apache.sling.testing.osgi-mock + test + diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticator.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticator.java index 7f9a8bb54c..9176f461aa 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticator.java +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticator.java @@ -27,6 +27,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.base.MultitenantConstants; import org.wso2.carbon.base.ServerConfiguration; +import org.wso2.carbon.certificate.mgt.core.bean.Certificate; import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.core.util.KeyStoreManager; import org.wso2.carbon.registry.core.exceptions.RegistryException; @@ -100,7 +101,7 @@ public class JWTAuthenticator implements WebappAuthenticator { requestUri = ""; } StringTokenizer tokenizer = new StringTokenizer(requestUri, "/"); - String context = tokenizer.nextToken(); + String context = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null; if (context == null || "".equals(context)) { authenticationInfo.setStatus(Status.CONTINUE); } @@ -114,7 +115,8 @@ public class JWTAuthenticator implements WebappAuthenticator { issuer = jwsObject.getJWTClaimsSet().getIssuer(); } catch (ParseException e) { log.error("Error occurred while parsing JWT header.", e); - return null; + authenticationInfo.setMessage("Error occured while parsing JWT header"); + return authenticationInfo; } try { @@ -135,7 +137,8 @@ public class JWTAuthenticator implements WebappAuthenticator { String trustStorePassword = serverConfig.getFirstProperty( DEFAULT_TRUST_STORE_PASSWORD); keyStore.load(new FileInputStream(trustStorePath), trustStorePassword.toCharArray()); - publicKey = keyStore.getCertificate(alias).getPublicKey(); + java.security.cert.Certificate certificate = keyStore.getCertificate(alias); + publicKey = certificate == null ? null : certificate.getPublicKey(); } else { authenticationInfo.setStatus(Status.FAILURE); return authenticationInfo; @@ -157,26 +160,25 @@ public class JWTAuthenticator implements WebappAuthenticator { } if (verifier != null && jwsObject.verify(verifier)) { username = MultitenantUtils.getTenantAwareUsername(username); - if (tenantId == -1) { - log.error("tenantDomain is not valid. username : " + username + ", tenantDomain " + - ": " + tenantDomain); + UserStoreManager userStore = AuthenticatorFrameworkDataHolder.getInstance().getRealmService(). + getTenantUserRealm(tenantId).getUserStoreManager(); + if (userStore.isExistingUser(username)) { + authenticationInfo.setTenantId(tenantId); + authenticationInfo.setUsername(username); + authenticationInfo.setTenantDomain(tenantDomain); + authenticationInfo.setStatus(Status.CONTINUE); } else { - UserStoreManager userStore = AuthenticatorFrameworkDataHolder.getInstance().getRealmService(). - getTenantUserRealm(tenantId).getUserStoreManager(); - if (userStore.isExistingUser(username)) { - authenticationInfo.setTenantId(tenantId); - authenticationInfo.setUsername(username); - authenticationInfo.setTenantDomain(tenantDomain); - authenticationInfo.setStatus(Status.CONTINUE); - } + authenticationInfo.setStatus(Status.FAILURE); } } else { authenticationInfo.setStatus(Status.FAILURE); } } catch (UserStoreException e) { log.error("Error occurred while obtaining the user.", e); + authenticationInfo.setStatus(Status.FAILURE); } catch (Exception e) { log.error("Error occurred while verifying the JWT header.", e); + authenticationInfo.setStatus(Status.FAILURE); } finally { PrivilegedCarbonContext.endTenantFlow(); } diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticatorTest.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticatorTest.java index 5ea9b1f88b..e3e3cf22f7 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticatorTest.java +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticatorTest.java @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * you may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + package org.wso2.carbon.webapp.authenticator.framework.authenticator; import org.apache.catalina.connector.Request; @@ -11,13 +29,9 @@ import org.wso2.carbon.identity.jwt.client.extension.dto.JWTConfig; import org.wso2.carbon.identity.jwt.client.extension.exception.JWTClientException; import org.wso2.carbon.identity.jwt.client.extension.util.JWTClientUtil; import org.wso2.carbon.webapp.authenticator.framework.AuthenticationInfo; -import org.wso2.carbon.webapp.authenticator.framework.internal.AuthenticatorFrameworkDataHolder; -import org.wso2.carbon.webapp.authenticator.framework.util.TestTenantIndexingLoader; -import org.wso2.carbon.webapp.authenticator.framework.util.TestTenantRegistryLoader; import java.io.File; import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.IOException; import java.lang.reflect.Field; import java.net.URL; @@ -30,6 +44,8 @@ public class JWTAuthenticatorTest { private Field headersField; private final String JWT_HEADER = "X-JWT-Assertion"; private String jwtToken; + private String wrongJwtToken; + private String jwtTokenWithWrongUser; private static final String SIGNED_JWT_AUTH_USERNAME = "http://wso2.org/claims/enduser"; private static final String SIGNED_JWT_AUTH_TENANT_ID = "http://wso2.org/claims/enduserTenantId"; private Properties properties; @@ -39,9 +55,6 @@ public class JWTAuthenticatorTest { @BeforeClass public void setup() throws NoSuchFieldException, IOException, JWTClientException { jwtAuthenticator = new JWTAuthenticator(); - properties = new Properties(); - properties.setProperty(ISSUER, ALIAS); - jwtAuthenticator.setProperties(properties); headersField = org.apache.coyote.Request.class.getDeclaredField("headers"); headersField.setAccessible(true); ClassLoader classLoader = getClass().getClassLoader(); @@ -60,9 +73,17 @@ public class JWTAuthenticatorTest { customClaims.put(SIGNED_JWT_AUTH_USERNAME, "admin"); customClaims.put(SIGNED_JWT_AUTH_TENANT_ID, String.valueOf(MultitenantConstants.SUPER_TENANT_ID)); jwtToken = JWTClientUtil.generateSignedJWTAssertion("admin", jwtConfig, false, customClaims); + customClaims = new HashMap<>(); + customClaims.put(SIGNED_JWT_AUTH_USERNAME, "admin"); + customClaims.put(SIGNED_JWT_AUTH_TENANT_ID, "-1"); + wrongJwtToken = JWTClientUtil.generateSignedJWTAssertion("admin", jwtConfig, false, customClaims); + customClaims = new HashMap<>(); + customClaims.put(SIGNED_JWT_AUTH_USERNAME, "notexisting"); + customClaims.put(SIGNED_JWT_AUTH_TENANT_ID, String.valueOf(MultitenantConstants.SUPER_TENANT_ID)); + jwtTokenWithWrongUser = JWTClientUtil.generateSignedJWTAssertion("notexisting", jwtConfig, false, customClaims); } - @Test(description = "This method tests the get methods in the JWTAuthenticator") + @Test(description = "This method tests the get methods in the JWTAuthenticator", dependsOnMethods = "testAuthenticate") public void testGetMethods() { Assert.assertEquals(jwtAuthenticator.getName(), "JWT", "GetName method returns wrong value"); Assert.assertNotNull(jwtAuthenticator.getProperties(), "Properties are not properly added to JWT " @@ -87,8 +108,61 @@ public class JWTAuthenticatorTest { Assert.assertTrue(jwtAuthenticator.canHandle(request)); } - @Test(description = "This method tests authenticate method under the successful condition") + @Test(description = "This method tests authenticate method under the successful condition", dependsOnMethods = + {"testAuthentiateFailureScenarios"}) public void testAuthenticate() throws IllegalAccessException, NoSuchFieldException { + Request request = createJWTRequest(jwtToken, "test"); + AuthenticationInfo authenticationInfo = jwtAuthenticator.authenticate(request, null); + Assert.assertNotNull(authenticationInfo.getUsername(), "Proper authentication request is not properly " + + "authenticated by the JWTAuthenticator"); + } + + @Test(description = "This method tests the authenticate method under failure conditions") + public void testAuthentiateFailureScenarios() throws NoSuchFieldException, IllegalAccessException { + Request request = createJWTRequest("test", ""); + AuthenticationInfo authenticationInfo = jwtAuthenticator.authenticate(request, null); + Assert.assertNotNull(authenticationInfo, "Returned authentication info was null"); + Assert.assertNull(authenticationInfo.getUsername(), "Un-authenticated request contain username"); + + request = createJWTRequest(jwtToken, ""); + authenticationInfo = jwtAuthenticator.authenticate(request, null); + Assert.assertNotNull(authenticationInfo, "Returned authentication info was null"); + Assert.assertNull(authenticationInfo.getUsername(), "Un-authenticated request contain username"); + + properties = new Properties(); + properties.setProperty(ISSUER, "test"); + jwtAuthenticator.setProperties(properties); + request = createJWTRequest(jwtToken, ""); + authenticationInfo = jwtAuthenticator.authenticate(request, null); + Assert.assertNotNull(authenticationInfo, "Returned authentication info was null"); + Assert.assertEquals(authenticationInfo.getStatus(), WebappAuthenticator.Status.FAILURE, + "Un authenticated request does not contain status as failure"); + + properties = new Properties(); + properties.setProperty(ISSUER, ALIAS); + jwtAuthenticator.setProperties(properties); + + request = createJWTRequest(wrongJwtToken, ""); + authenticationInfo = jwtAuthenticator.authenticate(request, null); + Assert.assertNotNull(authenticationInfo, "Returned authentication info was null"); + Assert.assertEquals(authenticationInfo.getStatus(), WebappAuthenticator.Status.FAILURE, + "Un authenticated request does not contain status as failure"); + + request = createJWTRequest(jwtTokenWithWrongUser, ""); + authenticationInfo = jwtAuthenticator.authenticate(request, null); + Assert.assertNotNull(authenticationInfo, "Returned authentication info was null"); + Assert.assertEquals(authenticationInfo.getStatus(), WebappAuthenticator.Status.FAILURE, + "Un authenticated request does not contain status as failure"); + } + + + /** + * To create a JWT request with the given jwt header. + * @param jwtToken JWT token to be added to the header + * @param requestUri Request URI to be added to the request. + */ + private Request createJWTRequest(String jwtToken, String requestUri) + throws IllegalAccessException, NoSuchFieldException { Request request = new Request(); org.apache.coyote.Request coyoteRequest = new org.apache.coyote.Request(); MimeHeaders mimeHeaders = new MimeHeaders(); @@ -98,12 +172,12 @@ public class JWTAuthenticatorTest { Field uriMB = org.apache.coyote.Request.class.getDeclaredField("uriMB"); uriMB.setAccessible(true); bytes = MessageBytes.newInstance(); - bytes.setString("test"); + bytes.setString(requestUri); uriMB.set(coyoteRequest, bytes); request.setCoyoteRequest(coyoteRequest); - AuthenticationInfo authenticationInfo = jwtAuthenticator.authenticate(request, null); - Assert.assertNotNull(authenticationInfo.getUsername(), "Proper authentication request is not properly " - + "authenticated by the JWTAuthenticator"); + return request; } + + } diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/internal/WebappAuthenticatorFrameworkServiceComponentTest.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/internal/WebappAuthenticatorFrameworkServiceComponentTest.java new file mode 100644 index 0000000000..a3a06039ec --- /dev/null +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/internal/WebappAuthenticatorFrameworkServiceComponentTest.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * you may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.wso2.carbon.webapp.authenticator.framework.internal; + +import org.apache.sling.testing.mock.osgi.MockOsgi; +import org.testng.annotations.Test; + +/** + * This is a test class for {@link WebappAuthenticatorFrameworkServiceComponent} + */ +public class WebappAuthenticatorFrameworkServiceComponentTest { + + @Test(description = "This method tests whether the bundle activator does not throw any exceptions, even under " + + "possible exception scenarios") + public void testActivateWithException() { + WebappAuthenticatorFrameworkServiceComponent webappAuthenticatorFrameworkServiceComponent = new + WebappAuthenticatorFrameworkServiceComponent(); + webappAuthenticatorFrameworkServiceComponent.activate(null); + } + + @Test(description = "This method tests whether bundle activation succeed with the proper confitions.") + public void testActivateWithoutExceptions() { + WebappAuthenticatorFrameworkServiceComponent webappAuthenticatorFrameworkServiceComponent = new + WebappAuthenticatorFrameworkServiceComponent(); + webappAuthenticatorFrameworkServiceComponent.activate(MockOsgi.newComponentContext()); + } +} diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/testng.xml b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/testng.xml index 2143071379..adc6b26287 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/testng.xml +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/testng.xml @@ -22,7 +22,7 @@ - + @@ -32,6 +32,7 @@ + diff --git a/pom.xml b/pom.xml index 5e2e6c6686..7b2777a540 100644 --- a/pom.xml +++ b/pom.xml @@ -1568,6 +1568,12 @@ + + org.slf4j + slf4j-nop + test + ${slf4j.nop.version} + @@ -2005,6 +2011,7 @@ 1.0b3 1.7.0 1.4.0.wso2v1 + 1.7.25 From fe7c772a6605790b87e7fe65ad62283eb288ce65 Mon Sep 17 00:00:00 2001 From: megala21 Date: Fri, 13 Oct 2017 14:55:47 +0530 Subject: [PATCH 3/4] Adding additional test cases for certificate authenticator --- .../pom.xml | 3 +- .../mgt/core/impl/CertificateGenerator.java | 36 +-- .../CertificateAuthenticator.java | 36 ++- .../authenticator/JWTAuthenticator.java | 9 +- .../CertificateAuthenticatorTest.java | 271 ++++++++++++++++++ .../authenticator/JWTAuthenticatorTest.java | 6 +- .../util/TestCertificateGenerator.java | 120 ++++++++ .../repository/conf/cdm-config.xml | 96 +++++++ .../src/test/resources/sql-scripts/h2.sql | 25 ++ .../src/test/resources/testng.xml | 1 + 10 files changed, 564 insertions(+), 39 deletions(-) create mode 100644 components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/CertificateAuthenticatorTest.java create mode 100644 components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestCertificateGenerator.java create mode 100644 components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/carbon-home/repository/conf/cdm-config.xml create mode 100644 components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/sql-scripts/h2.sql diff --git a/components/certificate-mgt/org.wso2.carbon.certificate.mgt.core/pom.xml b/components/certificate-mgt/org.wso2.carbon.certificate.mgt.core/pom.xml index 4db036d18d..227023b304 100644 --- a/components/certificate-mgt/org.wso2.carbon.certificate.mgt.core/pom.xml +++ b/components/certificate-mgt/org.wso2.carbon.certificate.mgt.core/pom.xml @@ -94,7 +94,8 @@ io.swagger.annotations.*;resolution:=optional, org.wso2.carbon.device.mgt.core.*, org.wso2.carbon.registry.indexing.*, - javax.cache.* + javax.cache.*, + javax.naming.ldap diff --git a/components/certificate-mgt/org.wso2.carbon.certificate.mgt.core/src/main/java/org/wso2/carbon/certificate/mgt/core/impl/CertificateGenerator.java b/components/certificate-mgt/org.wso2.carbon.certificate.mgt.core/src/main/java/org/wso2/carbon/certificate/mgt/core/impl/CertificateGenerator.java index f81539018b..877d606af8 100755 --- a/components/certificate-mgt/org.wso2.carbon.certificate.mgt.core/src/main/java/org/wso2/carbon/certificate/mgt/core/impl/CertificateGenerator.java +++ b/components/certificate-mgt/org.wso2.carbon.certificate.mgt.core/src/main/java/org/wso2/carbon/certificate/mgt/core/impl/CertificateGenerator.java @@ -61,6 +61,9 @@ import org.wso2.carbon.certificate.mgt.core.util.CommonUtil; import org.wso2.carbon.certificate.mgt.core.util.Serializer; import org.wso2.carbon.context.PrivilegedCarbonContext; +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; import javax.security.auth.x500.X500Principal; import javax.xml.bind.DatatypeConverter; import java.io.ByteArrayInputStream; @@ -112,7 +115,7 @@ public class CertificateGenerator { } } } catch (ClassNotFoundException | IOException e) { - String errorMsg = "Error while deserializing the certificate."; + String errorMsg = "Error while during deserialization of the certificate."; throw new CertificateManagementDAOException(errorMsg, e); } @@ -320,10 +323,20 @@ public class CertificateGenerator { CertificateResponse lookUpCertificate = null; KeyStoreReader keyStoreReader = new KeyStoreReader(); if (distinguishedName != null && !distinguishedName.isEmpty()) { - String[] dnSplits = distinguishedName.split("/CN="); - if (dnSplits != null) { - String commonNameExtracted = dnSplits[dnSplits.length - 1]; - lookUpCertificate = keyStoreReader.getCertificateBySerial(commonNameExtracted); + LdapName ldapName; + try { + ldapName = new LdapName(distinguishedName); + } catch (InvalidNameException e) { + throw new KeystoreException( + "Invalid name exception while trying to create a LDAP name using the distinguished name ", e); + } + for (Rdn relativeDistinuguishedNames : ldapName.getRdns()) { + if (relativeDistinuguishedNames.getType().equalsIgnoreCase("CN")) { + System.err.println("CN is: " + relativeDistinuguishedNames.getValue()); + lookUpCertificate = keyStoreReader + .getCertificateBySerial(String.valueOf(relativeDistinuguishedNames.getValue())); + break; + } } } return lookUpCertificate; @@ -409,21 +422,8 @@ public class CertificateGenerator { Date validityEndDate = commonUtil.getValidityEndDate(); X500Name certSubject = new X500Name(CertificateManagementConstants.DEFAULT_PRINCIPAL); - //X500Name certSubject = request.getSubject(); - Attribute attributes[] = request.getAttributes(); -// if (certSubject == null) { -// certSubject = new X500Name(ConfigurationUtil.DEFAULT_PRINCIPAL); -// } else { -// org.bouncycastle.asn1.x500.RDN[] rdn = certSubject.getRDNs(); -// -// if (rdn == null || rdn.length == 0) { -// certSubject = new X500Name(ConfigurationUtil.DEFAULT_PRINCIPAL); -// } -// } - - RDN[] certUniqueIdRDN; BigInteger certUniqueIdentifier; diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/CertificateAuthenticator.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/CertificateAuthenticator.java index 1a3ac9d18d..5a40a3a951 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/CertificateAuthenticator.java +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/CertificateAuthenticator.java @@ -1,3 +1,22 @@ +/* + * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + package org.wso2.carbon.webapp.authenticator.framework.authenticator; import org.apache.catalina.connector.Request; @@ -39,11 +58,8 @@ public class CertificateAuthenticator implements WebappAuthenticator { @Override public boolean canHandle(Request request) { - if (request.getHeader(CERTIFICATE_VERIFICATION_HEADER) != null || request.getHeader(MUTUAL_AUTH_HEADER) != null - || request.getHeader(PROXY_MUTUAL_AUTH_HEADER) != null) { - return true; - } - return false; + return request.getHeader(CERTIFICATE_VERIFICATION_HEADER) != null + || request.getHeader(MUTUAL_AUTH_HEADER) != null || request.getHeader(PROXY_MUTUAL_AUTH_HEADER) != null; } @Override @@ -64,8 +80,12 @@ public class CertificateAuthenticator implements WebappAuthenticator { authenticationInfo = checkCertificateResponse(certificateResponse); } else if (request.getHeader(MUTUAL_AUTH_HEADER) != null) { - X509Certificate[] clientCertificate = (X509Certificate[]) request. - getAttribute(CLIENT_CERTIFICATE_ATTRIBUTE); + Object object = request.getAttribute(CLIENT_CERTIFICATE_ATTRIBUTE); + X509Certificate[] clientCertificate = null; + if (object instanceof X509Certificate[]) { + clientCertificate = (X509Certificate[]) request. + getAttribute(CLIENT_CERTIFICATE_ATTRIBUTE); + } if (clientCertificate != null && clientCertificate[0] != null) { CertificateResponse certificateResponse = AuthenticatorFrameworkDataHolder.getInstance(). getCertificateManagementService().verifyPEMSignature(clientCertificate[0]); @@ -76,7 +96,6 @@ public class CertificateAuthenticator implements WebappAuthenticator { authenticationInfo.setMessage("No client certificate is present"); } } else if (request.getHeader(CERTIFICATE_VERIFICATION_HEADER) != null) { - String certHeader = request.getHeader(CERTIFICATE_VERIFICATION_HEADER); if (certHeader != null && AuthenticatorFrameworkDataHolder.getInstance().getCertificateManagementService(). @@ -105,7 +124,6 @@ public class CertificateAuthenticator implements WebappAuthenticator { EnrolmentInfo enrolmentInfo = tenantedDeviceWrapper.getDevice().getEnrolmentInfo(); authenticationInfo.setUsername(enrolmentInfo.getOwner()); } - authenticationInfo.setStatus(Status.CONTINUE); } } diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticator.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticator.java index 9176f461aa..48831a4d54 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticator.java +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticator.java @@ -27,7 +27,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.base.MultitenantConstants; import org.wso2.carbon.base.ServerConfiguration; -import org.wso2.carbon.certificate.mgt.core.bean.Certificate; import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.core.util.KeyStoreManager; import org.wso2.carbon.registry.core.exceptions.RegistryException; @@ -44,11 +43,7 @@ import java.security.KeyStore; import java.security.PublicKey; import java.security.interfaces.RSAPublicKey; import java.text.ParseException; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Properties; -import java.util.StringTokenizer; +import java.util.*; /** * This authenticator authenticates HTTP requests using JWT header. @@ -115,7 +110,7 @@ public class JWTAuthenticator implements WebappAuthenticator { issuer = jwsObject.getJWTClaimsSet().getIssuer(); } catch (ParseException e) { log.error("Error occurred while parsing JWT header.", e); - authenticationInfo.setMessage("Error occured while parsing JWT header"); + authenticationInfo.setMessage("Error occurred while parsing JWT header"); return authenticationInfo; } try { diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/CertificateAuthenticatorTest.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/CertificateAuthenticatorTest.java new file mode 100644 index 0000000000..511fe7be4d --- /dev/null +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/CertificateAuthenticatorTest.java @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.wso2.carbon.webapp.authenticator.framework.authenticator; + +import org.apache.catalina.Context; +import org.apache.catalina.connector.Request; +import org.apache.catalina.core.StandardContext; +import org.apache.tomcat.util.buf.MessageBytes; +import org.apache.tomcat.util.http.MimeHeaders; +import org.bouncycastle.cert.jcajce.JcaCertStore; +import org.bouncycastle.cms.CMSAbsentContent; +import org.bouncycastle.cms.CMSException; +import org.bouncycastle.cms.CMSSignedData; +import org.bouncycastle.cms.CMSSignedDataGenerator; +import org.h2.jdbcx.JdbcDataSource; +import org.mockito.Mockito; +import org.testng.Assert; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; +import org.wso2.carbon.base.MultitenantConstants; +import org.wso2.carbon.certificate.mgt.core.dao.CertificateManagementDAOFactory; +import org.wso2.carbon.certificate.mgt.core.exception.KeystoreException; +import org.wso2.carbon.certificate.mgt.core.impl.CertificateGenerator; +import org.wso2.carbon.certificate.mgt.core.impl.KeyStoreReader; +import org.wso2.carbon.certificate.mgt.core.scep.SCEPException; +import org.wso2.carbon.certificate.mgt.core.scep.SCEPManager; +import org.wso2.carbon.certificate.mgt.core.scep.SCEPManagerImpl; +import org.wso2.carbon.certificate.mgt.core.scep.TenantedDeviceWrapper; +import org.wso2.carbon.certificate.mgt.core.service.CertificateManagementService; +import org.wso2.carbon.certificate.mgt.core.service.CertificateManagementServiceImpl; +import org.wso2.carbon.device.mgt.common.Device; +import org.wso2.carbon.device.mgt.common.DeviceManagementException; +import org.wso2.carbon.device.mgt.common.EnrolmentInfo; +import org.wso2.carbon.device.mgt.core.config.DeviceConfigurationManager; +import org.wso2.carbon.webapp.authenticator.framework.AuthenticationInfo; +import org.wso2.carbon.webapp.authenticator.framework.internal.AuthenticatorFrameworkDataHolder; +import org.wso2.carbon.webapp.authenticator.framework.util.TestCertificateGenerator; + +import javax.sql.DataSource; +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.net.URL; +import java.security.cert.CertificateEncodingException; +import java.security.cert.X509Certificate; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +/** + * This is a test case for {@link CertificateAuthenticator}. + */ +public class CertificateAuthenticatorTest { + private CertificateAuthenticator certificateAuthenticator; + private Request certificationVerificationRequest; + private Request mutalAuthHeaderRequest; + private Request proxyMutalAuthHeaderRequest; + private Field headersField; + private static final String MUTUAL_AUTH_HEADER = "mutual-auth-header"; + private static final String PROXY_MUTUAL_AUTH_HEADER = "proxy-mutual-auth-header"; + private static final String CERTIFICATE_VERIFICATION_HEADER = "Mdm-Signature"; + private static final String CLIENT_CERTIFICATE_ATTRIBUTE = "javax.servlet.request.X509Certificate"; + private X509Certificate X509certificate; + + @BeforeClass + public void setup() throws KeystoreException, NoSuchFieldException, IllegalAccessException, SQLException, + DeviceManagementException, CertificateEncodingException, CMSException, IOException, SCEPException { + certificateAuthenticator = new CertificateAuthenticator(); + CertificateManagementService certificateManagementService = Mockito + .mock(CertificateManagementServiceImpl.class, Mockito.CALLS_REAL_METHODS); + headersField = org.apache.coyote.Request.class.getDeclaredField("headers"); + headersField.setAccessible(true); + + Field certificateManagementServiceImpl = CertificateManagementServiceImpl.class.getDeclaredField + ("certificateManagementServiceImpl"); + certificateManagementServiceImpl.setAccessible(true); + Field keyStoreReaderField = CertificateManagementServiceImpl.class.getDeclaredField("keyStoreReader"); + keyStoreReaderField.setAccessible(true); + Field certificateGeneratorField = CertificateManagementServiceImpl.class.getDeclaredField + ("certificateGenerator"); + certificateGeneratorField.setAccessible(true); + certificateManagementServiceImpl.set(null, certificateManagementService); + + // Create KeyStore Reader + Field dataSource = CertificateManagementDAOFactory.class.getDeclaredField("dataSource"); + dataSource.setAccessible(true); + dataSource.set(null, createDatabase()); + Field databaseEngine = CertificateManagementDAOFactory.class.getDeclaredField("databaseEngine"); + databaseEngine.setAccessible(true); + databaseEngine.set(null, "H2"); + KeyStoreReader keyStoreReader = new KeyStoreReader(); + keyStoreReaderField.set(null, keyStoreReader); + + CertificateGenerator certificateGenerator = new TestCertificateGenerator(); + certificateGeneratorField.set(null, certificateGenerator); + + AuthenticatorFrameworkDataHolder.getInstance(). + setCertificateManagementService(certificateManagementService); + X509certificate = certificateManagementService.generateX509Certificate(); + + proxyMutalAuthHeaderRequest = createRequest(PROXY_MUTUAL_AUTH_HEADER, String.valueOf(X509certificate)); + System.setProperty("carbon.config.dir.path", + System.getProperty("carbon.home") + File.separator + "repository" + File.separator + "conf"); + DeviceConfigurationManager.getInstance().initConfig(); + certificationVerificationRequest = createRequest(CERTIFICATE_VERIFICATION_HEADER, + createEncodedSignature(X509certificate)); + + mutalAuthHeaderRequest = createRequest(MUTUAL_AUTH_HEADER, "test"); + + SCEPManager scepManager = Mockito.mock(SCEPManagerImpl.class, Mockito.CALLS_REAL_METHODS); + TenantedDeviceWrapper tenantedDeviceWrapper = new TenantedDeviceWrapper(); + tenantedDeviceWrapper.setTenantDomain(MultitenantConstants.SUPER_TENANT_DOMAIN_NAME); + tenantedDeviceWrapper.setTenantId(MultitenantConstants.SUPER_TENANT_ID); + Device device = new Device(); + device.setEnrolmentInfo(new EnrolmentInfo("admin", null, null)); + tenantedDeviceWrapper.setDevice(device); + Mockito.doReturn(tenantedDeviceWrapper).when(scepManager).getValidatedDevice(Mockito.any()); + AuthenticatorFrameworkDataHolder.getInstance().setScepManager(scepManager); + } + + @Test(description = "This test case tests the behaviour of the CertificateAuthenticator for Proxy mutal Auth " + + "Header requests") + public void testRequestsWithProxyMutalAuthHeader() + throws KeystoreException, NoSuchFieldException, IllegalAccessException { + Assert.assertTrue(certificateAuthenticator.canHandle(proxyMutalAuthHeaderRequest), "canHandle method " + + "returned false for a request with all the required header"); + AuthenticationInfo authenticationInfo = certificateAuthenticator + .authenticate(proxyMutalAuthHeaderRequest, null); + Assert.assertNotNull(authenticationInfo, "Authentication Info from Certificate Authenticator is null"); + Assert.assertNull(authenticationInfo.getTenantDomain(), + "Authentication got succeeded without proper certificate"); + + proxyMutalAuthHeaderRequest = createRequest(PROXY_MUTUAL_AUTH_HEADER, + String.valueOf(X509certificate.getIssuerDN())); + authenticationInfo = certificateAuthenticator.authenticate(proxyMutalAuthHeaderRequest, null); + Assert.assertNotNull(authenticationInfo, "Authentication Info from Certificate Authenticator is null"); + Assert.assertNotNull(authenticationInfo.getTenantDomain(), + "Authentication got failed for a proper certificate"); + + CertificateGenerator tempCertificateGenerator = new CertificateGenerator(); + X509Certificate certificateWithOutCN = tempCertificateGenerator.generateX509Certificate(); + proxyMutalAuthHeaderRequest = createRequest(PROXY_MUTUAL_AUTH_HEADER, + String.valueOf(certificateWithOutCN.getIssuerDN())); + authenticationInfo = certificateAuthenticator.authenticate(proxyMutalAuthHeaderRequest, null); + Assert.assertNotNull(authenticationInfo, "Authentication Info from Certificate Authenticator is null"); + Assert.assertEquals(authenticationInfo.getStatus(), WebappAuthenticator.Status.FAILURE, + "Authentication got passed with a certificate without CN"); + + + } + + @Test(description = "This test case tests the behaviour of the CertificateAuthenticator for Certification " + + "Verification Header requests") + public void testRequestCertificateVerificationHeader() + throws CertificateEncodingException, IOException, CMSException, NoSuchFieldException, + IllegalAccessException { + Assert.assertTrue(certificateAuthenticator.canHandle(certificationVerificationRequest), + "canHandle method returned false for a request with all the required header"); + AuthenticationInfo authenticationInfo = certificateAuthenticator + .authenticate(certificationVerificationRequest, null); + Assert.assertNotNull(authenticationInfo, "Authentication Info from Certificate Authenticator is null"); + Assert.assertNull(authenticationInfo.getTenantDomain(), "Authentication got passed without proper certificate"); + authenticationInfo = certificateAuthenticator.authenticate(certificationVerificationRequest, null); + Assert.assertNotNull(authenticationInfo, "Authentication Info from Certificate Authenticator is null"); + Assert.assertEquals(authenticationInfo.getTenantDomain(), MultitenantConstants.SUPER_TENANT_DOMAIN_NAME, + "Authentication failed for a valid request with " + CERTIFICATE_VERIFICATION_HEADER + " header"); + } + + @Test(description = "This test case tests the behaviour of the Certificate Authenticator for the requests with " + + "Mutal Auth Header") + public void testMutalAuthHeaderRequest() { + Assert.assertTrue(certificateAuthenticator.canHandle(mutalAuthHeaderRequest), + "canHandle method returned false for a request with all the required header"); + + AuthenticationInfo authenticationInfo = certificateAuthenticator.authenticate(mutalAuthHeaderRequest, null); + Assert.assertNotNull(authenticationInfo, "Authentication Info from Certificate Authenticator is null"); + Assert.assertEquals(authenticationInfo.getMessage(), "No client certificate is present", + "Authentication got passed without proper certificate"); + + X509Certificate[] x509Certificates = new X509Certificate[1]; + x509Certificates[0] = X509certificate; + mutalAuthHeaderRequest.setAttribute(CLIENT_CERTIFICATE_ATTRIBUTE, x509Certificates); + authenticationInfo = certificateAuthenticator.authenticate(mutalAuthHeaderRequest, null); + Assert.assertNotNull(authenticationInfo, "Authentication Info from Certificate Authenticator is null"); + Assert.assertEquals(authenticationInfo.getTenantDomain(), MultitenantConstants.SUPER_TENANT_DOMAIN_NAME, + "Authentication failed even with proper certificate"); + } + /** + * To create a request that can be understandable by Certificate Authenticator. + * + * @param headerName Name of the header + * @param value Value for the header + * @return Request that is created. + * @throws IllegalAccessException Illegal Access Exception. + * @throws NoSuchFieldException No Such Field Exception. + */ + private Request createRequest(String headerName, String value) throws IllegalAccessException, NoSuchFieldException { + Request request = new Request(); + Context context = new StandardContext(); + request.setContext(context); + org.apache.coyote.Request coyoteRequest = new org.apache.coyote.Request(); + MimeHeaders mimeHeaders = new MimeHeaders(); + MessageBytes bytes = mimeHeaders.addValue(headerName); + bytes.setString(value); + headersField.set(coyoteRequest, mimeHeaders); + + request.setCoyoteRequest(coyoteRequest); + return request; + } + + private DataSource createDatabase() throws SQLException { + URL resourceURL = ClassLoader.getSystemResource("sql-scripts" + File.separator + "h2.sql"); + JdbcDataSource dataSource = new JdbcDataSource(); + dataSource.setURL("jdbc:h2:mem:cert;DB_CLOSE_DELAY=-1"); + dataSource.setUser("sa"); + dataSource.setPassword("sa"); + final String LOAD_DATA_QUERY = "RUNSCRIPT FROM '" + resourceURL.getPath() + "'"; + Connection conn = null; + Statement statement = null; + try { + conn = dataSource.getConnection(); + statement = conn.createStatement(); + statement.execute(LOAD_DATA_QUERY); + } finally { + if (conn != null) { + try { + conn.close(); + } catch (SQLException e) { + + } + } + if (statement != null) { + statement.close(); + } + } + return dataSource; + } + + private String createEncodedSignature(X509Certificate x509Certificate) + throws CertificateEncodingException, CMSException, IOException { + CMSSignedDataGenerator generator = new CMSSignedDataGenerator(); + List list = new ArrayList<>(); + list.add(x509Certificate); + JcaCertStore store = new JcaCertStore(list); + generator.addCertificates(store); + AtomicReference degenerateSd = new AtomicReference<>(generator.generate(new CMSAbsentContent())); + byte[] signature = degenerateSd.get().getEncoded(); + return Base64.getEncoder().encodeToString(signature); + } +} diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticatorTest.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticatorTest.java index e3e3cf22f7..a163afafde 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticatorTest.java +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticatorTest.java @@ -109,7 +109,7 @@ public class JWTAuthenticatorTest { } @Test(description = "This method tests authenticate method under the successful condition", dependsOnMethods = - {"testAuthentiateFailureScenarios"}) + { "testAuthenticateFailureScenarios" }) public void testAuthenticate() throws IllegalAccessException, NoSuchFieldException { Request request = createJWTRequest(jwtToken, "test"); AuthenticationInfo authenticationInfo = jwtAuthenticator.authenticate(request, null); @@ -118,7 +118,7 @@ public class JWTAuthenticatorTest { } @Test(description = "This method tests the authenticate method under failure conditions") - public void testAuthentiateFailureScenarios() throws NoSuchFieldException, IllegalAccessException { + public void testAuthenticateFailureScenarios() throws NoSuchFieldException, IllegalAccessException { Request request = createJWTRequest("test", ""); AuthenticationInfo authenticationInfo = jwtAuthenticator.authenticate(request, null); Assert.assertNotNull(authenticationInfo, "Returned authentication info was null"); @@ -178,6 +178,4 @@ public class JWTAuthenticatorTest { return request; } - - } diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestCertificateGenerator.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestCertificateGenerator.java new file mode 100644 index 0000000000..b532f5699c --- /dev/null +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestCertificateGenerator.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + +package org.wso2.carbon.webapp.authenticator.framework.util; + +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.OperatorCreationException; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; +import org.wso2.carbon.certificate.mgt.core.bean.Certificate; +import org.wso2.carbon.certificate.mgt.core.exception.KeystoreException; +import org.wso2.carbon.certificate.mgt.core.impl.CertificateGenerator; +import org.wso2.carbon.certificate.mgt.core.util.CertificateManagementConstants; +import org.wso2.carbon.certificate.mgt.core.util.CommonUtil; +import org.wso2.carbon.context.PrivilegedCarbonContext; + +import javax.security.auth.x500.X500Principal; +import java.math.BigInteger; +import java.security.*; +import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class TestCertificateGenerator extends CertificateGenerator { + private int count = 0; + + public X509Certificate generateX509Certificate() throws KeystoreException { + BigInteger serialNumber = CommonUtil.generateSerialNumber(); + String defaultPrinciple = "CN=" + serialNumber + ",O=WSO2,OU=Mobile,C=LK"; + + CommonUtil commonUtil = new CommonUtil(); + Date validityBeginDate = commonUtil.getValidityStartDate(); + Date validityEndDate = commonUtil.getValidityEndDate(); + + Security.addProvider(new BouncyCastleProvider()); + + try { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance( + CertificateManagementConstants.RSA, CertificateManagementConstants.PROVIDER); + keyPairGenerator.initialize(CertificateManagementConstants.RSA_KEY_LENGTH, new SecureRandom()); + KeyPair pair = keyPairGenerator.generateKeyPair(); + X500Principal principal = new X500Principal(defaultPrinciple); + X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder( + principal, serialNumber, validityBeginDate, validityEndDate, + principal, pair.getPublic()); + ContentSigner contentSigner = new JcaContentSignerBuilder(CertificateManagementConstants.SHA256_RSA) + .setProvider(CertificateManagementConstants.PROVIDER).build( + pair.getPrivate()); + X509Certificate certificate = new JcaX509CertificateConverter() + .setProvider(CertificateManagementConstants.PROVIDER).getCertificate( + certificateBuilder.build(contentSigner)); + certificate.verify(certificate.getPublicKey()); + List certificates = new ArrayList<>(); + org.wso2.carbon.certificate.mgt.core.bean.Certificate certificateToStore = + new org.wso2.carbon.certificate.mgt.core.bean.Certificate(); + certificateToStore.setTenantId(PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId()); + certificateToStore.setCertificate(certificate); + certificates.add(certificateToStore); + saveCertInKeyStore(certificates); + return certificate; + } catch (NoSuchAlgorithmException e) { + String errorMsg = "No such algorithm found when generating certificate"; + throw new KeystoreException(errorMsg, e); + } catch (NoSuchProviderException e) { + String errorMsg = "No such provider found when generating certificate"; + throw new KeystoreException(errorMsg, e); + } catch (OperatorCreationException e) { + String errorMsg = "Issue in operator creation when generating certificate"; + throw new KeystoreException(errorMsg, e); + } catch (CertificateExpiredException e) { + String errorMsg = "Certificate expired after generating certificate"; + throw new KeystoreException(errorMsg, e); + } catch (CertificateNotYetValidException e) { + String errorMsg = "Certificate not yet valid when generating certificate"; + throw new KeystoreException(errorMsg, e); + } catch (CertificateException e) { + String errorMsg = "Certificate issue occurred when generating certificate"; + throw new KeystoreException(errorMsg, e); + } catch (InvalidKeyException e) { + String errorMsg = "Invalid key used when generating certificate"; + throw new KeystoreException(errorMsg, e); + } catch (SignatureException e) { + String errorMsg = "Signature related issue occurred when generating certificate"; + throw new KeystoreException(errorMsg, e); + } + } + + public String extractChallengeToken(X509Certificate certificate) { + if (count != 0) { + return "WSO2 (Challenge)"; + } else { + count++; + return null; + } + } + +} diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/carbon-home/repository/conf/cdm-config.xml b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/carbon-home/repository/conf/cdm-config.xml new file mode 100644 index 0000000000..70ff0a6f41 --- /dev/null +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/carbon-home/repository/conf/cdm-config.xml @@ -0,0 +1,96 @@ + + + + + + + + jdbc/DM_DS + + + + + 1000 + 60000 + 60000 + true + + org.wso2.carbon.device.mgt.extensions.push.notification.provider.fcm.FCMBasedPushNotificationProvider + + org.wso2.carbon.device.mgt.extensions.push.notification.provider.mqtt.MQTTBasedPushNotificationProvider + org.wso2.carbon.device.mgt.extensions.push.notification.provider.http.HTTPBasedPushNotificationProvider + org.wso2.carbon.device.mgt.extensions.push.notification.provider.xmpp.XMPPBasedPushNotificationProvider + + + + false + + + https://localhost:9443 + admin + admin + + + org.wso2.carbon.policy.mgt + true + 60000 + 5 + 8 + 20 + + + + Simple + + + + 20 + 20 + 20 + 20 + 20 + 20 + + + + true + + + + true + 600 + + 10000 + + + false + 86400 + + + false + false + + BYOD,COPE + + diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/sql-scripts/h2.sql b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/sql-scripts/h2.sql new file mode 100644 index 0000000000..7cf6882829 --- /dev/null +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/sql-scripts/h2.sql @@ -0,0 +1,25 @@ +-- +-- Copyright (c) 2017, 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. + +CREATE TABLE IF NOT EXISTS DM_DEVICE_CERTIFICATE ( + ID INTEGER auto_increment NOT NULL, + SERIAL_NUMBER VARCHAR(500) DEFAULT NULL, + CERTIFICATE BLOB DEFAULT NULL, + TENANT_ID INTEGER DEFAULT 0, + USERNAME VARCHAR(500) DEFAULT NULL, + PRIMARY KEY (ID) +); diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/testng.xml b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/testng.xml index adc6b26287..c9f3f8ad68 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/testng.xml +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/testng.xml @@ -32,6 +32,7 @@ + From bbdd4a88bdf1afc9108ec4db490d1ccfd048d78f Mon Sep 17 00:00:00 2001 From: megala21 Date: Fri, 13 Oct 2017 15:15:33 +0530 Subject: [PATCH 4/4] Refactoring --- .../pom.xml | 6 ---- .../authenticator/JWTAuthenticator.java | 6 +++- .../CertificateAuthenticatorTest.java | 23 +++++++++++--- .../authenticator/JWTAuthenticatorTest.java | 14 +++------ .../util/TestCertificateGenerator.java | 31 +++++++++++-------- .../util/TestTenantIndexingLoader.java | 27 ++++++++++++++-- .../util/TestTenantRegistryLoader.java | 25 +++++++++++++-- .../src/test/resources/jwt.properties | 2 +- 8 files changed, 93 insertions(+), 41 deletions(-) diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/pom.xml b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/pom.xml index 2516a1c37e..506e94d8bf 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/pom.xml +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/pom.xml @@ -188,12 +188,6 @@ org.wso2.carbon.identity.inbound.auth.oauth2 org.wso2.carbon.identity.oauth - org.wso2.carbon diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticator.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticator.java index 48831a4d54..87ef877351 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticator.java +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/main/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticator.java @@ -43,7 +43,11 @@ import java.security.KeyStore; import java.security.PublicKey; import java.security.interfaces.RSAPublicKey; import java.text.ParseException; -import java.util.*; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Properties; +import java.util.StringTokenizer; /** * This authenticator authenticates HTTP requests using JWT header. diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/CertificateAuthenticatorTest.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/CertificateAuthenticatorTest.java index 511fe7be4d..05d8bf1eb0 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/CertificateAuthenticatorTest.java +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/CertificateAuthenticatorTest.java @@ -229,6 +229,12 @@ public class CertificateAuthenticatorTest { return request; } + /** + * To create certificate management database. + * + * @return Datasource. + * @throws SQLException SQL Exception. + */ private DataSource createDatabase() throws SQLException { URL resourceURL = ClassLoader.getSystemResource("sql-scripts" + File.separator + "h2.sql"); JdbcDataSource dataSource = new JdbcDataSource(); @@ -246,9 +252,7 @@ public class CertificateAuthenticatorTest { if (conn != null) { try { conn.close(); - } catch (SQLException e) { - - } + } catch (SQLException e) {} } if (statement != null) { statement.close(); @@ -257,8 +261,17 @@ public class CertificateAuthenticatorTest { return dataSource; } - private String createEncodedSignature(X509Certificate x509Certificate) - throws CertificateEncodingException, CMSException, IOException { + /** + * To create a encoded signature from certificate. + * + * @param x509Certificate Certificate that need to be encoded. + * @return Encoded signature. + * @throws CertificateEncodingException Certificate Encoding Exception. + * @throws CMSException CMS Exception. + * @throws IOException IO Exception. + */ + private String createEncodedSignature(X509Certificate x509Certificate) throws CertificateEncodingException, + CMSException, IOException { CMSSignedDataGenerator generator = new CMSSignedDataGenerator(); List list = new ArrayList<>(); list.add(x509Certificate); diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticatorTest.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticatorTest.java index a163afafde..3a82156b6b 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticatorTest.java +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/authenticator/JWTAuthenticatorTest.java @@ -39,6 +39,9 @@ import java.util.HashMap; import java.util.Map; import java.util.Properties; +/** + * This is a test class for {@link JWTAuthenticator}. + */ public class JWTAuthenticatorTest { private JWTAuthenticator jwtAuthenticator; private Field headersField; @@ -61,14 +64,12 @@ public class JWTAuthenticatorTest { URL resourceUrl = classLoader.getResource("jwt.properties"); File jwtPropertyFile; JWTConfig jwtConfig = null; - if (resourceUrl != null) { jwtPropertyFile = new File(resourceUrl.getFile()); Properties jwtConfigProperties = new Properties(); jwtConfigProperties.load(new FileInputStream(jwtPropertyFile)); jwtConfig = new JWTConfig(jwtConfigProperties); } - Map customClaims = new HashMap<>(); customClaims.put(SIGNED_JWT_AUTH_USERNAME, "admin"); customClaims.put(SIGNED_JWT_AUTH_TENANT_ID, String.valueOf(MultitenantConstants.SUPER_TENANT_ID)); @@ -83,7 +84,8 @@ public class JWTAuthenticatorTest { jwtTokenWithWrongUser = JWTClientUtil.generateSignedJWTAssertion("notexisting", jwtConfig, false, customClaims); } - @Test(description = "This method tests the get methods in the JWTAuthenticator", dependsOnMethods = "testAuthenticate") + @Test(description = "This method tests the get methods in the JWTAuthenticator", + dependsOnMethods = "testAuthenticate") public void testGetMethods() { Assert.assertEquals(jwtAuthenticator.getName(), "JWT", "GetName method returns wrong value"); Assert.assertNotNull(jwtAuthenticator.getProperties(), "Properties are not properly added to JWT " @@ -123,12 +125,10 @@ public class JWTAuthenticatorTest { AuthenticationInfo authenticationInfo = jwtAuthenticator.authenticate(request, null); Assert.assertNotNull(authenticationInfo, "Returned authentication info was null"); Assert.assertNull(authenticationInfo.getUsername(), "Un-authenticated request contain username"); - request = createJWTRequest(jwtToken, ""); authenticationInfo = jwtAuthenticator.authenticate(request, null); Assert.assertNotNull(authenticationInfo, "Returned authentication info was null"); Assert.assertNull(authenticationInfo.getUsername(), "Un-authenticated request contain username"); - properties = new Properties(); properties.setProperty(ISSUER, "test"); jwtAuthenticator.setProperties(properties); @@ -137,17 +137,14 @@ public class JWTAuthenticatorTest { Assert.assertNotNull(authenticationInfo, "Returned authentication info was null"); Assert.assertEquals(authenticationInfo.getStatus(), WebappAuthenticator.Status.FAILURE, "Un authenticated request does not contain status as failure"); - properties = new Properties(); properties.setProperty(ISSUER, ALIAS); jwtAuthenticator.setProperties(properties); - request = createJWTRequest(wrongJwtToken, ""); authenticationInfo = jwtAuthenticator.authenticate(request, null); Assert.assertNotNull(authenticationInfo, "Returned authentication info was null"); Assert.assertEquals(authenticationInfo.getStatus(), WebappAuthenticator.Status.FAILURE, "Un authenticated request does not contain status as failure"); - request = createJWTRequest(jwtTokenWithWrongUser, ""); authenticationInfo = jwtAuthenticator.authenticate(request, null); Assert.assertNotNull(authenticationInfo, "Returned authentication info was null"); @@ -175,7 +172,6 @@ public class JWTAuthenticatorTest { bytes.setString(requestUri); uriMB.set(coyoteRequest, bytes); request.setCoyoteRequest(coyoteRequest); - return request; } } diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestCertificateGenerator.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestCertificateGenerator.java index b532f5699c..5f8f96923d 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestCertificateGenerator.java +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestCertificateGenerator.java @@ -35,7 +35,14 @@ import org.wso2.carbon.context.PrivilegedCarbonContext; import javax.security.auth.x500.X500Principal; import java.math.BigInteger; -import java.security.*; +import java.security.InvalidKeyException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.SecureRandom; +import java.security.Security; +import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateNotYetValidException; @@ -44,34 +51,33 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +/** + * This is a mock implementation of {@link CertificateGenerator}. + */ public class TestCertificateGenerator extends CertificateGenerator { private int count = 0; public X509Certificate generateX509Certificate() throws KeystoreException { BigInteger serialNumber = CommonUtil.generateSerialNumber(); String defaultPrinciple = "CN=" + serialNumber + ",O=WSO2,OU=Mobile,C=LK"; - CommonUtil commonUtil = new CommonUtil(); Date validityBeginDate = commonUtil.getValidityStartDate(); Date validityEndDate = commonUtil.getValidityEndDate(); - Security.addProvider(new BouncyCastleProvider()); try { - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance( - CertificateManagementConstants.RSA, CertificateManagementConstants.PROVIDER); + KeyPairGenerator keyPairGenerator = KeyPairGenerator + .getInstance(CertificateManagementConstants.RSA, CertificateManagementConstants.PROVIDER); keyPairGenerator.initialize(CertificateManagementConstants.RSA_KEY_LENGTH, new SecureRandom()); KeyPair pair = keyPairGenerator.generateKeyPair(); X500Principal principal = new X500Principal(defaultPrinciple); - X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder( - principal, serialNumber, validityBeginDate, validityEndDate, - principal, pair.getPublic()); + X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(principal, serialNumber, + validityBeginDate, validityEndDate, principal, pair.getPublic()); ContentSigner contentSigner = new JcaContentSignerBuilder(CertificateManagementConstants.SHA256_RSA) - .setProvider(CertificateManagementConstants.PROVIDER).build( - pair.getPrivate()); + .setProvider(CertificateManagementConstants.PROVIDER).build(pair.getPrivate()); X509Certificate certificate = new JcaX509CertificateConverter() - .setProvider(CertificateManagementConstants.PROVIDER).getCertificate( - certificateBuilder.build(contentSigner)); + .setProvider(CertificateManagementConstants.PROVIDER) + .getCertificate(certificateBuilder.build(contentSigner)); certificate.verify(certificate.getPublicKey()); List certificates = new ArrayList<>(); org.wso2.carbon.certificate.mgt.core.bean.Certificate certificateToStore = @@ -116,5 +122,4 @@ public class TestCertificateGenerator extends CertificateGenerator { return null; } } - } diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestTenantIndexingLoader.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestTenantIndexingLoader.java index 12203c35d8..3f8b84dacf 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestTenantIndexingLoader.java +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestTenantIndexingLoader.java @@ -1,9 +1,30 @@ +/* + * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ + package org.wso2.carbon.webapp.authenticator.framework.util; import org.wso2.carbon.registry.indexing.service.TenantIndexingLoader; +/** + * This is a mock implementation of {@link TenantIndexingLoader} + */ public class TestTenantIndexingLoader implements TenantIndexingLoader { - @Override public void loadTenantIndex(int i) { - - } + @Override + public void loadTenantIndex(int i) { } } diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestTenantRegistryLoader.java b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestTenantRegistryLoader.java index 1656e91bc0..42d6f04f76 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestTenantRegistryLoader.java +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/java/org/wso2/carbon/webapp/authenticator/framework/util/TestTenantRegistryLoader.java @@ -1,11 +1,30 @@ +/* + * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * + */ package org.wso2.carbon.webapp.authenticator.framework.util; import org.wso2.carbon.registry.core.exceptions.RegistryException; import org.wso2.carbon.registry.core.service.TenantRegistryLoader; +/** + * This is a mock implementation of {@link TenantRegistryLoader} for the test cases. + */ public class TestTenantRegistryLoader implements TenantRegistryLoader { @Override - public void loadTenantRegistry(int i) throws RegistryException { - - } + public void loadTenantRegistry(int i) throws RegistryException { } } diff --git a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/jwt.properties b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/jwt.properties index b7be2e296a..839769e4c3 100644 --- a/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/jwt.properties +++ b/components/webapp-authenticator-framework/org.wso2.carbon.webapp.authenticator.framework/src/test/resources/jwt.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. +# Copyright (c) 2017, 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