diff options
Diffstat (limited to 'workhorse/internal/upload/body_uploader_test.go')
-rw-r--r-- | workhorse/internal/upload/body_uploader_test.go | 195 |
1 files changed, 195 insertions, 0 deletions
diff --git a/workhorse/internal/upload/body_uploader_test.go b/workhorse/internal/upload/body_uploader_test.go new file mode 100644 index 00000000000..451d7c97fab --- /dev/null +++ b/workhorse/internal/upload/body_uploader_test.go @@ -0,0 +1,195 @@ +package upload + +import ( + "fmt" + "io" + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "strconv" + "strings" + "testing" + + "github.com/dgrijalva/jwt-go" + "github.com/stretchr/testify/require" + + "gitlab.com/gitlab-org/gitlab-workhorse/internal/api" + "gitlab.com/gitlab-org/gitlab-workhorse/internal/filestore" + "gitlab.com/gitlab-org/gitlab-workhorse/internal/testhelper" +) + +const ( + fileContent = "A test file content" + fileLen = len(fileContent) +) + +func TestBodyUploader(t *testing.T) { + testhelper.ConfigureSecret() + + body := strings.NewReader(fileContent) + + resp := testUpload(&rails{}, &alwaysLocalPreparer{}, echoProxy(t, fileLen), body) + require.Equal(t, http.StatusOK, resp.StatusCode) + + uploadEcho, err := ioutil.ReadAll(resp.Body) + + require.NoError(t, err, "Can't read response body") + require.Equal(t, fileContent, string(uploadEcho)) +} + +func TestBodyUploaderCustomPreparer(t *testing.T) { + body := strings.NewReader(fileContent) + + resp := testUpload(&rails{}, &alwaysLocalPreparer{}, echoProxy(t, fileLen), body) + require.Equal(t, http.StatusOK, resp.StatusCode) + + uploadEcho, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err, "Can't read response body") + require.Equal(t, fileContent, string(uploadEcho)) +} + +func TestBodyUploaderCustomVerifier(t *testing.T) { + body := strings.NewReader(fileContent) + verifier := &mockVerifier{} + + resp := testUpload(&rails{}, &alwaysLocalPreparer{verifier: verifier}, echoProxy(t, fileLen), body) + require.Equal(t, http.StatusOK, resp.StatusCode) + + uploadEcho, err := ioutil.ReadAll(resp.Body) + require.NoError(t, err, "Can't read response body") + require.Equal(t, fileContent, string(uploadEcho)) + require.True(t, verifier.invoked, "Verifier.Verify not invoked") +} + +func TestBodyUploaderAuthorizationFailure(t *testing.T) { + testNoProxyInvocation(t, http.StatusUnauthorized, &rails{unauthorized: true}, &alwaysLocalPreparer{}) +} + +func TestBodyUploaderErrors(t *testing.T) { + tests := []struct { + name string + preparer *alwaysLocalPreparer + }{ + {name: "Prepare failure", preparer: &alwaysLocalPreparer{prepareError: fmt.Errorf("")}}, + {name: "Verify failure", preparer: &alwaysLocalPreparer{verifier: &alwaysFailsVerifier{}}}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + testNoProxyInvocation(t, http.StatusInternalServerError, &rails{}, test.preparer) + }) + } +} + +func testNoProxyInvocation(t *testing.T, expectedStatus int, auth PreAuthorizer, preparer Preparer) { + proxy := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + require.Fail(t, "request proxied upstream") + }) + + resp := testUpload(auth, preparer, proxy, nil) + require.Equal(t, expectedStatus, resp.StatusCode) +} + +func testUpload(auth PreAuthorizer, preparer Preparer, proxy http.Handler, body io.Reader) *http.Response { + req := httptest.NewRequest("POST", "http://example.com/upload", body) + w := httptest.NewRecorder() + + BodyUploader(auth, proxy, preparer).ServeHTTP(w, req) + + return w.Result() +} + +func echoProxy(t *testing.T, expectedBodyLength int) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + err := r.ParseForm() + require.NoError(t, err) + + require.Equal(t, "application/x-www-form-urlencoded", r.Header.Get("Content-Type"), "Wrong Content-Type header") + + require.Contains(t, r.PostForm, "file.md5") + require.Contains(t, r.PostForm, "file.sha1") + require.Contains(t, r.PostForm, "file.sha256") + require.Contains(t, r.PostForm, "file.sha512") + + require.Contains(t, r.PostForm, "file.path") + require.Contains(t, r.PostForm, "file.size") + require.Contains(t, r.PostForm, "file.gitlab-workhorse-upload") + require.Equal(t, strconv.Itoa(expectedBodyLength), r.PostFormValue("file.size")) + + token, err := jwt.ParseWithClaims(r.Header.Get(RewrittenFieldsHeader), &MultipartClaims{}, testhelper.ParseJWT) + require.NoError(t, err, "Wrong JWT header") + + rewrittenFields := token.Claims.(*MultipartClaims).RewrittenFields + if len(rewrittenFields) != 1 || len(rewrittenFields["file"]) == 0 { + t.Fatalf("Unexpected rewritten_fields value: %v", rewrittenFields) + } + + token, jwtErr := jwt.ParseWithClaims(r.PostFormValue("file.gitlab-workhorse-upload"), &testhelper.UploadClaims{}, testhelper.ParseJWT) + require.NoError(t, jwtErr, "Wrong signed upload fields") + + uploadFields := token.Claims.(*testhelper.UploadClaims).Upload + require.Contains(t, uploadFields, "name") + require.Contains(t, uploadFields, "path") + require.Contains(t, uploadFields, "remote_url") + require.Contains(t, uploadFields, "remote_id") + require.Contains(t, uploadFields, "size") + require.Contains(t, uploadFields, "md5") + require.Contains(t, uploadFields, "sha1") + require.Contains(t, uploadFields, "sha256") + require.Contains(t, uploadFields, "sha512") + + path := r.PostFormValue("file.path") + uploaded, err := os.Open(path) + require.NoError(t, err, "File not uploaded") + + //sending back the file for testing purpose + io.Copy(w, uploaded) + }) +} + +type rails struct { + unauthorized bool +} + +func (r *rails) PreAuthorizeHandler(next api.HandleFunc, _ string) http.Handler { + if r.unauthorized { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + }) + } + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + next(w, r, &api.Response{TempPath: os.TempDir()}) + }) +} + +type alwaysLocalPreparer struct { + verifier Verifier + prepareError error +} + +func (a *alwaysLocalPreparer) Prepare(_ *api.Response) (*filestore.SaveFileOpts, Verifier, error) { + opts, err := filestore.GetOpts(&api.Response{TempPath: os.TempDir()}) + if err != nil { + return nil, nil, err + } + + return opts, a.verifier, a.prepareError +} + +type alwaysFailsVerifier struct{} + +func (alwaysFailsVerifier) Verify(handler *filestore.FileHandler) error { + return fmt.Errorf("Verification failed") +} + +type mockVerifier struct { + invoked bool +} + +func (m *mockVerifier) Verify(handler *filestore.FileHandler) error { + m.invoked = true + + return nil +} |