You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cxf.apache.org by co...@apache.org on 2017/04/18 16:23:11 UTC

cxf-fediz git commit: Adding CSRF support to the OIDC client reg webapp

Repository: cxf-fediz
Updated Branches:
  refs/heads/master 02df115e9 -> c68e48208


Adding CSRF support to the OIDC client reg webapp


Project: http://git-wip-us.apache.org/repos/asf/cxf-fediz/repo
Commit: http://git-wip-us.apache.org/repos/asf/cxf-fediz/commit/c68e4820
Tree: http://git-wip-us.apache.org/repos/asf/cxf-fediz/tree/c68e4820
Diff: http://git-wip-us.apache.org/repos/asf/cxf-fediz/diff/c68e4820

Branch: refs/heads/master
Commit: c68e4820816c19241568f4a8fe8600bffb0243cd
Parents: 02df115
Author: Colm O hEigeartaigh <co...@apache.org>
Authored: Tue Apr 18 17:22:27 2017 +0100
Committer: Colm O hEigeartaigh <co...@apache.org>
Committed: Tue Apr 18 17:22:27 2017 +0100

----------------------------------------------------------------------
 .../cxf/fediz/service/oidc/CSRFUtils.java       | 52 ++++++++++++++++++
 .../oidc/clients/ClientRegistrationService.java | 55 +++++++++++++++++---
 .../src/main/webapp/WEB-INF/views/client.jsp    | 12 ++++-
 .../webapp/WEB-INF/views/clientCodeGrants.jsp   |  7 ++-
 .../main/webapp/WEB-INF/views/clientTokens.jsp  |  7 ++-
 .../webapp/WEB-INF/views/registerClient.jsp     | 10 +++-
 .../cxf/fediz/systests/oidc/OIDCTest.java       | 32 ++++++++++++
 7 files changed, 163 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/c68e4820/services/oidc/src/main/java/org/apache/cxf/fediz/service/oidc/CSRFUtils.java
----------------------------------------------------------------------
diff --git a/services/oidc/src/main/java/org/apache/cxf/fediz/service/oidc/CSRFUtils.java b/services/oidc/src/main/java/org/apache/cxf/fediz/service/oidc/CSRFUtils.java
new file mode 100644
index 0000000..79e078a
--- /dev/null
+++ b/services/oidc/src/main/java/org/apache/cxf/fediz/service/oidc/CSRFUtils.java
@@ -0,0 +1,52 @@
+/**
+ * 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.
+ */
+package org.apache.cxf.fediz.service.oidc;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.rt.security.crypto.CryptoUtils;
+
+public final class CSRFUtils {
+
+    public static final String CSRF_TOKEN = "CSRF_TOKEN";
+
+    private CSRFUtils() {
+        // complete
+    }
+
+    public static String getCSRFToken(HttpServletRequest request, boolean create) {
+        if (request != null && request.getSession() != null) {
+            // Return an existing token first
+            String savedToken = (String)request.getSession().getAttribute(CSRF_TOKEN);
+            if (savedToken != null) {
+                return savedToken;
+            }
+
+            // If no existing token then create a new one, save it, and return it
+            if (create) {
+                String token = StringUtils.toHexString(CryptoUtils.generateSecureRandomBytes(16));
+                request.getSession().setAttribute(CSRF_TOKEN, token);
+                return token;
+            }
+        }
+
+        return null;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/c68e4820/services/oidc/src/main/java/org/apache/cxf/fediz/service/oidc/clients/ClientRegistrationService.java
----------------------------------------------------------------------
diff --git a/services/oidc/src/main/java/org/apache/cxf/fediz/service/oidc/clients/ClientRegistrationService.java b/services/oidc/src/main/java/org/apache/cxf/fediz/service/oidc/clients/ClientRegistrationService.java
index b89f862..d00b9cd 100644
--- a/services/oidc/src/main/java/org/apache/cxf/fediz/service/oidc/clients/ClientRegistrationService.java
+++ b/services/oidc/src/main/java/org/apache/cxf/fediz/service/oidc/clients/ClientRegistrationService.java
@@ -38,6 +38,7 @@ import java.util.SortedSet;
 import java.util.TreeSet;
 import java.util.logging.Logger;
 
+import javax.servlet.http.HttpServletRequest;
 import javax.ws.rs.Consumes;
 import javax.ws.rs.FormParam;
 import javax.ws.rs.GET;
@@ -56,6 +57,9 @@ import org.apache.commons.validator.routines.UrlValidator;
 import org.apache.cxf.common.logging.LogUtils;
 import org.apache.cxf.common.util.Base64UrlUtility;
 import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.fediz.service.oidc.CSRFUtils;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.phase.PhaseInterceptorChain;
 import org.apache.cxf.rs.security.oauth2.common.Client;
 import org.apache.cxf.rs.security.oauth2.common.ServerAccessToken;
 import org.apache.cxf.rs.security.oauth2.common.UserSubject;
@@ -67,10 +71,11 @@ import org.apache.cxf.rs.security.oauth2.tokens.refresh.RefreshToken;
 import org.apache.cxf.rs.security.oauth2.utils.OAuthConstants;
 import org.apache.cxf.rs.security.oidc.idp.OidcUserSubject;
 import org.apache.cxf.rt.security.crypto.CryptoUtils;
+import org.apache.cxf.transport.http.AbstractHTTPDestination;
 
 @Path("/")
 public class ClientRegistrationService {
-    
+
     private static final Logger LOG = LogUtils.getL7dLogger(ClientRegistrationService.class);
 
     private Map<String, Collection<Client>> registrations = new HashMap<>();
@@ -119,7 +124,11 @@ public class ClientRegistrationService {
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     @Produces(MediaType.TEXT_HTML)
     @Path("/{id}/remove")
-    public RegisteredClients removeClient(@PathParam("id") String id) {
+    public RegisteredClients removeClient(@PathParam("id") String id,
+                                          @FormParam("client_csrfToken") String csrfToken) {
+        // CSRF
+        checkCSRFToken(csrfToken);
+
         Collection<Client> clients = getClientRegistrations();
         for (Iterator<Client> it = clients.iterator(); it.hasNext();) {
             Client c = it.next();
@@ -139,7 +148,11 @@ public class ClientRegistrationService {
     @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
     @Produces(MediaType.TEXT_HTML)
     @Path("/{id}/reset")
-    public Client resetClient(@PathParam("id") String id) {
+    public Client resetClient(@PathParam("id") String id,
+                              @FormParam("client_csrfToken") String csrfToken) {
+        // CSRF
+        checkCSRFToken(csrfToken);
+
         Client c = getRegisteredClient(id);
         if (c.isConfidential()) {
             c.setClientSecret(generateClientSecret());
@@ -172,7 +185,11 @@ public class ClientRegistrationService {
     @Produces(MediaType.TEXT_HTML)
     @Path("/{id}/at/{tokenId}/revoke")
     public ClientTokens revokeClientAccessToken(@PathParam("id") String clientId,
-                                                      @PathParam("tokenId") String tokenId) {
+                                                      @PathParam("tokenId") String tokenId,
+                                                      @FormParam("client_csrfToken") String csrfToken) {
+        // CSRF
+        checkCSRFToken(csrfToken);
+
         return doRevokeClientToken(clientId, tokenId, OAuthConstants.ACCESS_TOKEN);
     }
 
@@ -181,7 +198,11 @@ public class ClientRegistrationService {
     @Produces(MediaType.TEXT_HTML)
     @Path("/{id}/rt/{tokenId}/revoke")
     public ClientTokens revokeClientRefreshToken(@PathParam("id") String clientId,
-                                                      @PathParam("tokenId") String tokenId) {
+                                                      @PathParam("tokenId") String tokenId,
+                                                      @FormParam("client_csrfToken") String csrfToken) {
+        // CSRF
+        checkCSRFToken(csrfToken);
+
         return doRevokeClientToken(clientId, tokenId, OAuthConstants.REFRESH_TOKEN);
     }
 
@@ -213,7 +234,11 @@ public class ClientRegistrationService {
     @Produces(MediaType.TEXT_HTML)
     @Path("/{id}/codes/{code}/revoke")
     public ClientCodeGrants revokeClientCodeGrant(@PathParam("id") String id,
-                                                  @PathParam("code") String code) {
+                                                  @PathParam("code") String code,
+                                                  @FormParam("client_csrfToken") String csrfToken) {
+        // CSRF
+        checkCSRFToken(csrfToken);
+
         if (dataProvider instanceof AuthorizationCodeDataProvider) {
             ((AuthorizationCodeDataProvider)dataProvider).removeCodeGrant(code);
             return getClientCodeGrants(id);
@@ -230,9 +255,13 @@ public class ClientRegistrationService {
                                  @FormParam("client_audience") String audience,
                                  @FormParam("client_redirectURI") String redirectURI,
                                  @FormParam("client_logoutURI") String logoutURI,
-                                 @FormParam("client_homeRealm") String homeRealm
+                                 @FormParam("client_homeRealm") String homeRealm,
+                                 @FormParam("client_csrfToken") String csrfToken
     ) {
         try {
+            // CSRF
+            checkCSRFToken(csrfToken);
+
             // Client Name
             if (StringUtils.isEmpty(appName)) {
                 throwInvalidRegistrationException("The client name must not be empty");
@@ -322,13 +351,23 @@ public class ClientRegistrationService {
         }
     }
 
+    private void checkCSRFToken(String csrfToken) {
+        // CSRF
+        Message message = PhaseInterceptorChain.getCurrentMessage();
+        HttpServletRequest httpRequest = (HttpServletRequest) message.get(AbstractHTTPDestination.HTTP_REQUEST);
+        String savedToken = CSRFUtils.getCSRFToken(httpRequest, false);
+        if (StringUtils.isEmpty(csrfToken) || StringUtils.isEmpty(savedToken)
+            || !savedToken.equals(csrfToken)) {
+            throwInvalidRegistrationException("Invalid CSRF Token");
+        }
+    }
 
     private void throwInvalidRegistrationException(String error) {
         throw new InvalidRegistrationException(error);
     }
 
     private boolean isValidURI(String uri, boolean requireHttps) {
-        
+
         UrlValidator urlValidator = null;
 
         if (requireHttps) {

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/c68e4820/services/oidc/src/main/webapp/WEB-INF/views/client.jsp
----------------------------------------------------------------------
diff --git a/services/oidc/src/main/webapp/WEB-INF/views/client.jsp b/services/oidc/src/main/webapp/WEB-INF/views/client.jsp
index 597bd8a..0c43fff 100644
--- a/services/oidc/src/main/webapp/WEB-INF/views/client.jsp
+++ b/services/oidc/src/main/webapp/WEB-INF/views/client.jsp
@@ -4,6 +4,7 @@
 <%@ page import="java.util.Locale"%>
 <%@ page import="java.util.TimeZone"%>
 <%@ page import="javax.servlet.http.HttpServletRequest" %>
+<%@ page import="org.apache.cxf.fediz.service.oidc.CSRFUtils" %>
 <%@ page import="org.owasp.esapi.ESAPI" %>
 
 <%
@@ -16,7 +17,10 @@
     String basePath = request.getContextPath() + request.getServletPath();
     if (!basePath.endsWith("/")) {
         basePath += "/";
-    } 
+    }
+    
+    // Get or generate the CSRF token
+    String token = CSRFUtils.getCSRFToken(request, true);
 %>
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
@@ -178,6 +182,9 @@
 %>
 <td class="td_no_border">
 <form name="resetSecretForm" action="<%=basePath%>console/clients/<%= client.getClientId() + "/reset"%>" method="POST">
+    <div class="form-line">
+        <input type="hidden" value="<%=token%>" name="client_csrfToken" />
+    </div>
      <div data-type="control_button" class="form-line">
 	<button name="submit_reset_button" class="form-submit-button" type="submit">Reset Client Secret</button>
 </form>
@@ -188,6 +195,9 @@
 %>
 <td class="td_no_border">
 <form name="deleteForm" action="<%=basePath%>console/clients/<%= client.getClientId() + "/remove"%>" method="POST">
+    <div class="form-line">
+        <input type="hidden" value="<%=token%>" name="client_csrfToken" />
+    </div>
         <div data-type="control_button" class="form-line">
 	<button name="submit_delete_button" class="form-submit-button" type="submit">Delete Client</button>
         </div>

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/c68e4820/services/oidc/src/main/webapp/WEB-INF/views/clientCodeGrants.jsp
----------------------------------------------------------------------
diff --git a/services/oidc/src/main/webapp/WEB-INF/views/clientCodeGrants.jsp b/services/oidc/src/main/webapp/WEB-INF/views/clientCodeGrants.jsp
index 44a4646..8254c50 100644
--- a/services/oidc/src/main/webapp/WEB-INF/views/clientCodeGrants.jsp
+++ b/services/oidc/src/main/webapp/WEB-INF/views/clientCodeGrants.jsp
@@ -6,6 +6,7 @@
 <%@ page import="java.util.Locale"%>
 <%@ page import="java.util.TimeZone"%>
 <%@ page import="javax.servlet.http.HttpServletRequest" %>
+<%@ page import="org.apache.cxf.fediz.service.oidc.CSRFUtils" %>
 <%@ page import="org.apache.cxf.fediz.service.oidc.clients.ClientCodeGrants" %>
 <%@ page import="org.owasp.esapi.ESAPI" %>
 
@@ -15,7 +16,10 @@
     String basePath = request.getContextPath() + request.getServletPath();
     if (!basePath.endsWith("/")) {
         basePath += "/";
-    } 
+    }
+    
+    // Get or generate the CSRF token
+    String csrfToken = CSRFUtils.getCSRFToken(request, true);
 %>
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
@@ -76,6 +80,7 @@
 		   %>
            <td>
                <form action="<%=basePath%>console/clients/<%= client.getClientId() + "/codes/" + token.getCode() + "/revoke"%>" method="POST">
+                 <input type="hidden" value="<%=csrfToken%>" name="client_csrfToken" />
 		         <input type="submit" value="Delete"/>
                </form>
            </td>

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/c68e4820/services/oidc/src/main/webapp/WEB-INF/views/clientTokens.jsp
----------------------------------------------------------------------
diff --git a/services/oidc/src/main/webapp/WEB-INF/views/clientTokens.jsp b/services/oidc/src/main/webapp/WEB-INF/views/clientTokens.jsp
index b341739..ed96511 100644
--- a/services/oidc/src/main/webapp/WEB-INF/views/clientTokens.jsp
+++ b/services/oidc/src/main/webapp/WEB-INF/views/clientTokens.jsp
@@ -7,6 +7,7 @@
 <%@ page import="java.util.Locale"%>
 <%@ page import="java.util.TimeZone"%>
 <%@ page import="javax.servlet.http.HttpServletRequest" %>
+<%@ page import="org.apache.cxf.fediz.service.oidc.CSRFUtils" %>
 <%@ page import="org.apache.cxf.fediz.service.oidc.clients.ClientTokens" %>
 <%@ page import="org.owasp.esapi.ESAPI" %>
 
@@ -19,7 +20,9 @@
     } 
     SimpleDateFormat dateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss", Locale.US);
     dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
-
+    
+    // Get or generate the CSRF token
+    String csrfToken = CSRFUtils.getCSRFToken(request, true);
 %>
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
@@ -111,6 +114,7 @@
 	       %>
            <td>
                <form action="<%=basePath%>console/clients/<%= client.getClientId() + "/at/" + token.getTokenKey() + "/revoke"%>" method="POST">
+                   <input type="hidden" value="<%=csrfToken%>" name="client_csrfToken" />
 		           <input type="submit" value="Delete"/>  
                </form>
            </td>
@@ -170,6 +174,7 @@
 	       
            <td>
                <form action="<%=basePath%>console/clients/<%= client.getClientId() + "/rt/" + token.getTokenKey() + "/revoke"%>" method="POST">
+                 <input type="hidden" value="<%=csrfToken%>" name="client_csrfToken" />
 		         <input type="submit" value="Delete"/>
                </form>
            </td>

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/c68e4820/services/oidc/src/main/webapp/WEB-INF/views/registerClient.jsp
----------------------------------------------------------------------
diff --git a/services/oidc/src/main/webapp/WEB-INF/views/registerClient.jsp b/services/oidc/src/main/webapp/WEB-INF/views/registerClient.jsp
index 47796c3..ffe0541 100644
--- a/services/oidc/src/main/webapp/WEB-INF/views/registerClient.jsp
+++ b/services/oidc/src/main/webapp/WEB-INF/views/registerClient.jsp
@@ -1,11 +1,16 @@
 <%@ page
-	import="javax.servlet.http.HttpServletRequest,java.util.Map,java.util.Iterator,org.apache.cxf.fediz.service.oidc.clients.RegisterClient"%>
+	import="javax.servlet.http.HttpServletRequest,java.util.Map,java.util.Iterator,org.apache.cxf.fediz.service.oidc.clients.RegisterClient,
+	org.apache.cxf.fediz.service.oidc.CSRFUtils"
+%>
 <%
     RegisterClient reg = (RegisterClient)request.getAttribute("data");
     String basePath = request.getContextPath() + request.getServletPath();
     if (!basePath.endsWith("/")) {
         basePath += "/";
     }
+
+    // Get or generate the CSRF token
+    String csrfToken = CSRFUtils.getCSRFToken(request, true);
 %>
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
@@ -113,6 +118,9 @@ input, select, button {
 					%>
 				</select>
 			</div>
+			<div class="form-line">
+				<input type="hidden" value="<%=csrfToken%>" name="client_csrfToken" />
+			</div>
 			<div data-type="control_button" class="form-line">
 				<button name="submit_button" class="form-submit-button" type="submit">Register API Client</button>
 			</div>

http://git-wip-us.apache.org/repos/asf/cxf-fediz/blob/c68e4820/systests/oidc/src/test/java/org/apache/cxf/fediz/systests/oidc/OIDCTest.java
----------------------------------------------------------------------
diff --git a/systests/oidc/src/test/java/org/apache/cxf/fediz/systests/oidc/OIDCTest.java b/systests/oidc/src/test/java/org/apache/cxf/fediz/systests/oidc/OIDCTest.java
index df04250..216e1ec 100644
--- a/systests/oidc/src/test/java/org/apache/cxf/fediz/systests/oidc/OIDCTest.java
+++ b/systests/oidc/src/test/java/org/apache/cxf/fediz/systests/oidc/OIDCTest.java
@@ -794,6 +794,38 @@ public class OIDCTest {
         webClient.close();
     }
 
+    // Test that the form has the correct CSRF token in it when creating a client
+    @org.junit.Test
+    public void testCSRFClientRegistration() throws Exception {
+        String url = "https://localhost:" + getRpHttpsPort() + "/fediz-oidc/console/clients";
+        String user = "alice";
+        String password = "ecila";
+
+        // Login to the client page successfully
+        WebClient webClient = setupWebClient(user, password, getIdpHttpsPort());
+        HtmlPage loginPage = login(url, webClient);
+        final String bodyTextContent = loginPage.getBody().getTextContent();
+        Assert.assertTrue(bodyTextContent.contains("Registered Clients"));
+
+        // Register a new client
+
+        WebRequest request = new WebRequest(new URL(url), HttpMethod.POST);
+        request.setRequestParameters(new ArrayList<NameValuePair>());
+
+        request.getRequestParameters().add(new NameValuePair("client_name", "bad_client"));
+        request.getRequestParameters().add(new NameValuePair("client_type", "confidential"));
+        request.getRequestParameters().add(new NameValuePair("client_redirectURI", "https://127.0.0.1"));
+        request.getRequestParameters().add(new NameValuePair("client_audience", ""));
+        request.getRequestParameters().add(new NameValuePair("client_logoutURI", ""));
+        request.getRequestParameters().add(new NameValuePair("client_homeRealm", ""));
+        request.getRequestParameters().add(new NameValuePair("client_csrfToken", "12345"));
+
+        HtmlPage registeredClientPage = webClient.getPage(request);
+        Assert.assertTrue(registeredClientPage.asXml().contains("Invalid CSRF Token"));
+
+        webClient.close();
+    }
+
     private static WebClient setupWebClient(String user, String password, String idpPort) {
         final WebClient webClient = new WebClient();
         webClient.getOptions().setUseInsecureSSL(true);