[ARVADOS] created: 4faccebcdd7872eca76b183951a2850518d35916

git at public.curoverse.com git at public.curoverse.com
Wed Nov 26 00:47:27 EST 2014


        at  4faccebcdd7872eca76b183951a2850518d35916 (commit)


commit 4faccebcdd7872eca76b183951a2850518d35916
Author: Tom Clegg <tom at curoverse.com>
Date:   Sun Nov 23 02:09:11 2014 -0500

    3781: Add allowed headers. Respond to OPTIONS at any path.

diff --git a/services/keepproxy/keepproxy.go b/services/keepproxy/keepproxy.go
index 620434f..e547efd 100644
--- a/services/keepproxy/keepproxy.go
+++ b/services/keepproxy/keepproxy.go
@@ -248,7 +248,7 @@ func MakeRESTRouter(
 		rest.Handle(`/{hash:[0-9a-f]{32}}+{hints}`, PutBlockHandler{kc, t}).Methods("PUT")
 		rest.Handle(`/{hash:[0-9a-f]{32}}`, PutBlockHandler{kc, t}).Methods("PUT")
 		rest.Handle(`/`, PutBlockHandler{kc, t}).Methods("POST")
-		rest.Handle(`/{hash:[0-9a-f]{32}}{ignore}`, OptionsHandler{}).Methods("OPTIONS")
+		rest.Handle(`/`, OptionsHandler{}).Methods("OPTIONS")
 	}
 
 	rest.NotFoundHandler = InvalidPathHandler{}
@@ -256,6 +256,13 @@ func MakeRESTRouter(
 	return rest
 }
 
+func SetCorsHeaders(resp http.ResponseWriter) {
+	resp.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, OPTIONS")
+	resp.Header().Set("Access-Control-Allow-Origin", "*")
+	resp.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Length, Content-Type, X-Keep-Desired-Replicas")
+	resp.Header().Set("Access-Control-Max-Age", "86486400")
+}
+
 func (this InvalidPathHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
 	log.Printf("%s: %s %s unroutable", GetRemoteAddress(req), req.Method, req.URL.Path)
 	http.Error(resp, "Bad request", http.StatusBadRequest)
@@ -263,15 +270,11 @@ func (this InvalidPathHandler) ServeHTTP(resp http.ResponseWriter, req *http.Req
 
 func (this OptionsHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
 	log.Printf("%s: %s %s", GetRemoteAddress(req), req.Method, req.URL.Path)
-	resp.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, OPTIONS")
-	resp.Header().Set("Access-Control-Allow-Origin", "*")
-	resp.Header().Set("Access-Control-Allow-Headers", "Authorization, X-Keep-Desired-Replicas")
-	resp.Header().Set("Access-Control-Max-Age", "86486400")
+	SetCorsHeaders(resp)
 }
 
 func (this GetBlockHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
-	resp.Header().Set("Access-Control-Allow-Origin", "*")
-	resp.Header().Set("Access-Control-Allow-Headers", "Authorization")
+	SetCorsHeaders(resp)
 
 	kc := *this.KeepClient
 
@@ -335,6 +338,7 @@ func (this GetBlockHandler) ServeHTTP(resp http.ResponseWriter, req *http.Reques
 }
 
 func (this PutBlockHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
+	SetCorsHeaders(resp)
 
 	kc := *this.KeepClient
 
diff --git a/services/keepproxy/keepproxy_test.go b/services/keepproxy/keepproxy_test.go
index 667a0b2..0ac843f 100644
--- a/services/keepproxy/keepproxy_test.go
+++ b/services/keepproxy/keepproxy_test.go
@@ -349,7 +349,7 @@ func (s *ServerRequiredSuite) TestCorsHeaders(c *C) {
 			fmt.Sprintf("http://localhost:29954/%x+3",
 				md5.Sum([]byte("foo"))))
 		c.Check(err, Equals, nil)
-		c.Check(resp.Header.Get("Access-Control-Allow-Headers"), Equals, "Authorization")
+		c.Check(resp.Header.Get("Access-Control-Allow-Headers"), Equals, "Authorization, Content-Length, Content-Type, X-Keep-Desired-Replicas")
 		c.Check(resp.Header.Get("Access-Control-Allow-Origin"), Equals, "*")
 	}
 }

commit 500fb090fb7c295000c485ca7e2214ad448c40dc
Author: Tom Clegg <tom at curoverse.com>
Date:   Sat Nov 22 15:45:14 2014 -0500

    3781: Add POST method for writing without knowing MD5.

diff --git a/services/keepproxy/keepproxy.go b/services/keepproxy/keepproxy.go
index b74a982..620434f 100644
--- a/services/keepproxy/keepproxy.go
+++ b/services/keepproxy/keepproxy.go
@@ -7,6 +7,7 @@ import (
 	"fmt"
 	"github.com/gorilla/mux"
 	"io"
+	"io/ioutil"
 	"log"
 	"net"
 	"net/http"
@@ -246,6 +247,7 @@ func MakeRESTRouter(
 	if enable_put {
 		rest.Handle(`/{hash:[0-9a-f]{32}}+{hints}`, PutBlockHandler{kc, t}).Methods("PUT")
 		rest.Handle(`/{hash:[0-9a-f]{32}}`, PutBlockHandler{kc, t}).Methods("PUT")
+		rest.Handle(`/`, PutBlockHandler{kc, t}).Methods("POST")
 		rest.Handle(`/{hash:[0-9a-f]{32}}{ignore}`, OptionsHandler{}).Methods("OPTIONS")
 	}
 
@@ -261,7 +263,7 @@ func (this InvalidPathHandler) ServeHTTP(resp http.ResponseWriter, req *http.Req
 
 func (this OptionsHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
 	log.Printf("%s: %s %s", GetRemoteAddress(req), req.Method, req.URL.Path)
-	resp.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, PUT, OPTIONS")
+	resp.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, POST, PUT, OPTIONS")
 	resp.Header().Set("Access-Control-Allow-Origin", "*")
 	resp.Header().Set("Access-Control-Allow-Headers", "Authorization, X-Keep-Desired-Replicas")
 	resp.Header().Set("Access-Control-Max-Age", "86486400")
@@ -384,7 +386,20 @@ func (this PutBlockHandler) ServeHTTP(resp http.ResponseWriter, req *http.Reques
 	}
 
 	// Now try to put the block through
-	hash, replicas, err := kc.PutHR(hash, req.Body, contentLength)
+	var replicas int
+	var err error
+	if hash == "" {
+		if bytes, err := ioutil.ReadAll(req.Body); err != nil {
+			msg := fmt.Sprintf("Error reading request body: %s", err)
+			log.Printf(msg)
+			http.Error(resp, msg, http.StatusInternalServerError)
+			return
+		} else {
+			hash, replicas, err = kc.PutB(bytes)
+		}
+	} else {
+		hash, replicas, err = kc.PutHR(hash, req.Body, contentLength)
+	}
 
 	// Tell the client how many successful PUTs we accomplished
 	resp.Header().Set(keepclient.X_Keep_Replicas_Stored, fmt.Sprintf("%d", replicas))
diff --git a/services/keepproxy/keepproxy_test.go b/services/keepproxy/keepproxy_test.go
index ba9793d..667a0b2 100644
--- a/services/keepproxy/keepproxy_test.go
+++ b/services/keepproxy/keepproxy_test.go
@@ -14,6 +14,7 @@ import (
 	"net/url"
 	"os"
 	"os/exec"
+	"strings"
 	"testing"
 	"time"
 )
@@ -339,7 +340,7 @@ func (s *ServerRequiredSuite) TestCorsHeaders(c *C) {
 		c.Check(resp.StatusCode, Equals, 200)
 		body, err := ioutil.ReadAll(resp.Body)
 		c.Check(string(body), Equals, "")
-		c.Check(resp.Header.Get("Access-Control-Allow-Methods"), Equals, "GET, HEAD, PUT, OPTIONS")
+		c.Check(resp.Header.Get("Access-Control-Allow-Methods"), Equals, "GET, HEAD, POST, PUT, OPTIONS")
 		c.Check(resp.Header.Get("Access-Control-Allow-Origin"), Equals, "*")
 	}
 
@@ -352,3 +353,24 @@ func (s *ServerRequiredSuite) TestCorsHeaders(c *C) {
 		c.Check(resp.Header.Get("Access-Control-Allow-Origin"), Equals, "*")
 	}
 }
+
+func (s *ServerRequiredSuite) TestPostWithoutHash(c *C) {
+	runProxy(c, []string{"keepproxy"}, "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h", 29955)
+	waitForListener()
+	defer closeListener()
+
+	{
+		client := http.Client{}
+		req, err := http.NewRequest("POST",
+			"http://localhost:29955/",
+			strings.NewReader("qux"))
+		req.Header.Add("Authorization", "OAuth2 4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h")
+		req.Header.Add("Content-Type", "application/octet-stream")
+		resp, err := client.Do(req)
+		c.Check(err, Equals, nil)
+		body, err := ioutil.ReadAll(resp.Body)
+		c.Check(err, Equals, nil)
+		c.Check(string(body), Equals,
+			fmt.Sprintf("%x+%d", md5.Sum([]byte("qux")), 3))
+	}
+}

commit 3ae43002e9762e60844723edc5096c7282fb978d
Author: Tom Clegg <tom at curoverse.com>
Date:   Sat Nov 22 05:01:13 2014 -0500

    3781: Set CORS headers in keepproxy responses.

diff --git a/services/keepproxy/keepproxy.go b/services/keepproxy/keepproxy.go
index de4ccaf..b74a982 100644
--- a/services/keepproxy/keepproxy.go
+++ b/services/keepproxy/keepproxy.go
@@ -222,6 +222,8 @@ type PutBlockHandler struct {
 
 type InvalidPathHandler struct{}
 
+type OptionsHandler struct{}
+
 // MakeRESTRouter
 //     Returns a mux.Router that passes GET and PUT requests to the
 //     appropriate handlers.
@@ -244,6 +246,7 @@ func MakeRESTRouter(
 	if enable_put {
 		rest.Handle(`/{hash:[0-9a-f]{32}}+{hints}`, PutBlockHandler{kc, t}).Methods("PUT")
 		rest.Handle(`/{hash:[0-9a-f]{32}}`, PutBlockHandler{kc, t}).Methods("PUT")
+		rest.Handle(`/{hash:[0-9a-f]{32}}{ignore}`, OptionsHandler{}).Methods("OPTIONS")
 	}
 
 	rest.NotFoundHandler = InvalidPathHandler{}
@@ -256,7 +259,17 @@ func (this InvalidPathHandler) ServeHTTP(resp http.ResponseWriter, req *http.Req
 	http.Error(resp, "Bad request", http.StatusBadRequest)
 }
 
+func (this OptionsHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
+	log.Printf("%s: %s %s", GetRemoteAddress(req), req.Method, req.URL.Path)
+	resp.Header().Set("Access-Control-Allow-Methods", "GET, HEAD, PUT, OPTIONS")
+	resp.Header().Set("Access-Control-Allow-Origin", "*")
+	resp.Header().Set("Access-Control-Allow-Headers", "Authorization, X-Keep-Desired-Replicas")
+	resp.Header().Set("Access-Control-Max-Age", "86486400")
+}
+
 func (this GetBlockHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
+	resp.Header().Set("Access-Control-Allow-Origin", "*")
+	resp.Header().Set("Access-Control-Allow-Headers", "Authorization")
 
 	kc := *this.KeepClient
 
diff --git a/services/keepproxy/keepproxy_test.go b/services/keepproxy/keepproxy_test.go
index 88ac8a6..ba9793d 100644
--- a/services/keepproxy/keepproxy_test.go
+++ b/services/keepproxy/keepproxy_test.go
@@ -222,7 +222,7 @@ func (s *ServerRequiredSuite) TestPutAskGet(c *C) {
 }
 
 func (s *ServerRequiredSuite) TestPutAskGetForbidden(c *C) {
-	log.Print("TestPutAndGet start")
+	log.Print("TestPutAskGetForbidden start")
 
 	kc := runProxy(c, []string{"keepproxy"}, "123abc", 29951)
 	waitForListener()
@@ -260,7 +260,7 @@ func (s *ServerRequiredSuite) TestPutAskGetForbidden(c *C) {
 		log.Print("Get")
 	}
 
-	log.Print("TestPutAndGetForbidden done")
+	log.Print("TestPutAskGetForbidden done")
 }
 
 func (s *ServerRequiredSuite) TestGetDisabled(c *C) {
@@ -320,3 +320,35 @@ func (s *ServerRequiredSuite) TestPutDisabled(c *C) {
 
 	log.Print("TestPutDisabled done")
 }
+
+func (s *ServerRequiredSuite) TestCorsHeaders(c *C) {
+	runProxy(c, []string{"keepproxy"}, "4axaw8zxe0qm22wa6urpp5nskcne8z88cvbupv653y1njyi05h", 29954)
+	waitForListener()
+	defer closeListener()
+
+	{
+		client := http.Client{}
+		req, err := http.NewRequest("OPTIONS",
+			fmt.Sprintf("http://localhost:29954/%x+3",
+				md5.Sum([]byte("foo"))),
+			nil)
+		req.Header.Add("Access-Control-Request-Method", "PUT")
+		req.Header.Add("Access-Control-Request-Headers", "Authorization, X-Keep-Desired-Replicas")
+		resp, err := client.Do(req)
+		c.Check(err, Equals, nil)
+		c.Check(resp.StatusCode, Equals, 200)
+		body, err := ioutil.ReadAll(resp.Body)
+		c.Check(string(body), Equals, "")
+		c.Check(resp.Header.Get("Access-Control-Allow-Methods"), Equals, "GET, HEAD, PUT, OPTIONS")
+		c.Check(resp.Header.Get("Access-Control-Allow-Origin"), Equals, "*")
+	}
+
+	{
+		resp, err := http.Get(
+			fmt.Sprintf("http://localhost:29954/%x+3",
+				md5.Sum([]byte("foo"))))
+		c.Check(err, Equals, nil)
+		c.Check(resp.Header.Get("Access-Control-Allow-Headers"), Equals, "Authorization")
+		c.Check(resp.Header.Get("Access-Control-Allow-Origin"), Equals, "*")
+	}
+}

commit afef51be31f166fda47481e558881de09e97a983
Author: Tom Clegg <tom at curoverse.com>
Date:   Wed Nov 26 00:40:49 2014 -0500

    3781: Add tests for CORS headers. Turn off CORS headers at OPTIONS /login etc.

diff --git a/services/api/app/controllers/user_sessions_controller.rb b/services/api/app/controllers/user_sessions_controller.rb
index cdcb720..f6efda1 100644
--- a/services/api/app/controllers/user_sessions_controller.rb
+++ b/services/api/app/controllers/user_sessions_controller.rb
@@ -140,4 +140,8 @@ class UserSessionsController < ApplicationController
     callback_url += 'api_token=' + api_client_auth.api_token
     redirect_to callback_url
   end
+
+  def cross_origin_forbidden
+    send_error 'Forbidden', status: 403
+  end
 end
diff --git a/services/api/config/routes.rb b/services/api/config/routes.rb
index 096a0a5..c2cffd4 100644
--- a/services/api/config/routes.rb
+++ b/services/api/config/routes.rb
@@ -3,8 +3,13 @@ Server::Application.routes.draw do
 
   # See http://guides.rubyonrails.org/routing.html
 
-  # OPTIONS requests just get an empty response with CORS headers.
-  match '*a', :to => 'static#empty', :via => 'OPTIONS'
+  # OPTIONS requests are not allowed at routes that use cookies.
+  ['/auth/*a', '/login', '/logout'].each do |nono|
+    match nono, :to => 'user_sessions#cross_origin_forbidden', :via => 'OPTIONS'
+  end
+  # OPTIONS at discovery and API paths get an empty response with CORS headers.
+  match '/discovery/v1/*a', :to => 'static#empty', :via => 'OPTIONS'
+  match '/arvados/v1/*a', :to => 'static#empty', :via => 'OPTIONS'
 
   namespace :arvados do
     namespace :v1 do
diff --git a/services/api/test/integration/cross_origin_test.rb b/services/api/test/integration/cross_origin_test.rb
new file mode 100644
index 0000000..ebe7ce7
--- /dev/null
+++ b/services/api/test/integration/cross_origin_test.rb
@@ -0,0 +1,76 @@
+require 'test_helper'
+
+class CrossOriginTest < ActionDispatch::IntegrationTest
+  def options *args
+    # Rails doesn't support OPTIONS the same way as GET, POST, etc.
+    reset! unless integration_session
+    integration_session.__send__(:process, :options, *args).tap do
+      copy_session_variables!
+    end
+  end
+
+  %w(/login /logout /auth/example/callback /auth/joshid).each do |path|
+    test "OPTIONS requests are refused at #{path}" do
+      options path, {}, {}
+      assert_no_cors_headers
+    end
+
+    test "CORS headers do not exist at GET #{path}" do
+      get path, {}, {}
+      assert_no_cors_headers
+    end
+  end
+
+  %w(/discovery/v1/apis/arvados/v1/rest).each do |path|
+    test "CORS headers are set at GET #{path}" do
+      get path, {}, {}
+      assert_response :success
+      assert_cors_headers
+    end
+  end
+
+  ['/arvados/v1/collections',
+   '/arvados/v1/users',
+   '/arvados/v1/api_client_authorizations'].each do |path|
+    test "CORS headers are set and body is stub at OPTIONS #{path}" do
+      options path, {}, {}
+      assert_response :success
+      assert_cors_headers
+      assert_equal '-', response.body
+    end
+
+    test "CORS headers are set at authenticated GET #{path}" do
+      get path, {}, auth(:active_trustedclient)
+      assert_response :success
+      assert_cors_headers
+    end
+
+    # CORS headers are OK only if cookies are *not* used to determine
+    # whether a transaction is allowed. The following is a (far from
+    # perfect) test that the usual Rails cookie->session mechanism
+    # does not grant access to any resources.
+    ['GET', 'POST'].each do |method|
+      test "Session does not work at #{method} #{path}" do
+        send method.downcase, path, {format: 'json'}, {user_id: 1}
+        assert_response 401
+        assert_cors_headers
+      end
+    end
+  end
+
+  protected
+  def assert_cors_headers
+    assert_equal '*', response.headers['Access-Control-Allow-Origin']
+    allowed = response.headers['Access-Control-Allow-Methods'].split(', ')
+    %w(GET HEAD POST PUT DELETE).each do |m|
+      assert_includes allowed, m, "A-C-A-Methods should include #{m}"
+    end
+    assert_equal 'Authorization', response.headers['Access-Control-Allow-Headers']
+  end
+
+  def assert_no_cors_headers
+    response.headers.keys.each do |h|
+      assert_no_match /^Access-Control-/i, h
+    end
+  end
+end

commit a25c9cb6721e61afe433a238b2e2c580adf97f31
Author: Tom Clegg <tom at curoverse.com>
Date:   Sat Nov 22 04:49:53 2014 -0500

    3781: Set CORS headers in API responses.

diff --git a/services/api/app/controllers/application_controller.rb b/services/api/app/controllers/application_controller.rb
index 4f0364f..eacd5f2 100644
--- a/services/api/app/controllers/application_controller.rb
+++ b/services/api/app/controllers/application_controller.rb
@@ -27,6 +27,7 @@ class ApplicationController < ActionController::Base
 
   ERROR_ACTIONS = [:render_error, :render_not_found]
 
+  before_filter :set_cors_headers
   before_filter :respond_with_json_by_default
   before_filter :remote_ip
   before_filter :load_read_auths
@@ -345,6 +346,13 @@ class ApplicationController < ActionController::Base
     end
   end
 
+  def set_cors_headers
+    response.headers['Access-Control-Allow-Origin'] = '*'
+    response.headers['Access-Control-Allow-Methods'] = 'GET, HEAD, PUT, POST, DELETE'
+    response.headers['Access-Control-Allow-Headers'] = 'Authorization'
+    response.headers['Access-Control-Max-Age'] = '86486400'
+  end
+
   def respond_with_json_by_default
     html_index = request.accepts.index(Mime::HTML)
     if html_index.nil? or request.accepts[0...html_index].include?(Mime::JSON)
diff --git a/services/api/app/controllers/static_controller.rb b/services/api/app/controllers/static_controller.rb
index d624ea8..9c66f01 100644
--- a/services/api/app/controllers/static_controller.rb
+++ b/services/api/app/controllers/static_controller.rb
@@ -3,7 +3,7 @@ class StaticController < ApplicationController
 
   skip_before_filter :find_object_by_uuid
   skip_before_filter :render_404_if_no_object
-  skip_before_filter :require_auth_scope, :only => [ :home, :login_failure ]
+  skip_before_filter :require_auth_scope, only: [:home, :empty, :login_failure]
 
   def home
     respond_to do |f|
@@ -20,4 +20,8 @@ class StaticController < ApplicationController
     end
   end
 
+  def empty
+    render text: "-"
+  end
+
 end
diff --git a/services/api/app/controllers/user_sessions_controller.rb b/services/api/app/controllers/user_sessions_controller.rb
index 3e79915..cdcb720 100644
--- a/services/api/app/controllers/user_sessions_controller.rb
+++ b/services/api/app/controllers/user_sessions_controller.rb
@@ -1,6 +1,7 @@
 class UserSessionsController < ApplicationController
   before_filter :require_auth_scope, :only => [ :destroy ]
 
+  skip_before_filter :set_cors_headers
   skip_before_filter :find_object_by_uuid
   skip_before_filter :render_404_if_no_object
 
diff --git a/services/api/config/routes.rb b/services/api/config/routes.rb
index 705822a..096a0a5 100644
--- a/services/api/config/routes.rb
+++ b/services/api/config/routes.rb
@@ -3,6 +3,9 @@ Server::Application.routes.draw do
 
   # See http://guides.rubyonrails.org/routing.html
 
+  # OPTIONS requests just get an empty response with CORS headers.
+  match '*a', :to => 'static#empty', :via => 'OPTIONS'
+
   namespace :arvados do
     namespace :v1 do
       resources :api_client_authorizations do

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list