You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@archiva.apache.org by ma...@apache.org on 2017/03/10 22:51:07 UTC

[1/8] archiva-redback-core git commit: Implementing Header verification for REST API calls

Repository: archiva-redback-core
Updated Branches:
  refs/heads/master 78d822d14 -> d9146ce8a


Implementing Header verification for REST API calls

Implementing header verification techniques mentioned in:
https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Verifying_Same_Origin_with_Standard_Headers


Project: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/repo
Commit: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/commit/95f1b3e4
Tree: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/tree/95f1b3e4
Diff: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/diff/95f1b3e4

Branch: refs/heads/master
Commit: 95f1b3e4307fb7d407ad0cb83869af2564a1aae8
Parents: 78d822d
Author: Martin Stockhammer <ma...@apache.org>
Authored: Mon Jan 30 20:37:15 2017 +0100
Committer: Martin Stockhammer <ma...@apache.org>
Committed: Mon Jan 30 20:37:15 2017 +0100

----------------------------------------------------------------------
 .../archiva/redback/config-defaults.properties  |   6 +
 .../RequestValidationInterceptor.java           | 213 +++++++++++++++++++
 .../main/resources/META-INF/spring-context.xml  |   1 +
 .../rest/services/AbstractRestServicesTest.java |   5 +
 .../RequestValidationInterceptorTest.java       | 162 ++++++++++++++
 .../redback/rest/services/UserServiceTest.java  |   3 +
 .../mock/MockContainerRequestContext.java       | 175 +++++++++++++++
 .../services/mock/MockUserConfiguration.java    | 105 +++++++++
 8 files changed, 670 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/95f1b3e4/redback-configuration/src/main/resources/org/apache/archiva/redback/config-defaults.properties
----------------------------------------------------------------------
diff --git a/redback-configuration/src/main/resources/org/apache/archiva/redback/config-defaults.properties b/redback-configuration/src/main/resources/org/apache/archiva/redback/config-defaults.properties
index 402aaf9..9fa02ce 100644
--- a/redback-configuration/src/main/resources/org/apache/archiva/redback/config-defaults.properties
+++ b/redback-configuration/src/main/resources/org/apache/archiva/redback/config-defaults.properties
@@ -133,4 +133,10 @@ ldap.bind.authenticator.enabled=false
 user.manager.impl=jdo
 
 
+# REST security settings
 
+# REST base url is for avoiding CSRF attacks
+# If it is not set or empty it tries to determine the base url automatically
+rest.baseUrl=
+# If true, requests without Origin or Referer Header are denied
+rest.csrf.absentorigin.deny=true
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/95f1b3e4/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
----------------------------------------------------------------------
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
new file mode 100644
index 0000000..4300baf
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
@@ -0,0 +1,213 @@
+package org.apache.archiva.redback.rest.services.interceptors;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+import org.apache.archiva.redback.configuration.UserConfiguration;
+import org.apache.cxf.jaxrs.utils.JAXRSUtils;
+import org.apache.cxf.message.Message;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+import javax.inject.Named;
+import javax.servlet.http.HttpServletRequest;
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.container.ContainerRequestFilter;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.Provider;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+/**
+ * Created by Martin Stockhammer on 19.01.17.
+ *
+ * This interceptor tries to check if requests come from a valid origin and
+ * are not generated by another site on behalf of the real client.
+ *
+ * We are using some of the techniques mentioned in
+ * https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet
+ *
+ * Try to find Origin and Referer of the request.
+ * Match them to the target address, that may be either statically configured or is determined
+ * by the Host/X-Forwarded-For Header.
+ *
+ *
+ */
+@Provider
+@Service( "requestValidationInterceptor#rest" )
+public class RequestValidationInterceptor extends AbstractInterceptor implements ContainerRequestFilter {
+
+
+    private static final String X_FORWARDED_PROTO = "X-Forwarded-Proto";
+    private static final String X_FORWARDED_HOST = "X-Forwarded-Host";
+    private static final String ORIGIN = "Origin";
+    private static final String REFERER = "Referer";
+    private static final String CFG_REST_BASE_URL = "rest.baseUrl";
+    private static final String CFG_REST_CSRF_ABSENTORIGIN_DENY = "rest.csrffilter.absentorigin.deny";
+    private static final String CFG_REST_CSRF_ENABLED = "rest.csrffilter.enabled";
+
+    private final Logger log = LoggerFactory.getLogger( getClass() );
+
+    private boolean enabled = true;
+    private boolean useStaticUrl = false;
+    private boolean denyAbsentHeaders = true;
+    private URL baseUrl;
+    private HttpServletRequest httpRequest = null;
+
+    private UserConfiguration config;
+
+    @Inject
+    public RequestValidationInterceptor(@Named( value = "userConfiguration#default" )
+                                                        UserConfiguration config) {
+        this.config = config;
+    }
+
+    @PostConstruct
+    public void init() {
+        String baseUrlStr = config.getString(CFG_REST_BASE_URL, "");
+        if (!"".equals(baseUrlStr.trim())) {
+            try {
+                baseUrl = new URL(baseUrlStr);
+                useStaticUrl = true;
+            } catch (MalformedURLException ex) {
+                log.error("Configured baseUrl (rest.baseUrl={}) is invalid. Message: {}", baseUrlStr, ex.getMessage());
+            }
+        } else {
+            useStaticUrl = false;
+        }
+        denyAbsentHeaders = config.getBoolean(CFG_REST_CSRF_ABSENTORIGIN_DENY,true);
+        enabled = config.getBoolean(CFG_REST_CSRF_ENABLED,true);
+        if (!enabled) {
+            log.info("CSRF Filter is disabled by configuration");
+        }
+    }
+
+    @Override
+    public void filter(ContainerRequestContext containerRequestContext) throws IOException {
+        if (enabled) {
+            HttpServletRequest request = getRequest();
+            URL targetUrl = getTargetUrl(request);
+            if (targetUrl == null) {
+                log.error("Could not verify target URL.");
+                containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build());
+            }
+            if (!checkSourceRequestHeader(targetUrl, request)) {
+                log.warn("HTTP Header check failed. Assuming CSRF attack.");
+                containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build());
+            }
+        }
+    }
+
+    private HttpServletRequest getRequest() {
+        if (httpRequest!=null) {
+            return httpRequest;
+        }  else {
+            Message message = JAXRSUtils.getCurrentMessage();
+            return getHttpServletRequest(message);
+        }
+    }
+
+    private URL getTargetUrl(HttpServletRequest request) {
+        if (useStaticUrl) {
+            return baseUrl;
+        } else {
+            URL requestUrl;
+            try {
+                requestUrl = new URL(request.getRequestURL().toString());
+            } catch (MalformedURLException ex) {
+                log.error("Bad Request URL {}, Message: {}", request.getRequestURL(), ex.getMessage());
+                return null;
+            }
+            String xforwarded = request.getHeader(X_FORWARDED_HOST);
+            String xforwardedProto = request.getHeader(X_FORWARDED_PROTO);
+            if (xforwardedProto==null) {
+                xforwardedProto=requestUrl.getProtocol();
+            }
+            if (xforwarded!=null) {
+                try {
+                    return new URL(xforwardedProto+"://"+xforwarded);
+                } catch (MalformedURLException ex) {
+                    log.warn("X-Forwarded-Host Header is malformed: {}", ex.getMessage());
+                }
+            }
+            return requestUrl;
+        }
+    }
+
+    private int getPort(final URL url) {
+        return url.getPort() > 0 ? url.getPort() : url.getDefaultPort();
+    }
+
+    private boolean checkSourceRequestHeader(final URL targetUrl, final HttpServletRequest request) {
+        boolean headerFound=false;
+        String origin = request.getHeader(ORIGIN);
+        if (origin!=null) {
+            try {
+                URL originUrl = new URL(origin);
+                headerFound=true;
+                log.debug("Origin Header URL found: {}", originUrl);
+                if (!targetUrl.getProtocol().equals(originUrl.getProtocol())) {
+                    log.warn("Origin Header Protocol does not match originUrl={}, targetUrl={}",  originUrl, targetUrl);
+                    return false;
+                }
+                if (!targetUrl.getHost().equals(originUrl.getHost())) {
+                    log.warn("Origin Header Host does not match originUrl={}, targetUrl={}",originUrl,targetUrl);
+                    return false;
+                }
+                int originPort = getPort(originUrl);
+                int targetPort = getPort(targetUrl);
+                if (targetPort != originPort) {
+                    log.warn("Origin Header Port does not match originUrl={}, targetUrl={}",originUrl,targetUrl);
+                    return false;
+                }
+            } catch (MalformedURLException ex) {
+                log.warn("Bad URL in Origin HTTP-Header: {}. Message: {}",origin, ex.getMessage());
+                return false;
+            }
+        }
+        String referer = request.getHeader(REFERER);
+        if (referer!=null) {
+            try {
+                URL refererUrl = new URL(referer);
+                headerFound=true;
+                log.debug("Referer Header URL found: {}",refererUrl);
+                if (!targetUrl.getHost().equals(refererUrl.getHost())) {
+                    log.warn("Referer Header Host does not match refererUrl={}, targetUrl={}",refererUrl,targetUrl);
+                    return false;
+                }
+            } catch (MalformedURLException ex) {
+                log.warn("Bad URL in Referer HTTP-Header: {}, Message: {}", referer, ex.getMessage());
+                return false;
+            }
+        }
+        if (!headerFound && denyAbsentHeaders) {
+            log.warn("Neither Origin nor Referer header found. Request is denied.");
+            return false;
+        }
+        return true;
+    }
+
+    public void setHttpRequest(HttpServletRequest request) {
+        this.httpRequest = request;
+    }
+}

http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/95f1b3e4/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml
----------------------------------------------------------------------
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml b/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml
index 98188f4..4e7b815 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml
@@ -65,6 +65,7 @@
       <ref bean="permissionInterceptor#rest"/>
       <ref bean="redbackServiceExceptionMapper"/>
       <ref bean="passwordRuleViolationExceptionMapper"/>
+      <ref bean="requestValidationInterceptor#rest" />
     </jaxrs:providers>
    </jaxrs:server>
 

http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/95f1b3e4/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/AbstractRestServicesTest.java
----------------------------------------------------------------------
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/AbstractRestServicesTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/AbstractRestServicesTest.java
index 806f322..0cab072 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/AbstractRestServicesTest.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/AbstractRestServicesTest.java
@@ -175,6 +175,7 @@ public abstract class AbstractRestServicesTest
         {
             WebClient.client( service ).header( "Authorization", authzHeader );
         }
+        WebClient.client(service).header("Referer","http://localhost");
         WebClient.client( service ).accept( MediaType.APPLICATION_JSON_TYPE );
         WebClient.client( service ).type( MediaType.APPLICATION_JSON_TYPE );
 
@@ -196,6 +197,8 @@ public abstract class AbstractRestServicesTest
         {
             WebClient.client( service ).header( "Authorization", authzHeader );
         }
+        WebClient.client( service ).header("Referer","http://localhost/");
+
 
         WebClient.client( service ).accept( MediaType.APPLICATION_JSON_TYPE );
         WebClient.client( service ).type( MediaType.APPLICATION_JSON_TYPE );
@@ -216,6 +219,7 @@ public abstract class AbstractRestServicesTest
         {
             WebClient.client( service ).header( "Authorization", authzHeader );
         }
+        WebClient.client( service ).header("Referer","http://localhost/");
 
         WebClient.client( service ).accept( MediaType.APPLICATION_JSON_TYPE );
         WebClient.client( service ).type( MediaType.APPLICATION_JSON_TYPE );
@@ -238,6 +242,7 @@ public abstract class AbstractRestServicesTest
         {
             WebClient.client( service ).header( "Authorization", authzHeader );
         }
+        WebClient.client( service ).header("Referer","http://localhost/");
 
         WebClient.client( service ).accept( MediaType.APPLICATION_JSON_TYPE );
         WebClient.client( service ).type( MediaType.APPLICATION_JSON_TYPE );

http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/95f1b3e4/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/RequestValidationInterceptorTest.java
----------------------------------------------------------------------
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/RequestValidationInterceptorTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/RequestValidationInterceptorTest.java
new file mode 100644
index 0000000..c88492a
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/RequestValidationInterceptorTest.java
@@ -0,0 +1,162 @@
+package org.apache.archiva.redback.rest.services;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+import junit.framework.TestCase;
+import org.apache.archiva.redback.configuration.UserConfigurationException;
+import org.apache.archiva.redback.rest.services.interceptors.RequestValidationInterceptor;
+import org.apache.archiva.redback.rest.services.mock.MockContainerRequestContext;
+import org.apache.archiva.redback.rest.services.mock.MockUserConfiguration;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.springframework.mock.web.MockHttpServletRequest;
+
+import java.io.IOException;
+
+
+
+/**
+ * Created by Martin Stockhammer on 21.01.17.
+ *
+ * Unit Test for RequestValidationInterceptor.
+ *
+ */
+@RunWith(JUnit4.class)
+public class RequestValidationInterceptorTest extends TestCase {
+
+
+
+    @Test
+    public void validateRequestWithoutHeader() throws UserConfigurationException, IOException {
+        MockUserConfiguration cfg = new MockUserConfiguration();
+        RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
+        MockHttpServletRequest request = new MockHttpServletRequest();
+        interceptor.setHttpRequest(request);
+        interceptor.init();
+        MockContainerRequestContext ctx = new MockContainerRequestContext();
+        interceptor.filter(ctx);
+        assertTrue(ctx.isAborted());
+    }
+
+    @Test
+    public void validateRequestWithOrigin() throws UserConfigurationException, IOException {
+        MockUserConfiguration cfg = new MockUserConfiguration();
+        RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
+        MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService");
+        request.setServerName("test.archiva.org");
+        request.addHeader("Origin","http://test.archiva.org/myservlet");
+        interceptor.setHttpRequest(request);
+        interceptor.init();
+        MockContainerRequestContext ctx = new MockContainerRequestContext();
+        interceptor.filter(ctx);
+        assertFalse(ctx.isAborted());
+    }
+
+    @Test
+    public void validateRequestWithBadOrigin() throws UserConfigurationException, IOException {
+        MockUserConfiguration cfg = new MockUserConfiguration();
+        RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
+        MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService");
+        request.setServerName("test.archiva.org");
+        request.addHeader("Origin","http://test2.archiva.org/myservlet");
+        interceptor.setHttpRequest(request);
+        interceptor.init();
+        MockContainerRequestContext ctx = new MockContainerRequestContext();
+        interceptor.filter(ctx);
+        assertTrue(ctx.isAborted());
+    }
+
+    @Test
+    public void validateRequestWithReferer() throws UserConfigurationException, IOException {
+        MockUserConfiguration cfg = new MockUserConfiguration();
+        RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
+        MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService");
+        request.setServerName("test.archiva.org");
+        request.addHeader("Referer","http://test.archiva.org/myservlet2");
+        interceptor.setHttpRequest(request);
+        interceptor.init();
+        MockContainerRequestContext ctx = new MockContainerRequestContext();
+        interceptor.filter(ctx);
+        assertFalse(ctx.isAborted());
+    }
+
+    @Test
+    public void validateRequestWithBadReferer() throws UserConfigurationException, IOException {
+        MockUserConfiguration cfg = new MockUserConfiguration();
+        RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
+        MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService");
+        request.setServerName("test.archiva.org");
+        request.addHeader("Referer","http://test3.archiva.org/myservlet2");
+        interceptor.setHttpRequest(request);
+        interceptor.init();
+        MockContainerRequestContext ctx = new MockContainerRequestContext();
+        interceptor.filter(ctx);
+        assertTrue(ctx.isAborted());
+    }
+
+    @Test
+    public void validateRequestWithOriginAndReferer() throws UserConfigurationException, IOException {
+        MockUserConfiguration cfg = new MockUserConfiguration();
+        RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
+        MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService");
+        request.setServerName("test.archiva.org");
+        request.addHeader("Origin","http://test.archiva.org/myservlet");
+        request.addHeader("Referer","http://test.archiva.org/myservlet2");
+        interceptor.setHttpRequest(request);
+        interceptor.init();
+        MockContainerRequestContext ctx = new MockContainerRequestContext();
+        interceptor.filter(ctx);
+        assertFalse(ctx.isAborted());
+    }
+
+
+    @Test
+    public void validateRequestWithOriginAndStaticUrl() throws UserConfigurationException, IOException {
+        MockUserConfiguration cfg = new MockUserConfiguration();
+        cfg.addValue("rest.baseUrl","http://test.archiva.org");
+        RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
+        MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService");
+        request.setServerName("test4.archiva.org");
+        request.addHeader("Origin","http://test.archiva.org/myservlet");
+        interceptor.setHttpRequest(request);
+        interceptor.init();
+        MockContainerRequestContext ctx = new MockContainerRequestContext();
+        interceptor.filter(ctx);
+        assertFalse(ctx.isAborted());
+    }
+
+    @Test
+    public void validateRequestWithBadOriginAndStaticUrl() throws UserConfigurationException, IOException {
+        MockUserConfiguration cfg = new MockUserConfiguration();
+        cfg.addValue("rest.baseUrl","http://mytest.archiva.org");
+        RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
+        MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService");
+        request.setServerName("mytest.archiva.org");
+        request.addHeader("Origin","http://test.archiva.org/myservlet");
+        interceptor.setHttpRequest(request);
+        interceptor.init();
+        MockContainerRequestContext ctx = new MockContainerRequestContext();
+        interceptor.filter(ctx);
+        assertTrue(ctx.isAborted());
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/95f1b3e4/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/UserServiceTest.java
----------------------------------------------------------------------
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/UserServiceTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/UserServiceTest.java
index b5a80d2..a1d6f57 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/UserServiceTest.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/UserServiceTest.java
@@ -93,6 +93,9 @@ public class UserServiceTest
         throws Exception
     {
 
+        UserService userService = getUserService();
+        WebClient.client( userService ).header( "Origin", "http://localhost/myrequest");
+
         try
         {
             getFakeCreateAdminService().testAuthzWithoutKarmasNeededButAuthz();

http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/95f1b3e4/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/mock/MockContainerRequestContext.java
----------------------------------------------------------------------
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/mock/MockContainerRequestContext.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/mock/MockContainerRequestContext.java
new file mode 100644
index 0000000..ae98a8f
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/mock/MockContainerRequestContext.java
@@ -0,0 +1,175 @@
+package org.apache.archiva.redback.rest.services.mock;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import javax.ws.rs.container.ContainerRequestContext;
+import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Request;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+import javax.ws.rs.core.UriInfo;
+import java.io.InputStream;
+import java.net.URI;
+import java.util.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Created by Martin Stockhammer on 21.01.17.
+ *
+ * RequestContext used in unit tests.
+ */
+public class MockContainerRequestContext implements ContainerRequestContext {
+
+    private boolean aborted = false;
+
+    @Override
+    public Object getProperty(String s) {
+        return null;
+    }
+
+    @Override
+    public Collection<String> getPropertyNames() {
+        return null;
+    }
+
+    @Override
+    public void setProperty(String s, Object o) {
+
+    }
+
+    @Override
+    public void removeProperty(String s) {
+
+    }
+
+    @Override
+    public UriInfo getUriInfo() {
+        return null;
+    }
+
+    @Override
+    public void setRequestUri(URI uri) {
+
+    }
+
+    @Override
+    public void setRequestUri(URI uri, URI uri1) {
+
+    }
+
+    @Override
+    public Request getRequest() {
+        return null;
+    }
+
+
+    @Override
+    public String getMethod() {
+        return null;
+    }
+
+    @Override
+    public void setMethod(String s) {
+
+    }
+
+    @Override
+    public MultivaluedMap<String, String> getHeaders() {
+        return null;
+    }
+
+    @Override
+    public String getHeaderString(String s) {
+        return null;
+    }
+
+    @Override
+    public Date getDate() {
+        return null;
+    }
+
+    @Override
+    public Locale getLanguage() {
+        return null;
+    }
+
+    @Override
+    public int getLength() {
+        return 0;
+    }
+
+    @Override
+    public MediaType getMediaType() {
+        return null;
+    }
+
+    @Override
+    public List<MediaType> getAcceptableMediaTypes() {
+        return null;
+    }
+
+    @Override
+    public List<Locale> getAcceptableLanguages() {
+        return null;
+    }
+
+    @Override
+    public Map<String, Cookie> getCookies() {
+        return null;
+    }
+
+    @Override
+    public boolean hasEntity() {
+        return false;
+    }
+
+    @Override
+    public InputStream getEntityStream() {
+        return null;
+    }
+
+    @Override
+    public void setEntityStream(InputStream inputStream) {
+
+    }
+
+    @Override
+    public SecurityContext getSecurityContext() {
+        return null;
+    }
+
+    @Override
+    public void setSecurityContext(SecurityContext securityContext) {
+
+    }
+
+    @Override
+    public void abortWith(Response response) {
+        this.aborted=true;
+    }
+
+    public boolean isAborted() {
+        return aborted;
+    }
+}

http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/95f1b3e4/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/mock/MockUserConfiguration.java
----------------------------------------------------------------------
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/mock/MockUserConfiguration.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/mock/MockUserConfiguration.java
new file mode 100644
index 0000000..6eaaed8
--- /dev/null
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/mock/MockUserConfiguration.java
@@ -0,0 +1,105 @@
+package org.apache.archiva.redback.rest.services.mock;
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+
+import org.apache.archiva.redback.configuration.UserConfiguration;
+import org.apache.archiva.redback.configuration.UserConfigurationException;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Created by Martin Stockhammer on 21.01.17.
+ *
+ * User configuration implementation to be used in unit tests.
+ *
+ */
+public class MockUserConfiguration implements UserConfiguration {
+
+    private Map<String, String> values = new java.util.HashMap<String,String>();
+
+    @SuppressWarnings("SameParameterValue")
+    public void addValue(String key, String value) {
+        values.put(key,value);
+    }
+
+    @Override
+    public void initialize() throws UserConfigurationException {
+
+    }
+
+    @Override
+    public String getString(String key) {
+        return values.get(key);
+    }
+
+    @Override
+    public String getString(String key, String defaultValue) {
+        if (values.containsKey(key)) {
+            return values.get(key);
+        } else {
+            return defaultValue;
+        }
+    }
+
+    @Override
+    public int getInt(String key) {
+        return getInt(key, -1);
+    }
+
+    @Override
+    public int getInt(String key, int defaultValue) {
+        if (values.containsKey(key)) {
+            return Integer.parseInt(values.get(key));
+        } else {
+            return defaultValue;
+        }
+    }
+
+    @Override
+    public boolean getBoolean(String key) {
+        return getBoolean(key, false);
+    }
+
+    @Override
+    public boolean getBoolean(String key, boolean defaultValue) {
+        if (values.containsKey(key)) {
+            return Boolean.parseBoolean(values.get(key));
+        } else {
+            return defaultValue;
+        }
+    }
+
+    @Override
+    public List<String> getList(String key) {
+        return null;
+    }
+
+    @Override
+    public String getConcatenatedList(String key, String defaultValue) {
+        return null;
+    }
+
+    @Override
+    public Collection<String> getKeys() {
+        return values.keySet();
+    }
+}


[5/8] archiva-redback-core git commit: Adding validationToken to toString method

Posted by ma...@apache.org.
Adding validationToken to toString method


Project: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/repo
Commit: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/commit/461b7e98
Tree: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/tree/461b7e98
Diff: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/diff/461b7e98

Branch: refs/heads/master
Commit: 461b7e98f8648518f4dc9e1759aad0dcf3e18fc9
Parents: a6ceb38
Author: Martin Stockhammer <ma...@apache.org>
Authored: Sun Feb 12 17:12:41 2017 +0100
Committer: Martin Stockhammer <ma...@apache.org>
Committed: Sun Feb 12 17:12:41 2017 +0100

----------------------------------------------------------------------
 .../main/java/org/apache/archiva/redback/rest/api/model/User.java   | 1 +
 1 file changed, 1 insertion(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/461b7e98/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/User.java
----------------------------------------------------------------------
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/User.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/User.java
index 2f3e93e..66287df 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/User.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/User.java
@@ -308,6 +308,7 @@ public class User
             ", assignedRoles=" + assignedRoles +
             ", readOnly=" + readOnly +
             ", userManagerId='" + userManagerId + '\'' +
+            ", validationToken='" + validationToken + '\'' +
             '}';
     }
 


[6/8] archiva-redback-core git commit: Adding log statements

Posted by ma...@apache.org.
Adding log statements


Project: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/repo
Commit: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/commit/98c98b39
Tree: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/tree/98c98b39
Diff: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/diff/98c98b39

Branch: refs/heads/master
Commit: 98c98b3979f35aef0435ca779e5bf73ed22ab3af
Parents: 461b7e9
Author: Martin Stockhammer <ma...@apache.org>
Authored: Sun Feb 12 18:44:55 2017 +0100
Committer: Martin Stockhammer <ma...@apache.org>
Committed: Sun Feb 12 18:44:55 2017 +0100

----------------------------------------------------------------------
 .../archiva/redback/rest/services/DefaultLoginService.java   | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/98c98b39/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLoginService.java
----------------------------------------------------------------------
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLoginService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLoginService.java
index 3bc30bb..33160b4 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLoginService.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLoginService.java
@@ -141,12 +141,15 @@ public class DefaultLoginService
         String userName = loginRequest.getUsername(), password = loginRequest.getPassword();
         PasswordBasedAuthenticationDataSource authDataSource =
             new PasswordBasedAuthenticationDataSource( userName, password );
+        log.debug("Login for {}",userName);
         try
         {
             SecuritySession securitySession = securitySystem.authenticate( authDataSource );
+            log.debug("Security session {}", securitySession);
             if ( securitySession.getAuthenticationResult().isAuthenticated() )
             {
                 org.apache.archiva.redback.users.User user = securitySession.getUser();
+                log.debug("user {} authenticated", user.getUsername());
                 if ( !user.isValidated() )
                 {
                     log.info( "user {} not validated", user.getUsername() );
@@ -156,7 +159,10 @@ public class DefaultLoginService
                 restUser.setReadOnly( securitySystem.userManagerReadOnly() );
                 // validationToken only set during login
                 try {
-                    restUser.setValidationToken(securitySystem.getTokenManager().encryptToken(user.getUsername(), tokenLifetime));
+                    String validationToken = securitySystem.getTokenManager().encryptToken(user.getUsername(), tokenLifetime);
+                    restUser.setValidationToken(validationToken);
+                    log.debug("Validation Token set {}",validationToken);
+
                 } catch (EncryptionFailedException e) {
                     log.error("Validation token could not be created "+e.getMessage());
                 }


[7/8] archiva-redback-core git commit: Adding token validation to interceptor

Posted by ma...@apache.org.
Adding token validation to interceptor


Project: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/repo
Commit: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/commit/6a6b7bb9
Tree: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/tree/6a6b7bb9
Diff: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/diff/6a6b7bb9

Branch: refs/heads/master
Commit: 6a6b7bb93993aaca72bba723d094b76c578b05c3
Parents: 98c98b3
Author: Martin Stockhammer <ma...@apache.org>
Authored: Sun Feb 12 20:47:14 2017 +0100
Committer: Martin Stockhammer <ma...@apache.org>
Committed: Sun Feb 12 20:47:14 2017 +0100

----------------------------------------------------------------------
 .../archiva/redback/config-defaults.properties  |   6 +-
 .../RequestValidationInterceptor.java           | 103 ++++++++++++++++++-
 .../RequestValidationInterceptorTest.java       |  20 +++-
 3 files changed, 124 insertions(+), 5 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/6a6b7bb9/redback-configuration/src/main/resources/org/apache/archiva/redback/config-defaults.properties
----------------------------------------------------------------------
diff --git a/redback-configuration/src/main/resources/org/apache/archiva/redback/config-defaults.properties b/redback-configuration/src/main/resources/org/apache/archiva/redback/config-defaults.properties
index 9fa02ce..4b3e6fa 100644
--- a/redback-configuration/src/main/resources/org/apache/archiva/redback/config-defaults.properties
+++ b/redback-configuration/src/main/resources/org/apache/archiva/redback/config-defaults.properties
@@ -136,7 +136,11 @@ user.manager.impl=jdo
 # REST security settings
 
 # REST base url is for avoiding CSRF attacks
+# Enable CSRF filtering
+rest.csrffilter.enabled=true
 # If it is not set or empty it tries to determine the base url automatically
 rest.baseUrl=
 # If true, requests without Origin or Referer Header are denied
-rest.csrf.absentorigin.deny=true
\ No newline at end of file
+rest.csrffilter.absentorigin.deny=true
+# If true, the validation of the CSRF tokens will be disabled
+rest.csrffilter.disableTokenValidation=false
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/6a6b7bb9/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
----------------------------------------------------------------------
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
index 182d23a..a9afede 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
@@ -19,7 +19,17 @@ package org.apache.archiva.redback.rest.services.interceptors;
  */
 
 
+import org.apache.archiva.redback.authentication.AuthenticationException;
+import org.apache.archiva.redback.authentication.AuthenticationResult;
+import org.apache.archiva.redback.authentication.InvalidTokenException;
+import org.apache.archiva.redback.authentication.TokenData;
+import org.apache.archiva.redback.authentication.TokenManager;
+import org.apache.archiva.redback.authorization.RedbackAuthorization;
 import org.apache.archiva.redback.configuration.UserConfiguration;
+import org.apache.archiva.redback.integration.filter.authentication.basic.HttpBasicAuthentication;
+import org.apache.archiva.redback.policy.AccountLockedException;
+import org.apache.archiva.redback.policy.MustChangePasswordException;
+import org.apache.archiva.redback.users.User;
 import org.apache.cxf.jaxrs.utils.JAXRSUtils;
 import org.apache.cxf.message.Message;
 import org.slf4j.Logger;
@@ -60,15 +70,18 @@ public class RequestValidationInterceptor extends AbstractInterceptor implements
 
     private static final String X_FORWARDED_PROTO = "X-Forwarded-Proto";
     private static final String X_FORWARDED_HOST = "X-Forwarded-Host";
+    private static final String X_XSRF_TOKEN = "X-XSRF-TOKEN";
     private static final String ORIGIN = "Origin";
     private static final String REFERER = "Referer";
-    private static final String CFG_REST_BASE_URL = "rest.baseUrl";
-    private static final String CFG_REST_CSRF_ABSENTORIGIN_DENY = "rest.csrffilter.absentorigin.deny";
-    private static final String CFG_REST_CSRF_ENABLED = "rest.csrffilter.enabled";
+    public static final String CFG_REST_BASE_URL = "rest.baseUrl";
+    public static final String CFG_REST_CSRF_ABSENTORIGIN_DENY = "rest.csrffilter.absentorigin.deny";
+    public static final String CFG_REST_CSRF_ENABLED = "rest.csrffilter.enabled";
+    public static final String CFG_REST_CSRF_DISABLE_TOKEN_VALIDATION = "rest.csrffilter.disableTokenValidation";
 
     private final Logger log = LoggerFactory.getLogger( getClass() );
 
     private boolean enabled = true;
+    private boolean checkToken = true;
     private boolean useStaticUrl = false;
     private boolean denyAbsentHeaders = true;
     private URL baseUrl;
@@ -77,6 +90,14 @@ public class RequestValidationInterceptor extends AbstractInterceptor implements
     private UserConfiguration config;
 
     @Inject
+    @Named( value = "httpAuthenticator#basic" )
+    private HttpBasicAuthentication httpAuthenticator;
+
+    @Inject
+    @Named( value = "tokenManager#default")
+    TokenManager tokenManager;
+
+    @Inject
     public RequestValidationInterceptor(@Named( value = "userConfiguration#default" )
                                                         UserConfiguration config) {
         this.config = config;
@@ -100,6 +121,7 @@ public class RequestValidationInterceptor extends AbstractInterceptor implements
         if (!enabled) {
             log.info("CSRF Filter is disabled by configuration");
         }
+        checkToken = !config.getBoolean(CFG_REST_CSRF_DISABLE_TOKEN_VALIDATION, false);
     }
 
     @Override
@@ -110,14 +132,60 @@ public class RequestValidationInterceptor extends AbstractInterceptor implements
             if (targetUrl == null) {
                 log.error("Could not verify target URL.");
                 containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build());
+                return;
             }
             if (!checkSourceRequestHeader(targetUrl, request)) {
                 log.warn("HTTP Header check failed. Assuming CSRF attack.");
                 containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build());
+                return;
+            }
+
+            if (checkToken) {
+                checkValidationToken(containerRequestContext, request);
             }
         }
     }
 
+    private void checkValidationToken(ContainerRequestContext containerRequestContext, HttpServletRequest request) {
+        Message message = JAXRSUtils.getCurrentMessage();
+        RedbackAuthorization redbackAuthorization = getRedbackAuthorization(message);
+        // We check only services that are restricted
+        if (!redbackAuthorization.noRestriction()) {
+            String tokenString = request.getHeader(X_XSRF_TOKEN);
+            if (tokenString==null || tokenString.length()==0) {
+                log.warn("No validation token header found: {}",X_XSRF_TOKEN);
+                containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build());
+                return;
+            }
+
+            try {
+                TokenData td = tokenManager.decryptToken(tokenString);
+                AuthenticationResult auth = getAuthenticationResult(message, request);
+                if (auth==null) {
+                    log.error("Not authentication data found");
+                    containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build());
+                    return;
+                }
+                User loggedIn = auth.getUser();
+                if (loggedIn==null) {
+                    log.error("User not logged in");
+                    containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build());
+                    return;
+                }
+                String username = loggedIn.getUsername();
+                if (!td.isValid() || !td.getUser().equals(username)) {
+                    log.error("Invalid data in validation token header {} for user {}: isValid={}, username={}",
+                            X_XSRF_TOKEN, username, td.isValid(), td.getUser());
+                    containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build());
+                }
+            } catch (InvalidTokenException e) {
+                log.error("Token validation failed {}", e.getMessage());
+                containerRequestContext.abortWith(Response.status(Response.Status.FORBIDDEN).build());
+            }
+        }
+        log.debug("Token validated");
+    }
+
     private HttpServletRequest getRequest() {
         if (httpRequest!=null) {
             return httpRequest;
@@ -215,4 +283,33 @@ public class RequestValidationInterceptor extends AbstractInterceptor implements
     public void setHttpRequest(HttpServletRequest request) {
         this.httpRequest = request;
     }
+
+    private AuthenticationResult getAuthenticationResult(Message message, HttpServletRequest request) {
+        AuthenticationResult authenticationResult = message.get(AuthenticationResult.class);
+
+        log.debug("authenticationResult from message: {}", authenticationResult);
+        if ( authenticationResult == null )
+        {
+            try
+            {
+                authenticationResult =
+                        httpAuthenticator.getAuthenticationResult( request, getHttpServletResponse( message ) );
+
+                log.debug( "authenticationResult from request: {}", authenticationResult );
+            }
+            catch ( AuthenticationException e )
+            {
+                log.debug( "failed to authenticate for path {}", message.get( Message.REQUEST_URI ) );
+            }
+            catch ( AccountLockedException e )
+            {
+                log.debug( "account locked for path {}", message.get( Message.REQUEST_URI ) );
+            }
+            catch ( MustChangePasswordException e )
+            {
+                log.debug( "must change password for path {}", message.get( Message.REQUEST_URI ) );
+            }
+        }
+        return authenticationResult;
+    }
 }

http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/6a6b7bb9/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/RequestValidationInterceptorTest.java
----------------------------------------------------------------------
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/RequestValidationInterceptorTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/RequestValidationInterceptorTest.java
index c88492a..27ab531 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/RequestValidationInterceptorTest.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/RequestValidationInterceptorTest.java
@@ -20,10 +20,12 @@ package org.apache.archiva.redback.rest.services;
 
 
 import junit.framework.TestCase;
+import org.apache.archiva.redback.authentication.TokenManager;
 import org.apache.archiva.redback.configuration.UserConfigurationException;
 import org.apache.archiva.redback.rest.services.interceptors.RequestValidationInterceptor;
 import org.apache.archiva.redback.rest.services.mock.MockContainerRequestContext;
 import org.apache.archiva.redback.rest.services.mock.MockUserConfiguration;
+import org.apache.archiva.redback.system.SecuritySystem;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -36,7 +38,7 @@ import java.io.IOException;
 /**
  * Created by Martin Stockhammer on 21.01.17.
  *
- * Unit Test for RequestValidationInterceptor.
+ * Unit Test for RequestValidationInterceptor. The unit tests are all without token validation.
  *
  */
 @RunWith(JUnit4.class)
@@ -46,7 +48,9 @@ public class RequestValidationInterceptorTest extends TestCase {
 
     @Test
     public void validateRequestWithoutHeader() throws UserConfigurationException, IOException {
+        TokenManager tm = new TokenManager();
         MockUserConfiguration cfg = new MockUserConfiguration();
+        cfg.addValue(RequestValidationInterceptor.CFG_REST_CSRF_DISABLE_TOKEN_VALIDATION,"true");
         RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
         MockHttpServletRequest request = new MockHttpServletRequest();
         interceptor.setHttpRequest(request);
@@ -58,7 +62,9 @@ public class RequestValidationInterceptorTest extends TestCase {
 
     @Test
     public void validateRequestWithOrigin() throws UserConfigurationException, IOException {
+        TokenManager tm = new TokenManager();
         MockUserConfiguration cfg = new MockUserConfiguration();
+        cfg.addValue(RequestValidationInterceptor.CFG_REST_CSRF_DISABLE_TOKEN_VALIDATION,"true");
         RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
         MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService");
         request.setServerName("test.archiva.org");
@@ -72,7 +78,9 @@ public class RequestValidationInterceptorTest extends TestCase {
 
     @Test
     public void validateRequestWithBadOrigin() throws UserConfigurationException, IOException {
+        TokenManager tm = new TokenManager();
         MockUserConfiguration cfg = new MockUserConfiguration();
+        cfg.addValue(RequestValidationInterceptor.CFG_REST_CSRF_DISABLE_TOKEN_VALIDATION,"true");
         RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
         MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService");
         request.setServerName("test.archiva.org");
@@ -86,7 +94,9 @@ public class RequestValidationInterceptorTest extends TestCase {
 
     @Test
     public void validateRequestWithReferer() throws UserConfigurationException, IOException {
+        TokenManager tm = new TokenManager();
         MockUserConfiguration cfg = new MockUserConfiguration();
+        cfg.addValue(RequestValidationInterceptor.CFG_REST_CSRF_DISABLE_TOKEN_VALIDATION,"true");
         RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
         MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService");
         request.setServerName("test.archiva.org");
@@ -100,7 +110,9 @@ public class RequestValidationInterceptorTest extends TestCase {
 
     @Test
     public void validateRequestWithBadReferer() throws UserConfigurationException, IOException {
+        TokenManager tm = new TokenManager();
         MockUserConfiguration cfg = new MockUserConfiguration();
+        cfg.addValue(RequestValidationInterceptor.CFG_REST_CSRF_DISABLE_TOKEN_VALIDATION,"true");
         RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
         MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService");
         request.setServerName("test.archiva.org");
@@ -114,7 +126,9 @@ public class RequestValidationInterceptorTest extends TestCase {
 
     @Test
     public void validateRequestWithOriginAndReferer() throws UserConfigurationException, IOException {
+        TokenManager tm = new TokenManager();
         MockUserConfiguration cfg = new MockUserConfiguration();
+        cfg.addValue(RequestValidationInterceptor.CFG_REST_CSRF_DISABLE_TOKEN_VALIDATION,"true");
         RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
         MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService");
         request.setServerName("test.archiva.org");
@@ -132,6 +146,8 @@ public class RequestValidationInterceptorTest extends TestCase {
     public void validateRequestWithOriginAndStaticUrl() throws UserConfigurationException, IOException {
         MockUserConfiguration cfg = new MockUserConfiguration();
         cfg.addValue("rest.baseUrl","http://test.archiva.org");
+        cfg.addValue(RequestValidationInterceptor.CFG_REST_CSRF_DISABLE_TOKEN_VALIDATION,"true");
+        TokenManager tm = new TokenManager();
         RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
         MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService");
         request.setServerName("test4.archiva.org");
@@ -147,6 +163,8 @@ public class RequestValidationInterceptorTest extends TestCase {
     public void validateRequestWithBadOriginAndStaticUrl() throws UserConfigurationException, IOException {
         MockUserConfiguration cfg = new MockUserConfiguration();
         cfg.addValue("rest.baseUrl","http://mytest.archiva.org");
+        cfg.addValue(RequestValidationInterceptor.CFG_REST_CSRF_DISABLE_TOKEN_VALIDATION,"true");
+        TokenManager tm = new TokenManager();
         RequestValidationInterceptor interceptor = new RequestValidationInterceptor(cfg);
         MockHttpServletRequest request = new MockHttpServletRequest("GET","/api/v1/userService");
         request.setServerName("mytest.archiva.org");


[3/8] archiva-redback-core git commit: Adding validation token generator got login

Posted by ma...@apache.org.
Adding validation token generator got login


Project: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/repo
Commit: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/commit/de5816ad
Tree: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/tree/de5816ad
Diff: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/diff/de5816ad

Branch: refs/heads/master
Commit: de5816adf044d94245bb9e97249b2d27e6507bce
Parents: e9bc481
Author: Martin Stockhammer <ma...@apache.org>
Authored: Sat Feb 11 20:20:24 2017 +0100
Committer: Martin Stockhammer <ma...@apache.org>
Committed: Sun Feb 12 14:02:01 2017 +0100

----------------------------------------------------------------------
 .../EncryptionFailedException.java              |  47 ++++
 .../authentication/InvalidTokenException.java   |  48 ++++
 .../redback/authentication/SimpleTokenData.java |  88 +++++++
 .../redback/authentication/TokenData.java       |  66 +++++
 .../redback/authentication/TokenManager.java    | 248 +++++++++++++++++++
 .../main/resources/META-INF/spring-context.xml  |   4 +-
 .../authentication/TokenManagerTest.java        | 153 ++++++++++++
 .../archiva/redback/rest/api/model/User.java    |  16 ++
 .../rest/services/DefaultLoginService.java      |  12 +
 .../redback/system/DefaultSecuritySystem.java   |  15 ++
 .../archiva/redback/system/SecuritySystem.java  |   8 +
 11 files changed, 704 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/de5816ad/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/EncryptionFailedException.java
----------------------------------------------------------------------
diff --git a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/EncryptionFailedException.java b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/EncryptionFailedException.java
new file mode 100644
index 0000000..83c1079
--- /dev/null
+++ b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/EncryptionFailedException.java
@@ -0,0 +1,47 @@
+package org.apache.archiva.redback.authentication;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.
+ */
+
+/**
+ *
+ * Exception used by the token manager.
+ *
+ * Created by Martin Stockhammer on 11.02.17.
+ */
+public class EncryptionFailedException extends Exception {
+
+    private static final long serialVersionUID = -2275802156651048276L;
+
+    public EncryptionFailedException() {
+        super();
+    }
+
+    public EncryptionFailedException(String message) {
+        super(message);
+    }
+
+    public EncryptionFailedException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public EncryptionFailedException(Throwable cause) {
+        super(cause);
+    }
+}

http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/de5816ad/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/InvalidTokenException.java
----------------------------------------------------------------------
diff --git a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/InvalidTokenException.java b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/InvalidTokenException.java
new file mode 100644
index 0000000..b3af182
--- /dev/null
+++ b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/InvalidTokenException.java
@@ -0,0 +1,48 @@
+package org.apache.archiva.redback.authentication;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.
+ */
+
+/**
+ * Exception used by the token manager.
+ *
+ * Created by Martin Stockhammer on 11.02.17.
+ */
+public class InvalidTokenException extends Exception {
+
+    private static final long serialVersionUID = -1148088610607667870L;
+
+    public InvalidTokenException() {
+        super();
+    }
+
+    public InvalidTokenException(String message) {
+        super(message);
+    }
+
+    public InvalidTokenException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public InvalidTokenException(Throwable cause) {
+        super(cause);
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/de5816ad/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/SimpleTokenData.java
----------------------------------------------------------------------
diff --git a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/SimpleTokenData.java b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/SimpleTokenData.java
new file mode 100644
index 0000000..e3302bf
--- /dev/null
+++ b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/SimpleTokenData.java
@@ -0,0 +1,88 @@
+package org.apache.archiva.redback.authentication;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ *
+ * Simple Token information class that contains a username and a lifetime.
+ *
+ * The class is not able to detect time manipulations. It is assumed that the
+ * current time of the system is correct.
+ *
+ * This class is immutable.
+ *
+ * Created by Martin Stockhammer on 03.02.17.
+ */
+public final class SimpleTokenData implements Serializable, TokenData {
+
+
+    private static final long serialVersionUID = 5907745449771921813L;
+
+    private final String user;
+    private final Date created;
+    private final Date validBefore;
+    private final long nonce;
+
+
+    /**
+     * Creates a new token info instance for the given user.
+     * The lifetime in milliseconds defines the invalidation date by
+     * adding the lifetime to the current time of instantiation.
+     *
+     * @param user The user name
+     * @param lifetime The number of milliseconds after that the token is invalid
+     * @param nonce Should be a random number and different for each instance.
+     */
+    public SimpleTokenData(final String user, final long lifetime, final long nonce) {
+        this.user=user;
+        this.created=new Date();
+        this.validBefore =new Date(created.getTime()+lifetime);
+        this.nonce = nonce;
+    }
+
+    @Override
+    public final String getUser() {
+        return user;
+    }
+
+    @Override
+    public final Date created() {
+        return created;
+    }
+
+    @Override
+    public final Date validBefore() {
+        return validBefore;
+    }
+
+    @Override
+    public final long getNonce()  {
+        return nonce;
+    }
+
+    @Override
+    public boolean isValid() {
+        return (new Date().getTime())<validBefore.getTime();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/de5816ad/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/TokenData.java
----------------------------------------------------------------------
diff --git a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/TokenData.java b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/TokenData.java
new file mode 100644
index 0000000..f641f3a
--- /dev/null
+++ b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/TokenData.java
@@ -0,0 +1,66 @@
+package org.apache.archiva.redback.authentication;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.util.Date;
+
+/**
+ *
+ * This contains the token payload that is used for verification of tokens.
+ *
+ * Created by Martin Stockhammer on 11.02.17.
+ */
+public interface TokenData {
+
+    /**
+     * Returns the user name.
+     *
+     * @return The username property.
+     */
+    String getUser();
+
+    /**
+     * The date the token was created.
+     *
+     * @return The creation date.
+     */
+    Date created();
+
+    /**
+     * The date after that the token is invalid.
+     *
+     * @return The invalidation date.
+     */
+    Date validBefore();
+
+    /**
+     * The nonce that is stored in the token.
+     *
+     * @return The nonce.
+     */
+    long getNonce();
+
+    /**
+     * Returns true, if the token is valid.
+     *
+     * @return True, if valid, otherwise false.
+     */
+    boolean isValid();
+}

http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/de5816ad/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/TokenManager.java
----------------------------------------------------------------------
diff --git a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/TokenManager.java b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/TokenManager.java
new file mode 100644
index 0000000..08538bd
--- /dev/null
+++ b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/TokenManager.java
@@ -0,0 +1,248 @@
+package org.apache.archiva.redback.authentication;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.commons.codec.binary.Base64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.PostConstruct;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.IvParameterSpec;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InvalidClassException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.Arrays;
+
+
+/**
+ *
+ * Class that manages tokens that are encrypted with a dynamic key. The tokens
+ * are converted into BASE64 strings.
+ *
+ * Each token contains information about username,
+ *
+ * Created by Martin Stockhammer on 03.02.17.
+ */
+@Service("tokenManager#jce")
+public class TokenManager {
+
+    private static final SecureRandom rd = new SecureRandom();
+    private final Logger log = LoggerFactory.getLogger(getClass());
+    private String algorithm = "AES/ECB/PKCS5Padding";
+    private int keySize = -1;
+    private Cipher deCipher;
+    private Cipher enCipher;
+
+    boolean paddingUsed = true;
+
+
+    @PostConstruct
+    public void initialize() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, EncryptionFailedException, InvalidAlgorithmParameterException {
+        log.debug("Initializing key for token generator");
+        try {
+            enCipher = Cipher.getInstance(algorithm);
+            deCipher = Cipher.getInstance(algorithm);
+            String[] keyAlg = enCipher.getAlgorithm().split("/");
+            if (keyAlg.length<1) {
+                throw new EncryptionFailedException("Initialization of key failed. Not algorithm found.");
+            }
+            String encryptionAlgorithm = keyAlg[0];
+            KeyGenerator keyGen = KeyGenerator.getInstance(encryptionAlgorithm);
+            if (keySize>0) {
+                keyGen.init(keySize);
+            }
+            if (keyAlg.length==3 && keyAlg[2].equals("NoPadding")) {
+                paddingUsed=false;
+            }
+            SecretKey secretKey = keyGen.generateKey();
+            enCipher.init(Cipher.ENCRYPT_MODE, secretKey);
+            // We have to provide the IV depending on the algorithm used
+            // CBC needs an IV, ECB not.
+            if (enCipher.getIV()==null) {
+                deCipher.init(Cipher.DECRYPT_MODE, secretKey);
+            } else {
+                deCipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(enCipher.getIV()));
+            }
+        } catch (NoSuchAlgorithmException e) {
+            log.error("Error occurred during key initialization. Requested algorithm not available. "+e.getMessage());
+            throw e;
+        } catch (NoSuchPaddingException e) {
+            log.error("Error occurred during key initialization. Requested padding not available. "+e.getMessage());
+            throw e;
+        } catch (InvalidKeyException e) {
+            log.error("The key is not valid.");
+            throw e;
+        } catch (InvalidAlgorithmParameterException e) {
+            log.error("Invalid encryption parameters.");
+            throw e;
+        }
+    }
+
+
+    @SuppressWarnings("SameParameterValue")
+    public String encryptToken(String user, long lifetime) throws EncryptionFailedException {
+        return encryptToken(new SimpleTokenData(user, lifetime, createNonce()));
+    }
+
+    public String encryptToken(TokenData tokenData) throws EncryptionFailedException {
+        try {
+            return encode(encrypt(tokenData));
+        } catch (IOException e) {
+            log.error("Error during object conversion: "+e.getMessage());
+            throw new EncryptionFailedException(e);
+        } catch (BadPaddingException e) {
+            log.error("Padding invalid");
+            throw new EncryptionFailedException(e);
+        } catch (IllegalBlockSizeException e) {
+            log.error("Block size invalid");
+            throw new EncryptionFailedException(e);
+        }
+    }
+
+    public TokenData decryptToken(String token) throws InvalidTokenException {
+        try {
+            return decrypt(decode(token));
+        } catch (IOException ex) {
+            log.error("Error during data read. " + ex.getMessage());
+            throw new InvalidTokenException(ex);
+        } catch (ClassNotFoundException ex) {
+            log.error("Token data invalid.");
+            throw new InvalidTokenException(ex);
+        } catch (BadPaddingException ex) {
+            log.error("The encrypted token has the wrong padding.");
+            throw new InvalidTokenException(ex);
+        } catch (IllegalBlockSizeException ex) {
+            log.error("The encrypted token has the wrong block size.");
+            throw new InvalidTokenException(ex);
+        }
+    }
+
+    private long createNonce() {
+        return rd.nextLong();
+    }
+
+    protected byte[] encrypt(TokenData info) throws IOException, BadPaddingException, IllegalBlockSizeException {
+        return doEncrypt(convertToByteArray(info));
+    }
+
+    protected byte[] doEncrypt(byte[] data) throws BadPaddingException, IllegalBlockSizeException {
+        byte[] encData;
+        if (!paddingUsed && (data.length % enCipher.getBlockSize())!=0) {
+            int blocks = data.length / enCipher.getBlockSize();
+            encData = Arrays.copyOf(data, enCipher.getBlockSize()*(blocks+1));
+        } else {
+            encData = data;
+        }
+        return enCipher.doFinal(encData);
+    }
+
+    protected TokenData decrypt(byte[] token) throws BadPaddingException, IllegalBlockSizeException, IOException, ClassNotFoundException {
+        Object result = convertFromByteArray(doDecrypt(token));
+        if (!(result instanceof TokenData)) {
+            throw new InvalidClassException("No TokenData found in decrypted token");
+        }
+        return (TokenData)result;
+    }
+
+    protected byte[] doDecrypt(byte[] encryptedData) throws BadPaddingException, IllegalBlockSizeException {
+        return deCipher.doFinal(encryptedData);
+    }
+
+    private String encode(byte[] token) {
+        return Base64.encodeBase64String(token);
+    }
+
+    private byte[] decode(String token) {
+        return Base64.decodeBase64(token);
+    }
+
+
+    private Object convertFromByteArray(byte[] byteObject) throws IOException,
+            ClassNotFoundException {
+        ByteArrayInputStream bais;
+        ObjectInputStream in;
+        bais = new ByteArrayInputStream(byteObject);
+        in = new ObjectInputStream(bais);
+        Object o = in.readObject();
+        in.close();
+        return o;
+
+    }
+
+
+    private byte[] convertToByteArray(Object complexObject) throws IOException {
+        ByteArrayOutputStream baos;
+        ObjectOutputStream out;
+        baos = new ByteArrayOutputStream();
+        out = new ObjectOutputStream(baos);
+        out.writeObject(complexObject);
+        out.close();
+        return baos.toByteArray();
+    }
+
+    public String getAlgorithm() {
+        return algorithm;
+    }
+
+    /**
+     * Sets the encryption algorithm and resets the key size. You may change the key size after
+     * calling this method.
+     * Additionally run the initialize() method after setting algorithm and keysize.
+     *
+     *
+     * @param algorithm The encryption algorithm to use.
+     */
+    public void setAlgorithm(String algorithm) {
+        if (!this.algorithm.equals(algorithm)) {
+            this.algorithm = algorithm;
+            this.keySize=-1;
+        }
+    }
+
+    public int getKeySize() {
+        return keySize;
+    }
+
+    /**
+     * Sets the key size for the encryption. This method must be called after
+     * setting the algorithm. The keysize will be reset by calling <code>setAlgorithm()</code>
+     *
+     * The key size must be valid for the given algorithm.
+     *
+     * @param keySize The size of the encryption key
+     */
+    public void setKeySize(int keySize) {
+        this.keySize = keySize;
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/de5816ad/redback-authentication/redback-authentication-api/src/main/resources/META-INF/spring-context.xml
----------------------------------------------------------------------
diff --git a/redback-authentication/redback-authentication-api/src/main/resources/META-INF/spring-context.xml b/redback-authentication/redback-authentication-api/src/main/resources/META-INF/spring-context.xml
index a07857c..d2aab37 100644
--- a/redback-authentication/redback-authentication-api/src/main/resources/META-INF/spring-context.xml
+++ b/redback-authentication/redback-authentication-api/src/main/resources/META-INF/spring-context.xml
@@ -30,5 +30,7 @@
   <context:annotation-config />
   <context:component-scan 
     base-package="org.apache.archiva.redback.authentication"/>
- 
+
+  <alias name="tokenManager#jce" alias="tokenManager#default" />
+
 </beans>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/de5816ad/redback-authentication/redback-authentication-api/src/test/java/org/apache/archiva/redback/authentication/TokenManagerTest.java
----------------------------------------------------------------------
diff --git a/redback-authentication/redback-authentication-api/src/test/java/org/apache/archiva/redback/authentication/TokenManagerTest.java b/redback-authentication/redback-authentication-api/src/test/java/org/apache/archiva/redback/authentication/TokenManagerTest.java
new file mode 100644
index 0000000..5200956
--- /dev/null
+++ b/redback-authentication/redback-authentication-api/src/test/java/org/apache/archiva/redback/authentication/TokenManagerTest.java
@@ -0,0 +1,153 @@
+package org.apache.archiva.redback.authentication;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.junit.Test;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+
+import static org.junit.Assert.*;
+
+/**
+ * Test the TokenManager implementation. Uses no spring dependencies.
+ *
+ *  Created by Martin Stockhammer on 11.02.17.
+ */
+public class TokenManagerTest {
+
+    @Test
+    public void initialize() throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, EncryptionFailedException, InvalidAlgorithmParameterException {
+        TokenManager tokenManager = new TokenManager();
+        tokenManager.initialize();
+    }
+
+    @Test
+    public void encryptToken() throws Exception {
+        TokenManager tokenManager = new TokenManager();
+        tokenManager.initialize();
+        assertEquals(tokenManager.getAlgorithm(),"AES/ECB/PKCS5Padding");
+        assertEquals(tokenManager.getKeySize(), -1);
+        String token = tokenManager.encryptToken("testuser01",1000);
+        assertNotNull(token);
+        assertTrue("Token size too low",token.length()>300);
+
+    }
+
+    @Test
+    public void decryptToken() throws Exception {
+        TokenManager tokenManager = new TokenManager();
+        tokenManager.initialize();
+        String token = tokenManager.encryptToken("testuser00003",1000);
+        assertNotNull(token);
+        assertTrue("Token size too low",token.length()>300);
+        TokenData tokenData = tokenManager.decryptToken(token);
+        assertNotNull(tokenData);
+        assertEquals("testuser00003", tokenData.getUser());
+        assertTrue(tokenData.isValid());
+
+    }
+
+    @Test
+    public void decryptExpiredToken() throws Exception {
+        TokenManager tokenManager = new TokenManager();
+        tokenManager.initialize();
+        SimpleTokenData sToken = new SimpleTokenData("testuser00003", 0, 1345455);
+        String token = tokenManager.encryptToken(sToken);
+        assertNotNull(token);
+        assertTrue("Token size too low",token.length()>300);
+        TokenData tokenData = tokenManager.decryptToken(token);
+        assertNotNull(tokenData);
+        assertEquals("testuser00003", tokenData.getUser());
+        assertFalse(tokenData.isValid());
+
+    }
+
+    @Test(expected = InvalidTokenException.class)
+    public void decryptInvalidToken() throws Exception {
+        TokenManager tokenManager = new TokenManager();
+        tokenManager.initialize();
+        SimpleTokenData sToken = new SimpleTokenData("testuser00003", 0, 1345455);
+        String token = tokenManager.encryptToken(sToken);
+        assertNotNull(token);
+        assertTrue("Token size too low",token.length()>300);
+        tokenManager.initialize();
+        tokenManager.decryptToken(token);
+
+    }
+
+    @Test
+    public void decryptTokenWithDifferentAlgorithm() throws Exception {
+        TokenManager tokenManager = new TokenManager();
+        tokenManager.setAlgorithm("DES/ECB/PKCS5Padding");
+        tokenManager.initialize();
+        String token = tokenManager.encryptToken("testuser00005",2000);
+        assertNotNull(token);
+        assertTrue("Token size too low",token.length()>300);
+        TokenData tokenData = tokenManager.decryptToken(token);
+        assertNotNull(tokenData);
+        assertEquals("testuser00005", tokenData.getUser());
+        assertTrue(tokenData.isValid());
+
+
+        tokenManager.setAlgorithm("DES/ECB/NoPadding");
+        tokenManager.initialize();
+        token = tokenManager.encryptToken("testuser00006",2000);
+        assertNotNull(token);
+        assertTrue("Token size too low",token.length()>300);
+        tokenData = tokenManager.decryptToken(token);
+        assertNotNull(tokenData);
+        assertEquals("testuser00006", tokenData.getUser());
+        assertTrue(tokenData.isValid());
+
+    }
+
+    @Test
+    public void nativeEncryption() throws EncryptionFailedException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidTokenException, BadPaddingException, IllegalBlockSizeException {
+        TokenManager tokenManager = new TokenManager();
+        tokenManager.setAlgorithm("DES/CBC/PKCS5Padding");
+        tokenManager.setKeySize(56);
+        tokenManager.initialize();
+        byte[] data = { 1, 5, 12, 18, 124, 44, 88, -28, -44};
+        byte[] token = tokenManager.doEncrypt(data);
+        assertNotNull(token);
+        byte[] tokenData = tokenManager.doDecrypt(token);
+        assertNotNull(tokenData);
+        assertArrayEquals(data, tokenData);
+
+
+        tokenManager.setAlgorithm("AES/CBC/NoPadding");
+        tokenManager.setKeySize(128);
+        tokenManager.initialize();
+        token = tokenManager.doEncrypt(data);
+        assertNotNull(token);
+        // Without padding the decrypted value is a multiple of the block size.
+        tokenData = Arrays.copyOf(tokenManager.doDecrypt(token), data.length);
+        assertNotNull(tokenData);
+        assertArrayEquals(data, tokenData);
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/de5816ad/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/User.java
----------------------------------------------------------------------
diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/User.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/User.java
index 1b6d50c..2f3e93e 100644
--- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/User.java
+++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/User.java
@@ -80,6 +80,14 @@ public class User
      */
     private String userManagerId;
 
+    /**
+     * for request validation
+     *
+     * @since 2.2
+     */
+    private String validationToken;
+
+
     public User()
     {
         // no op
@@ -272,6 +280,14 @@ public class User
         this.userManagerId = userManagerId;
     }
 
+    public String getValidationToken() {
+        return validationToken;
+    }
+
+    public void setValidationToken(String validationToken) {
+        this.validationToken = validationToken;
+    }
+
     @Override
     public String toString()
     {

http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/de5816ad/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLoginService.java
----------------------------------------------------------------------
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLoginService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLoginService.java
index ee3cc47..3bc30bb 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLoginService.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultLoginService.java
@@ -22,7 +22,9 @@ package org.apache.archiva.redback.rest.services;
 import org.apache.archiva.redback.authentication.AuthenticationConstants;
 import org.apache.archiva.redback.authentication.AuthenticationException;
 import org.apache.archiva.redback.authentication.AuthenticationFailureCause;
+import org.apache.archiva.redback.authentication.EncryptionFailedException;
 import org.apache.archiva.redback.authentication.PasswordBasedAuthenticationDataSource;
+import org.apache.archiva.redback.authentication.TokenManager;
 import org.apache.archiva.redback.integration.filter.authentication.HttpAuthenticator;
 import org.apache.archiva.redback.keys.AuthenticationKey;
 import org.apache.archiva.redback.keys.KeyManager;
@@ -75,6 +77,9 @@ public class DefaultLoginService
     @Context
     private HttpServletRequest httpServletRequest;
 
+    // validation token lifetime: 3 hours
+    long tokenLifetime = 1000*3600*3;
+
     @Inject
     public DefaultLoginService( SecuritySystem securitySystem,
                                 @Named( "httpAuthenticator#basic" ) HttpAuthenticator httpAuthenticator )
@@ -149,6 +154,13 @@ public class DefaultLoginService
                 }
                 User restUser = buildRestUser( user );
                 restUser.setReadOnly( securitySystem.userManagerReadOnly() );
+                // validationToken only set during login
+                try {
+                    restUser.setValidationToken(securitySystem.getTokenManager().encryptToken(user.getUsername(), tokenLifetime));
+                } catch (EncryptionFailedException e) {
+                    log.error("Validation token could not be created "+e.getMessage());
+                }
+
                 // here create an http session
                 httpAuthenticator.authenticate( authDataSource, httpServletRequest.getSession( true ) );
                 return restUser;

http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/de5816ad/redback-system/src/main/java/org/apache/archiva/redback/system/DefaultSecuritySystem.java
----------------------------------------------------------------------
diff --git a/redback-system/src/main/java/org/apache/archiva/redback/system/DefaultSecuritySystem.java b/redback-system/src/main/java/org/apache/archiva/redback/system/DefaultSecuritySystem.java
index 2f90c79..811903e 100644
--- a/redback-system/src/main/java/org/apache/archiva/redback/system/DefaultSecuritySystem.java
+++ b/redback-system/src/main/java/org/apache/archiva/redback/system/DefaultSecuritySystem.java
@@ -19,10 +19,12 @@ package org.apache.archiva.redback.system;
  * under the License.
  */
 
+import com.sun.javafx.fxml.expression.Expression;
 import org.apache.archiva.redback.authentication.AuthenticationDataSource;
 import org.apache.archiva.redback.authentication.AuthenticationException;
 import org.apache.archiva.redback.authentication.AuthenticationManager;
 import org.apache.archiva.redback.authentication.AuthenticationResult;
+import org.apache.archiva.redback.authentication.TokenManager;
 import org.apache.archiva.redback.authorization.AuthorizationDataSource;
 import org.apache.archiva.redback.authorization.AuthorizationException;
 import org.apache.archiva.redback.authorization.AuthorizationResult;
@@ -69,6 +71,10 @@ public class DefaultSecuritySystem
     private KeyManager keyManager;
 
     @Inject
+    @Named( value = "tokenManager#default")
+    private TokenManager tokenManager;
+
+    @Inject
     private UserSecurityPolicy policy;
 
     // ----------------------------------------------------------------------------
@@ -305,4 +311,13 @@ public class DefaultSecuritySystem
         return userManager.isReadOnly();
     }
 
+    @Override
+    public TokenManager getTokenManager() {
+        return tokenManager;
+    }
+
+    public void setTokenManager(TokenManager tokenManager) {
+        this.tokenManager = tokenManager;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/de5816ad/redback-system/src/main/java/org/apache/archiva/redback/system/SecuritySystem.java
----------------------------------------------------------------------
diff --git a/redback-system/src/main/java/org/apache/archiva/redback/system/SecuritySystem.java b/redback-system/src/main/java/org/apache/archiva/redback/system/SecuritySystem.java
index c7b049d..f12868a 100644
--- a/redback-system/src/main/java/org/apache/archiva/redback/system/SecuritySystem.java
+++ b/redback-system/src/main/java/org/apache/archiva/redback/system/SecuritySystem.java
@@ -19,6 +19,7 @@ package org.apache.archiva.redback.system;
  * under the License.
  */
 
+import org.apache.archiva.redback.authentication.TokenManager;
 import org.apache.archiva.redback.policy.AccountLockedException;
 import org.apache.archiva.redback.policy.MustChangePasswordException;
 import org.apache.archiva.redback.policy.UserSecurityPolicy;
@@ -111,5 +112,12 @@ public interface SecuritySystem
      * @since 2.1
      */
     boolean userManagerReadOnly();
+
+    /**
+     * Returns the token manager implementation.
+     *
+     * @since 2.2
+     */
+    TokenManager getTokenManager();
 }
 


[4/8] archiva-redback-core git commit: Using random iv vector to generate unique tokens for each login

Posted by ma...@apache.org.
Using random iv vector to generate unique tokens for each login


Project: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/repo
Commit: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/commit/a6ceb384
Tree: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/tree/a6ceb384
Diff: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/diff/a6ceb384

Branch: refs/heads/master
Commit: a6ceb384697b06d361c3323e24501cc7f2475900
Parents: de5816a
Author: Martin Stockhammer <ma...@apache.org>
Authored: Sun Feb 12 17:09:15 2017 +0100
Committer: Martin Stockhammer <ma...@apache.org>
Committed: Sun Feb 12 17:09:15 2017 +0100

----------------------------------------------------------------------
 .../redback/authentication/TokenManager.java    | 115 +++++++++++++++----
 .../authentication/TokenManagerTest.java        |   7 +-
 2 files changed, 94 insertions(+), 28 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/a6ceb384/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/TokenManager.java
----------------------------------------------------------------------
diff --git a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/TokenManager.java b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/TokenManager.java
index 08538bd..e37c1d4 100644
--- a/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/TokenManager.java
+++ b/redback-authentication/redback-authentication-api/src/main/java/org/apache/archiva/redback/authentication/TokenManager.java
@@ -20,6 +20,7 @@ package org.apache.archiva.redback.authentication;
  */
 
 import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang.ArrayUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Service;
@@ -57,12 +58,12 @@ import java.util.Arrays;
 @Service("tokenManager#jce")
 public class TokenManager {
 
-    private static final SecureRandom rd = new SecureRandom();
+    private final ThreadLocal<SecureRandom> rd = new ThreadLocal<SecureRandom>();
     private final Logger log = LoggerFactory.getLogger(getClass());
-    private String algorithm = "AES/ECB/PKCS5Padding";
+    private String algorithm = "AES/CBC/PKCS5Padding";
     private int keySize = -1;
-    private Cipher deCipher;
-    private Cipher enCipher;
+    private int ivSize = -1;
+    private SecretKey secretKey;
 
     boolean paddingUsed = true;
 
@@ -71,8 +72,8 @@ public class TokenManager {
     public void initialize() throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, EncryptionFailedException, InvalidAlgorithmParameterException {
         log.debug("Initializing key for token generator");
         try {
-            enCipher = Cipher.getInstance(algorithm);
-            deCipher = Cipher.getInstance(algorithm);
+            rd.set(new SecureRandom());
+            Cipher enCipher = Cipher.getInstance(algorithm);
             String[] keyAlg = enCipher.getAlgorithm().split("/");
             if (keyAlg.length<1) {
                 throw new EncryptionFailedException("Initialization of key failed. Not algorithm found.");
@@ -85,14 +86,14 @@ public class TokenManager {
             if (keyAlg.length==3 && keyAlg[2].equals("NoPadding")) {
                 paddingUsed=false;
             }
-            SecretKey secretKey = keyGen.generateKey();
+            this.secretKey = keyGen.generateKey();
             enCipher.init(Cipher.ENCRYPT_MODE, secretKey);
             // We have to provide the IV depending on the algorithm used
             // CBC needs an IV, ECB not.
             if (enCipher.getIV()==null) {
-                deCipher.init(Cipher.DECRYPT_MODE, secretKey);
+                ivSize=-1;
             } else {
-                deCipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(enCipher.getIV()));
+                ivSize=enCipher.getIV().length;
             }
         } catch (NoSuchAlgorithmException e) {
             log.error("Error occurred during key initialization. Requested algorithm not available. "+e.getMessage());
@@ -103,14 +104,9 @@ public class TokenManager {
         } catch (InvalidKeyException e) {
             log.error("The key is not valid.");
             throw e;
-        } catch (InvalidAlgorithmParameterException e) {
-            log.error("Invalid encryption parameters.");
-            throw e;
         }
     }
 
-
-    @SuppressWarnings("SameParameterValue")
     public String encryptToken(String user, long lifetime) throws EncryptionFailedException {
         return encryptToken(new SimpleTokenData(user, lifetime, createNonce()));
     }
@@ -127,6 +123,18 @@ public class TokenManager {
         } catch (IllegalBlockSizeException e) {
             log.error("Block size invalid");
             throw new EncryptionFailedException(e);
+        } catch (NoSuchPaddingException e) {
+            log.error("Padding not available "+algorithm);
+            throw new EncryptionFailedException(e);
+        } catch (InvalidKeyException e) {
+            log.error("Bad encryption key");
+            throw new EncryptionFailedException(e);
+        } catch (NoSuchAlgorithmException e) {
+            log.error("Bad encryption algorithm "+algorithm);
+            throw new EncryptionFailedException(e);
+        } catch (InvalidAlgorithmParameterException e) {
+            log.error("Invalid encryption parameters");
+            throw new EncryptionFailedException(e);
         }
     }
 
@@ -145,29 +153,63 @@ public class TokenManager {
         } catch (IllegalBlockSizeException ex) {
             log.error("The encrypted token has the wrong block size.");
             throw new InvalidTokenException(ex);
+        } catch (NoSuchPaddingException e) {
+            log.error("Padding not available "+algorithm);
+            throw new InvalidTokenException(e);
+        } catch (InvalidKeyException e) {
+            log.error("Invalid decryption key");
+            throw new InvalidTokenException(e);
+        } catch (NoSuchAlgorithmException e) {
+            log.error("Encryption algorithm not available "+algorithm);
+            throw new InvalidTokenException(e);
+        } catch (InvalidAlgorithmParameterException e) {
+            log.error("Invalid encryption parameters");
+            throw new InvalidTokenException(e);
         }
     }
 
     private long createNonce() {
-        return rd.nextLong();
+        if (rd.get()==null) {
+            rd.set(new SecureRandom());
+        }
+        return rd.get().nextLong();
+    }
+
+    protected byte[] encrypt(TokenData info) throws IOException, BadPaddingException, IllegalBlockSizeException, NoSuchPaddingException, NoSuchAlgorithmException, InvalidAlgorithmParameterException, InvalidKeyException {
+        return doEncrypt(convertToByteArray(info), info.getNonce());
     }
 
-    protected byte[] encrypt(TokenData info) throws IOException, BadPaddingException, IllegalBlockSizeException {
-        return doEncrypt(convertToByteArray(info));
+    private byte[] getIv(long nonce) {
+        byte[] iv = new byte[ivSize];
+        SecureRandom sr = getRandomGenerator();
+        sr.setSeed(nonce);
+        sr.nextBytes(iv);
+        return iv;
     }
 
-    protected byte[] doEncrypt(byte[] data) throws BadPaddingException, IllegalBlockSizeException {
+    protected byte[] doEncrypt(byte[] data, long nonce) throws BadPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
+        Cipher cipher = getEnCipher();
         byte[] encData;
-        if (!paddingUsed && (data.length % enCipher.getBlockSize())!=0) {
-            int blocks = data.length / enCipher.getBlockSize();
-            encData = Arrays.copyOf(data, enCipher.getBlockSize()*(blocks+1));
+        byte[] iv;
+        if (ivSize>0) {
+            iv = getIv(nonce);
+            cipher.init(Cipher.ENCRYPT_MODE,this.secretKey,new IvParameterSpec(iv));
+        } else {
+            iv = new byte[0];
+            cipher.init(Cipher.ENCRYPT_MODE,this.secretKey);
+        }
+        if (!paddingUsed && (data.length % cipher.getBlockSize())!=0) {
+            int blocks = data.length / cipher.getBlockSize();
+            encData = Arrays.copyOf(data, cipher.getBlockSize()*(blocks+1));
         } else {
             encData = data;
         }
-        return enCipher.doFinal(encData);
+        byte[] encrypted = cipher.doFinal(encData);
+        // prepending the iv to the token
+        return ArrayUtils.addAll(iv,encrypted);
     }
 
-    protected TokenData decrypt(byte[] token) throws BadPaddingException, IllegalBlockSizeException, IOException, ClassNotFoundException {
+    protected TokenData decrypt(byte[] token) throws BadPaddingException, IllegalBlockSizeException, IOException, ClassNotFoundException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException {
         Object result = convertFromByteArray(doDecrypt(token));
         if (!(result instanceof TokenData)) {
             throw new InvalidClassException("No TokenData found in decrypted token");
@@ -175,8 +217,31 @@ public class TokenManager {
         return (TokenData)result;
     }
 
-    protected byte[] doDecrypt(byte[] encryptedData) throws BadPaddingException, IllegalBlockSizeException {
-        return deCipher.doFinal(encryptedData);
+    protected byte[] doDecrypt(byte[] encryptedData) throws BadPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidAlgorithmParameterException, InvalidKeyException {
+        Cipher cipher = getDeCipher();
+        if (ivSize>0) {
+            byte[] iv = Arrays.copyOfRange(encryptedData,0,ivSize);
+            cipher.init(Cipher.DECRYPT_MODE,this.secretKey,new IvParameterSpec(iv));
+            return cipher.doFinal(encryptedData,ivSize,encryptedData.length-ivSize);
+        } else {
+            cipher.init(Cipher.DECRYPT_MODE,this.secretKey);
+            return cipher.doFinal(encryptedData);
+        }
+    }
+
+    private SecureRandom getRandomGenerator() {
+        if (rd.get()==null) {
+            rd.set(new SecureRandom());
+        }
+        return rd.get();
+    }
+
+    private Cipher getEnCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
+        return Cipher.getInstance(algorithm);
+    }
+
+    private Cipher getDeCipher() throws NoSuchPaddingException, NoSuchAlgorithmException {
+        return Cipher.getInstance(algorithm);
     }
 
     private String encode(byte[] token) {

http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/a6ceb384/redback-authentication/redback-authentication-api/src/test/java/org/apache/archiva/redback/authentication/TokenManagerTest.java
----------------------------------------------------------------------
diff --git a/redback-authentication/redback-authentication-api/src/test/java/org/apache/archiva/redback/authentication/TokenManagerTest.java b/redback-authentication/redback-authentication-api/src/test/java/org/apache/archiva/redback/authentication/TokenManagerTest.java
index 5200956..1a07033 100644
--- a/redback-authentication/redback-authentication-api/src/test/java/org/apache/archiva/redback/authentication/TokenManagerTest.java
+++ b/redback-authentication/redback-authentication-api/src/test/java/org/apache/archiva/redback/authentication/TokenManagerTest.java
@@ -27,6 +27,7 @@ import javax.crypto.NoSuchPaddingException;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
 import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
 import java.util.Arrays;
 
 import static org.junit.Assert.*;
@@ -48,7 +49,7 @@ public class TokenManagerTest {
     public void encryptToken() throws Exception {
         TokenManager tokenManager = new TokenManager();
         tokenManager.initialize();
-        assertEquals(tokenManager.getAlgorithm(),"AES/ECB/PKCS5Padding");
+        assertEquals(tokenManager.getAlgorithm(),"AES/CBC/PKCS5Padding");
         assertEquals(tokenManager.getKeySize(), -1);
         String token = tokenManager.encryptToken("testuser01",1000);
         assertNotNull(token);
@@ -131,7 +132,7 @@ public class TokenManagerTest {
         tokenManager.setKeySize(56);
         tokenManager.initialize();
         byte[] data = { 1, 5, 12, 18, 124, 44, 88, -28, -44};
-        byte[] token = tokenManager.doEncrypt(data);
+        byte[] token = tokenManager.doEncrypt(data, new SecureRandom().nextLong());
         assertNotNull(token);
         byte[] tokenData = tokenManager.doDecrypt(token);
         assertNotNull(tokenData);
@@ -141,7 +142,7 @@ public class TokenManagerTest {
         tokenManager.setAlgorithm("AES/CBC/NoPadding");
         tokenManager.setKeySize(128);
         tokenManager.initialize();
-        token = tokenManager.doEncrypt(data);
+        token = tokenManager.doEncrypt(data, new SecureRandom().nextLong());
         assertNotNull(token);
         // Without padding the decrypted value is a multiple of the block size.
         tokenData = Arrays.copyOf(tokenManager.doDecrypt(token), data.length);


[2/8] archiva-redback-core git commit: Adding port check for Referer header

Posted by ma...@apache.org.
Adding port check for Referer header


Project: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/repo
Commit: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/commit/e9bc4818
Tree: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/tree/e9bc4818
Diff: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/diff/e9bc4818

Branch: refs/heads/master
Commit: e9bc4818844bf684e4fd16e3a5adc99fe9eb9f96
Parents: 95f1b3e
Author: Martin Stockhammer <ma...@apache.org>
Authored: Mon Jan 30 22:42:33 2017 +0100
Committer: Martin Stockhammer <ma...@apache.org>
Committed: Mon Jan 30 22:42:33 2017 +0100

----------------------------------------------------------------------
 .../interceptors/RequestValidationInterceptor.java        |  7 ++++++-
 .../redback/rest/services/AbstractRestServicesTest.java   | 10 +++++-----
 2 files changed, 11 insertions(+), 6 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/e9bc4818/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
----------------------------------------------------------------------
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
index 4300baf..182d23a 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/interceptors/RequestValidationInterceptor.java
@@ -161,6 +161,7 @@ public class RequestValidationInterceptor extends AbstractInterceptor implements
     private boolean checkSourceRequestHeader(final URL targetUrl, final HttpServletRequest request) {
         boolean headerFound=false;
         String origin = request.getHeader(ORIGIN);
+        int targetPort = getPort(targetUrl);
         if (origin!=null) {
             try {
                 URL originUrl = new URL(origin);
@@ -175,7 +176,6 @@ public class RequestValidationInterceptor extends AbstractInterceptor implements
                     return false;
                 }
                 int originPort = getPort(originUrl);
-                int targetPort = getPort(targetUrl);
                 if (targetPort != originPort) {
                     log.warn("Origin Header Port does not match originUrl={}, targetUrl={}",originUrl,targetUrl);
                     return false;
@@ -195,6 +195,11 @@ public class RequestValidationInterceptor extends AbstractInterceptor implements
                     log.warn("Referer Header Host does not match refererUrl={}, targetUrl={}",refererUrl,targetUrl);
                     return false;
                 }
+                int refererPort = getPort(refererUrl);
+                if (targetPort != refererPort) {
+                    log.warn("Referer Header Port does not match refererUrl={}, targetUrl={}",refererUrl,targetUrl);
+                    return false;
+                }
             } catch (MalformedURLException ex) {
                 log.warn("Bad URL in Referer HTTP-Header: {}, Message: {}", referer, ex.getMessage());
                 return false;

http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/e9bc4818/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/AbstractRestServicesTest.java
----------------------------------------------------------------------
diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/AbstractRestServicesTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/AbstractRestServicesTest.java
index 0cab072..4b565a2 100644
--- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/AbstractRestServicesTest.java
+++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/AbstractRestServicesTest.java
@@ -158,6 +158,7 @@ public abstract class AbstractRestServicesTest
 
     protected UserService getUserService()
     {
+
         return getUserService( null );
     }
 
@@ -175,7 +176,7 @@ public abstract class AbstractRestServicesTest
         {
             WebClient.client( service ).header( "Authorization", authzHeader );
         }
-        WebClient.client(service).header("Referer","http://localhost");
+        WebClient.client(service).header("Referer","http://localhost:"+port);
         WebClient.client( service ).accept( MediaType.APPLICATION_JSON_TYPE );
         WebClient.client( service ).type( MediaType.APPLICATION_JSON_TYPE );
 
@@ -197,8 +198,7 @@ public abstract class AbstractRestServicesTest
         {
             WebClient.client( service ).header( "Authorization", authzHeader );
         }
-        WebClient.client( service ).header("Referer","http://localhost/");
-
+        WebClient.client(service).header("Referer","http://localhost:"+port);
 
         WebClient.client( service ).accept( MediaType.APPLICATION_JSON_TYPE );
         WebClient.client( service ).type( MediaType.APPLICATION_JSON_TYPE );
@@ -219,7 +219,7 @@ public abstract class AbstractRestServicesTest
         {
             WebClient.client( service ).header( "Authorization", authzHeader );
         }
-        WebClient.client( service ).header("Referer","http://localhost/");
+        WebClient.client(service).header("Referer","http://localhost:"+port);
 
         WebClient.client( service ).accept( MediaType.APPLICATION_JSON_TYPE );
         WebClient.client( service ).type( MediaType.APPLICATION_JSON_TYPE );
@@ -242,7 +242,7 @@ public abstract class AbstractRestServicesTest
         {
             WebClient.client( service ).header( "Authorization", authzHeader );
         }
-        WebClient.client( service ).header("Referer","http://localhost/");
+        WebClient.client(service).header("Referer","http://localhost:"+port);
 
         WebClient.client( service ).accept( MediaType.APPLICATION_JSON_TYPE );
         WebClient.client( service ).type( MediaType.APPLICATION_JSON_TYPE );


[8/8] archiva-redback-core git commit: Fixing bad import

Posted by ma...@apache.org.
Fixing bad import


Project: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/repo
Commit: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/commit/d9146ce8
Tree: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/tree/d9146ce8
Diff: http://git-wip-us.apache.org/repos/asf/archiva-redback-core/diff/d9146ce8

Branch: refs/heads/master
Commit: d9146ce8abc7ac5c4abf252096383b05b1c0ec16
Parents: 6a6b7bb
Author: Martin Stockhammer <ma...@apache.org>
Authored: Thu Mar 9 20:07:03 2017 +0100
Committer: Martin Stockhammer <ma...@apache.org>
Committed: Thu Mar 9 20:07:03 2017 +0100

----------------------------------------------------------------------
 .../org/apache/archiva/redback/system/DefaultSecuritySystem.java    | 1 -
 1 file changed, 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/archiva-redback-core/blob/d9146ce8/redback-system/src/main/java/org/apache/archiva/redback/system/DefaultSecuritySystem.java
----------------------------------------------------------------------
diff --git a/redback-system/src/main/java/org/apache/archiva/redback/system/DefaultSecuritySystem.java b/redback-system/src/main/java/org/apache/archiva/redback/system/DefaultSecuritySystem.java
index 811903e..3761394 100644
--- a/redback-system/src/main/java/org/apache/archiva/redback/system/DefaultSecuritySystem.java
+++ b/redback-system/src/main/java/org/apache/archiva/redback/system/DefaultSecuritySystem.java
@@ -19,7 +19,6 @@ package org.apache.archiva.redback.system;
  * under the License.
  */
 
-import com.sun.javafx.fxml.expression.Expression;
 import org.apache.archiva.redback.authentication.AuthenticationDataSource;
 import org.apache.archiva.redback.authentication.AuthenticationException;
 import org.apache.archiva.redback.authentication.AuthenticationManager;