Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-pages.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGrzegorz Bizon <grzesiek.bizon@gmail.com>2019-10-02 16:00:22 +0300
committerGrzegorz Bizon <grzesiek.bizon@gmail.com>2019-10-02 16:00:22 +0300
commit9ecd5703d7c0325c01c65c51a9ec625b1436a1ad (patch)
tree6c91b45200631720338466a49a05f849feddfc3a
parent78c7e7be19993403054f8584fbc5b6c17885b20f (diff)
parent9943255d61c5646f6cf9e1a8a03e4a2dc19831f5 (diff)
Merge branch 'master' into backstage/gb/domain-serving-refactoring
-rw-r--r--.gitlab-ci.yml1
-rw-r--r--CHANGELOG4
-rw-r--r--Makefile2
-rw-r--r--Makefile.build.mk6
-rw-r--r--VERSION2
-rw-r--r--acceptance_test.go235
-rw-r--r--app.go8
-rw-r--r--go.mod1
-rw-r--r--go.sum3
-rw-r--r--internal/artifact/artifact.go53
-rw-r--r--internal/artifact/artifact_test.go59
-rw-r--r--internal/auth/auth.go121
-rw-r--r--internal/auth/auth_test.go88
-rw-r--r--internal/handlers/handlers.go67
-rw-r--r--internal/handlers/handlers_test.go161
-rw-r--r--internal/interface.go18
-rw-r--r--internal/logging/logging.go8
-rw-r--r--internal/mocks/mocks.go128
18 files changed, 860 insertions, 105 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 56e3286a..5631d943 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -56,6 +56,7 @@ verify:
needs: ['download deps']
script:
- make setup
+ - make generate-mocks
- make verify
- make cover
artifacts:
diff --git a/CHANGELOG b/CHANGELOG
index fbd2ffbd..475c74f1 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,7 @@
+v 1.10.0
+
+- Add support for previewing artifacts that are not public !134
+
v 1.9.0
- Add full HTTP metrics and logging to GitLab pages using LabKit
diff --git a/Makefile b/Makefile
index 4c44be0a..6dd8662b 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@ IMPORT_PATH := gitlab.com/gitlab-org/gitlab-pages
V := 1
# Space separated patterns of packages to skip in list, test, fmt.
-IGNORED_PACKAGES := /vendor/ /internal/httputil/
+IGNORED_PACKAGES := /vendor/ /internal/httputil/ /internal/mocks/
# GitLab Pages is statically compiled without CGO to help it in chroot mode
export CGO_ENABLED := 0
diff --git a/Makefile.build.mk b/Makefile.build.mk
index b58e2139..13745382 100644
--- a/Makefile.build.mk
+++ b/Makefile.build.mk
@@ -1,4 +1,4 @@
-.PHONY: all setup build clean
+.PHONY: all setup generate-mocks build clean
all: gitlab-pages
@@ -7,6 +7,10 @@ setup: clean .GOPATH/.ok
go get golang.org/x/lint/golint
go get github.com/wadey/gocovmerge
go get github.com/fzipp/gocyclo
+ go get github.com/golang/mock/mockgen
+
+generate-mocks: .GOPATH/.ok
+ $Q bin/mockgen -source=internal/interface.go -destination=internal/mocks/mocks.go -package=mocks
build: .GOPATH/.ok
$Q go install $(if $V,-v) $(VERSION_FLAGS) -buildmode exe $(IMPORT_PATH)
diff --git a/VERSION b/VERSION
index f8e233b2..81c871de 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.9.0
+1.10.0
diff --git a/acceptance_test.go b/acceptance_test.go
index 85884a99..44dbc90d 100644
--- a/acceptance_test.go
+++ b/acceptance_test.go
@@ -596,6 +596,143 @@ func TestArtifactProxyRequest(t *testing.T) {
}
}
+func TestPrivateArtifactProxyRequest(t *testing.T) {
+ skipUnlessEnabled(t, "not-inplace-chroot")
+
+ setupTransport(t)
+
+ testServer := makeGitLabPagesAccessStub(t)
+
+ keyFile, certFile := CreateHTTPSFixtureFiles(t)
+ cert, err := tls.LoadX509KeyPair(certFile, keyFile)
+ require.NoError(t, err)
+ defer os.Remove(keyFile)
+ defer os.Remove(certFile)
+
+ testServer.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}
+ testServer.StartTLS()
+ defer testServer.Close()
+
+ cases := []struct {
+ Host string
+ Path string
+ Status int
+ BinaryOption string
+ Description string
+ }{
+ {
+ "group.gitlab-example.com",
+ "/-/private/-/jobs/1/artifacts/200.html",
+ http.StatusOK,
+ "",
+ "basic proxied request for private project",
+ },
+ {
+ "group.gitlab-example.com",
+ "/-/subgroup/private/-/jobs/1/artifacts/200.html",
+ http.StatusOK,
+ "",
+ "basic proxied request for subgroup",
+ },
+ {
+ "group.gitlab-example.com",
+ "/-/private/-/jobs/1/artifacts/delayed_200.html",
+ http.StatusBadGateway,
+ "-artifacts-server-timeout=1",
+ "502 error while attempting to proxy",
+ },
+ {
+ "group.gitlab-example.com",
+ "/-/private/-/jobs/1/artifacts/404.html",
+ http.StatusNotFound,
+ "",
+ "Proxying 404 from server",
+ },
+ {
+ "group.gitlab-example.com",
+ "/-/private/-/jobs/1/artifacts/500.html",
+ http.StatusInternalServerError,
+ "",
+ "Proxying 500 from server",
+ },
+ }
+
+ // Ensure the IP address is used in the URL, as we're relying on IP SANs to
+ // validate
+ artifactServerURL := testServer.URL + "/api/v4"
+ t.Log("Artifact server URL", artifactServerURL)
+
+ for _, c := range cases {
+
+ t.Run(fmt.Sprintf("Proxy Request Test with AC: %s", c.Description), func(t *testing.T) {
+ teardown := RunPagesProcessWithSSLCertFile(
+ t,
+ *pagesBinary,
+ listeners,
+ "",
+ certFile,
+ "-artifacts-server="+artifactServerURL,
+ "-auth-client-id=1",
+ "-auth-client-secret=1",
+ "-auth-server="+testServer.URL,
+ "-auth-redirect-uri=https://projects.gitlab-example.com/auth",
+ "-auth-secret=something-very-secret",
+ c.BinaryOption,
+ )
+ defer teardown()
+
+ resp, err := GetRedirectPage(t, httpListener, c.Host, c.Path)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ require.Equal(t, http.StatusFound, resp.StatusCode)
+
+ cookie := resp.Header.Get("Set-Cookie")
+
+ // Redirects to the projects under gitlab pages domain for authentication flow
+ url, err := url.Parse(resp.Header.Get("Location"))
+ require.NoError(t, err)
+ require.Equal(t, "projects.gitlab-example.com", url.Host)
+ require.Equal(t, "/auth", url.Path)
+ state := url.Query().Get("state")
+
+ resp, err = GetRedirectPage(t, httpsListener, url.Host, url.Path+"?"+url.RawQuery)
+
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ require.Equal(t, http.StatusFound, resp.StatusCode)
+ pagesDomainCookie := resp.Header.Get("Set-Cookie")
+
+ // Go to auth page with correct state will cause fetching the token
+ authrsp, err := GetRedirectPageWithCookie(t, httpsListener, "projects.gitlab-example.com", "/auth?code=1&state="+
+ state, pagesDomainCookie)
+
+ require.NoError(t, err)
+ defer authrsp.Body.Close()
+
+ // Will redirect auth callback to correct host
+ url, err = url.Parse(authrsp.Header.Get("Location"))
+ require.NoError(t, err)
+ require.Equal(t, c.Host, url.Host)
+ require.Equal(t, "/auth", url.Path)
+
+ // Request auth callback in project domain
+ authrsp, err = GetRedirectPageWithCookie(t, httpsListener, url.Host, url.Path+"?"+url.RawQuery, cookie)
+
+ // server returns the ticket, user will be redirected to the project page
+ require.Equal(t, http.StatusFound, authrsp.StatusCode)
+ cookie = authrsp.Header.Get("Set-Cookie")
+ resp, err = GetRedirectPageWithCookie(t, httpsListener, c.Host, c.Path, cookie)
+
+ require.Equal(t, c.Status, resp.StatusCode)
+
+ require.NoError(t, err)
+ defer resp.Body.Close()
+ })
+ }
+}
+
func TestEnvironmentVariablesConfig(t *testing.T) {
skipUnlessEnabled(t)
os.Setenv("LISTEN_HTTP", net.JoinHostPort(httpListener.Host, httpListener.Port))
@@ -778,10 +915,6 @@ func TestWhenLoginCallbackWithCorrectStateWithoutEndpoint(t *testing.T) {
// 2000-2999: Unauthorized
// 3000-3999: Invalid token
func makeGitLabPagesAccessStub(t *testing.T) *httptest.Server {
- allowedProjects := regexp.MustCompile(`/api/v4/projects/1\d{3}/pages_access`)
- deniedProjects := regexp.MustCompile(`/api/v4/projects/2\d{3}/pages_access`)
- invalidTokenProjects := regexp.MustCompile(`/api/v4/projects/3\d{3}/pages_access`)
-
return httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/oauth/token":
@@ -792,22 +925,10 @@ func makeGitLabPagesAccessStub(t *testing.T) *httptest.Server {
require.Equal(t, "Bearer abc", r.Header.Get("Authorization"))
w.WriteHeader(http.StatusOK)
default:
- switch {
- case allowedProjects.MatchString(r.URL.Path):
- require.Equal(t, "Bearer abc", r.Header.Get("Authorization"))
- w.WriteHeader(http.StatusOK)
- case deniedProjects.MatchString(r.URL.Path):
- require.Equal(t, "Bearer abc", r.Header.Get("Authorization"))
- w.WriteHeader(http.StatusUnauthorized)
- case invalidTokenProjects.MatchString(r.URL.Path):
- require.Equal(t, "Bearer abc", r.Header.Get("Authorization"))
- w.WriteHeader(http.StatusUnauthorized)
- fmt.Fprint(w, "{\"error\":\"invalid_token\"}")
- default:
- t.Logf("Unexpected r.URL.RawPath: %q", r.URL.Path)
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
- w.WriteHeader(http.StatusNotFound)
+ if handleAccessControlArtifactRequests(t, w, r) {
+ return
}
+ handleAccessControlRequests(t, w, r)
}
}))
}
@@ -878,6 +999,72 @@ func TestAcmeChallengesWhenItIsNotConfigured(t *testing.T) {
)
}
+func handleAccessControlArtifactRequests(t *testing.T, w http.ResponseWriter, r *http.Request) bool {
+ authorization := r.Header.Get("Authorization")
+
+ switch {
+ case regexp.MustCompile(`/api/v4/projects/group/private/jobs/\d+/artifacts/delayed_200.html`).MatchString(r.URL.Path):
+ sleepIfAuthorized(t, authorization, w)
+ return true
+ case regexp.MustCompile(`/api/v4/projects/group/private/jobs/\d+/artifacts/404.html`).MatchString(r.URL.Path):
+ w.WriteHeader(http.StatusNotFound)
+ return true
+ case regexp.MustCompile(`/api/v4/projects/group/private/jobs/\d+/artifacts/500.html`).MatchString(r.URL.Path):
+ returnIfAuthorized(t, authorization, w, http.StatusInternalServerError)
+ return true
+ case regexp.MustCompile(`/api/v4/projects/group/private/jobs/\d+/artifacts/200.html`).MatchString(r.URL.Path):
+ returnIfAuthorized(t, authorization, w, http.StatusOK)
+ return true
+ case regexp.MustCompile(`/api/v4/projects/group/subgroup/private/jobs/\d+/artifacts/200.html`).MatchString(r.URL.Path):
+ returnIfAuthorized(t, authorization, w, http.StatusOK)
+ return true
+ default:
+ return false
+ }
+}
+
+func handleAccessControlRequests(t *testing.T, w http.ResponseWriter, r *http.Request) {
+ allowedProjects := regexp.MustCompile(`/api/v4/projects/1\d{3}/pages_access`)
+ deniedProjects := regexp.MustCompile(`/api/v4/projects/2\d{3}/pages_access`)
+ invalidTokenProjects := regexp.MustCompile(`/api/v4/projects/3\d{3}/pages_access`)
+
+ switch {
+ case allowedProjects.MatchString(r.URL.Path):
+ require.Equal(t, "Bearer abc", r.Header.Get("Authorization"))
+ w.WriteHeader(http.StatusOK)
+ case deniedProjects.MatchString(r.URL.Path):
+ require.Equal(t, "Bearer abc", r.Header.Get("Authorization"))
+ w.WriteHeader(http.StatusUnauthorized)
+ case invalidTokenProjects.MatchString(r.URL.Path):
+ require.Equal(t, "Bearer abc", r.Header.Get("Authorization"))
+ w.WriteHeader(http.StatusUnauthorized)
+ fmt.Fprint(w, "{\"error\":\"invalid_token\"}")
+ default:
+ t.Logf("Unexpected r.URL.RawPath: %q", r.URL.Path)
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ w.WriteHeader(http.StatusNotFound)
+ }
+}
+
+func returnIfAuthorized(t *testing.T, authorization string, w http.ResponseWriter, status int) {
+ if authorization != "" {
+ require.Equal(t, "Bearer abc", authorization)
+ w.WriteHeader(status)
+ } else {
+ w.WriteHeader(http.StatusNotFound)
+ }
+}
+
+func sleepIfAuthorized(t *testing.T, authorization string, w http.ResponseWriter) {
+ if authorization != "" {
+ require.Equal(t, "Bearer abc", authorization)
+ time.Sleep(2 * time.Second)
+ } else {
+ w.WriteHeader(http.StatusNotFound)
+ }
+
+}
+
func TestAccessControlUnderCustomDomain(t *testing.T) {
skipUnlessEnabled(t, "not-inplace-chroot")
@@ -1034,14 +1221,18 @@ func TestAccessControlProject404DoesNotRedirect(t *testing.T) {
require.Equal(t, http.StatusNotFound, rsp.StatusCode)
}
-func TestAccessControl(t *testing.T) {
- skipUnlessEnabled(t, "not-inplace-chroot")
-
+func setupTransport(t *testing.T) {
transport := (TestHTTPSClient.Transport).(*http.Transport)
defer func(t time.Duration) {
transport.ResponseHeaderTimeout = t
}(transport.ResponseHeaderTimeout)
transport.ResponseHeaderTimeout = 5 * time.Second
+}
+
+func TestAccessControl(t *testing.T) {
+ skipUnlessEnabled(t, "not-inplace-chroot")
+
+ setupTransport(t)
keyFile, certFile := CreateHTTPSFixtureFiles(t)
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
diff --git a/app.go b/app.go
index 82cc2680..e9130467 100644
--- a/app.go
+++ b/app.go
@@ -22,6 +22,7 @@ import (
"gitlab.com/gitlab-org/gitlab-pages/internal/auth"
headerConfig "gitlab.com/gitlab-org/gitlab-pages/internal/config"
"gitlab.com/gitlab-org/gitlab-pages/internal/domain"
+ "gitlab.com/gitlab-org/gitlab-pages/internal/handlers"
"gitlab.com/gitlab-org/gitlab-pages/internal/httperrors"
"gitlab.com/gitlab-org/gitlab-pages/internal/logging"
"gitlab.com/gitlab-org/gitlab-pages/internal/netutil"
@@ -49,6 +50,7 @@ type theApp struct {
domains *source.Domains
Artifact *artifact.Artifact
Auth *auth.Auth
+ Handlers *handlers.Handlers
AcmeMiddleware *acme.Middleware
CustomHeaders http.Header
}
@@ -139,9 +141,7 @@ func (a *theApp) tryAuxiliaryHandlers(w http.ResponseWriter, r *http.Request, ht
return true
}
- // In the event a host is prefixed with the artifact prefix an artifact
- // value is created, and an attempt to proxy the request is made
- if a.Artifact.TryMakeRequest(host, w, r) {
+ if a.Handlers.HandleArtifactRequest(host, w, r) {
return true
}
@@ -477,6 +477,8 @@ func runApp(config appConfig) {
config.RedirectURI, config.GitLabServer)
}
+ a.Handlers = handlers.New(a.Auth, a.Artifact)
+
if config.GitLabServer != "" {
a.AcmeMiddleware = &acme.Middleware{GitlabURL: config.GitLabServer}
}
diff --git a/go.mod b/go.mod
index 2c4252a4..29f515e0 100644
--- a/go.mod
+++ b/go.mod
@@ -6,6 +6,7 @@ require (
github.com/certifi/gocertifi v0.0.0-20190905060710-a5e0173ced67 // indirect
github.com/fzipp/gocyclo v0.0.0-20150627053110-6acd4345c835
github.com/gogo/protobuf v1.3.0 // indirect
+ github.com/golang/mock v1.3.1
github.com/gorilla/context v1.1.1
github.com/gorilla/securecookie v1.1.1
github.com/gorilla/sessions v1.2.0
diff --git a/go.sum b/go.sum
index d2b5584f..55aaab32 100644
--- a/go.sum
+++ b/go.sum
@@ -39,6 +39,8 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekf
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@@ -194,6 +196,7 @@ golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGm
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b h1:qMK98NmNCRVDIYFycQ5yVRkvgDUFfdP8Ip4KqmDEB7g=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190917032747-2dc213d980bc h1:AzQrNvr65FlhSjBpg0eVCY43QLsuOqtzWGtjcBqT6J8=
golang.org/x/tools v0.0.0-20190917032747-2dc213d980bc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/internal/artifact/artifact.go b/internal/artifact/artifact.go
index 5050b426..28a595bb 100644
--- a/internal/artifact/artifact.go
+++ b/internal/artifact/artifact.go
@@ -1,6 +1,7 @@
package artifact
import (
+ "errors"
"fmt"
"io"
"net/http"
@@ -11,8 +12,11 @@ import (
"strings"
"time"
+ "gitlab.com/gitlab-org/labkit/errortracking"
+
"gitlab.com/gitlab-org/gitlab-pages/internal/httperrors"
"gitlab.com/gitlab-org/gitlab-pages/internal/httptransport"
+ "gitlab.com/gitlab-org/gitlab-pages/internal/logging"
)
const (
@@ -26,7 +30,10 @@ const (
var (
// Captures subgroup + project, job ID and artifacts path
- pathExtractor = regexp.MustCompile(`(?i)\A/-/(.*)/-/jobs/(\d+)/artifacts(/[^?]*)\z`)
+ pathExtractor = regexp.MustCompile(`(?i)\A/-/(.*)/-/jobs/(\d+)/artifacts(/[^?]*)\z`)
+ errCreateArtifactRequest = errors.New("Failed to create the artifact request")
+ errArtifactRequest = errors.New("Failed to request the artifact")
+ errArtifactResponse = errors.New("Artifact request response was not successful")
)
// Artifact proxies requests for artifact files to the GitLab artifacts API
@@ -51,8 +58,9 @@ func New(server string, timeoutSeconds int, pagesDomain string) *Artifact {
// TryMakeRequest will attempt to proxy a request and write it to the argument
// http.ResponseWriter, ultimately returning a bool that indicates if the
-// http.ResponseWriter has been written to in any capacity.
-func (a *Artifact) TryMakeRequest(host string, w http.ResponseWriter, r *http.Request) bool {
+// http.ResponseWriter has been written to in any capacity. Additional handler func
+// may be given which should return true if it did handle the response.
+func (a *Artifact) TryMakeRequest(host string, w http.ResponseWriter, r *http.Request, token string, additionalHandler func(*http.Response) bool) bool {
if a == nil || a.server == "" || host == "" {
return false
}
@@ -62,30 +70,51 @@ func (a *Artifact) TryMakeRequest(host string, w http.ResponseWriter, r *http.Re
return false
}
- a.makeRequest(w, reqURL)
+ a.makeRequest(w, r, reqURL, token, additionalHandler)
+
return true
}
-func (a *Artifact) makeRequest(w http.ResponseWriter, reqURL *url.URL) {
- resp, err := a.client.Get(reqURL.String())
+func (a *Artifact) makeRequest(w http.ResponseWriter, r *http.Request, reqURL *url.URL, token string, additionalHandler func(*http.Response) bool) {
+ req, err := http.NewRequest("GET", reqURL.String(), nil)
+ if err != nil {
+ logging.LogRequest(r).WithError(err).Error(errCreateArtifactRequest)
+ errortracking.Capture(err, errortracking.WithRequest(r))
+ httperrors.Serve500(w)
+ return
+ }
+
+ if token != "" {
+ req.Header.Add("Authorization", "Bearer "+token)
+ }
+ resp, err := a.client.Do(req)
+
if err != nil {
+ logging.LogRequest(r).WithError(err).Error(errArtifactRequest)
+ errortracking.Capture(err, errortracking.WithRequest(r))
httperrors.Serve502(w)
return
}
+ if additionalHandler(resp) {
+ return
+ }
+
if resp.StatusCode == http.StatusNotFound {
httperrors.Serve404(w)
return
}
if resp.StatusCode == http.StatusInternalServerError {
+ logging.LogRequest(r).Error(errArtifactResponse)
+ errortracking.Capture(errArtifactResponse, errortracking.WithRequest(r))
httperrors.Serve500(w)
return
}
- // we only cache responses within the 2xx series response codes
- if (resp.StatusCode >= minStatusCode) && (resp.StatusCode <= maxStatusCode) {
- w.Header().Set("Cache-Control", "max-age=3600")
+ // we only cache responses within the 2xx series response codes and that were not private
+ if token == "" {
+ addCacheHeader(w, resp)
}
w.Header().Set("Content-Type", resp.Header.Get("Content-Type"))
@@ -95,6 +124,12 @@ func (a *Artifact) makeRequest(w http.ResponseWriter, reqURL *url.URL) {
return
}
+func addCacheHeader(w http.ResponseWriter, resp *http.Response) {
+ if (resp.StatusCode >= minStatusCode) && (resp.StatusCode <= maxStatusCode) {
+ w.Header().Set("Cache-Control", "max-age=3600")
+ }
+}
+
// BuildURL returns a pointer to a url.URL for where the request should be
// proxied to. The returned bool will indicate if there is some sort of issue
// with the url while it is being generated.
diff --git a/internal/artifact/artifact_test.go b/internal/artifact/artifact_test.go
index ef37384e..689effb5 100644
--- a/internal/artifact/artifact_test.go
+++ b/internal/artifact/artifact_test.go
@@ -15,29 +15,12 @@ import (
func TestTryMakeRequest(t *testing.T) {
content := "<!DOCTYPE html><html><head><title>Title of the document</title></head><body></body></html>"
contentType := "text/html; charset=utf-8"
- testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-Type", contentType)
- switch r.URL.RawPath {
- case "/projects/group%2Fsubgroup%2Fproject/jobs/1/artifacts/200.html":
- w.WriteHeader(http.StatusOK)
- case "/projects/group%2Fsubgroup%2Fproject/jobs/1/artifacts/max-caching.html":
- w.WriteHeader(http.StatusIMUsed)
- case "/projects/group%2Fsubgroup%2Fproject/jobs/1/artifacts/non-caching.html":
- w.WriteHeader(http.StatusTeapot)
- case "/projects/group%2Fsubgroup%2Fproject/jobs/1/artifacts/500.html":
- w.WriteHeader(http.StatusInternalServerError)
- case "/projects/group%2Fsubgroup%2Fgroup%2Fproject/jobs/1/artifacts/404.html":
- w.WriteHeader(http.StatusNotFound)
- default:
- t.Log("Surprising r.URL.RawPath", r.URL.RawPath)
- w.WriteHeader(999)
- }
- fmt.Fprint(w, content)
- }))
+ testServer := makeArtifactServerStub(t, content, contentType)
defer testServer.Close()
cases := []struct {
Path string
+ Token string
Status int
Content string
Length string
@@ -47,6 +30,7 @@ func TestTryMakeRequest(t *testing.T) {
}{
{
"/200.html",
+ "",
http.StatusOK,
content,
"90",
@@ -55,7 +39,18 @@ func TestTryMakeRequest(t *testing.T) {
"basic successful request",
},
{
+ "/200.html",
+ "token",
+ http.StatusOK,
+ content,
+ "90",
+ "",
+ "text/html; charset=utf-8",
+ "basic successful request",
+ },
+ {
"/max-caching.html",
+ "",
http.StatusIMUsed,
content,
"90",
@@ -65,6 +60,7 @@ func TestTryMakeRequest(t *testing.T) {
},
{
"/non-caching.html",
+ "",
http.StatusTeapot,
content,
"90",
@@ -82,7 +78,7 @@ func TestTryMakeRequest(t *testing.T) {
r := &http.Request{URL: reqURL}
art := artifact.New(testServer.URL, 1, "gitlab-example.io")
- require.True(t, art.TryMakeRequest("group.gitlab-example.io", result, r))
+ require.True(t, art.TryMakeRequest("group.gitlab-example.io", result, r, c.Token, func(resp *http.Response) bool { return false }))
require.Equal(t, c.Status, result.Code)
require.Equal(t, c.ContentType, result.Header().Get("Content-Type"))
require.Equal(t, c.Length, result.Header().Get("Content-Length"))
@@ -93,6 +89,29 @@ func TestTryMakeRequest(t *testing.T) {
}
}
+// provide stub for testing different artifact responses
+func makeArtifactServerStub(t *testing.T, content string, contentType string) *httptest.Server {
+ return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Content-Type", contentType)
+ switch r.URL.RawPath {
+ case "/projects/group%2Fsubgroup%2Fproject/jobs/1/artifacts/200.html":
+ w.WriteHeader(http.StatusOK)
+ case "/projects/group%2Fsubgroup%2Fproject/jobs/1/artifacts/max-caching.html":
+ w.WriteHeader(http.StatusIMUsed)
+ case "/projects/group%2Fsubgroup%2Fproject/jobs/1/artifacts/non-caching.html":
+ w.WriteHeader(http.StatusTeapot)
+ case "/projects/group%2Fsubgroup%2Fproject/jobs/1/artifacts/500.html":
+ w.WriteHeader(http.StatusInternalServerError)
+ case "/projects/group%2Fsubgroup%2Fgroup%2Fproject/jobs/1/artifacts/404.html":
+ w.WriteHeader(http.StatusNotFound)
+ default:
+ t.Log("Surprising r.URL.RawPath", r.URL.RawPath)
+ w.WriteHeader(999)
+ }
+ fmt.Fprint(w, content)
+ }))
+}
+
func TestBuildURL(t *testing.T) {
cases := []struct {
RawServer string
diff --git a/internal/auth/auth.go b/internal/auth/auth.go
index 95a26250..2e8473b4 100644
--- a/internal/auth/auth.go
+++ b/internal/auth/auth.go
@@ -364,6 +364,19 @@ func (a *Auth) fetchAccessToken(code string) (tokenResponse, error) {
return token, nil
}
+func (a *Auth) checkSessionIsValid(w http.ResponseWriter, r *http.Request) *sessions.Session {
+ session, err := a.checkSession(w, r)
+ if err != nil {
+ return nil
+ }
+
+ if a.checkTokenExists(session, w, r) {
+ return nil
+ }
+
+ return session
+}
+
func (a *Auth) checkTokenExists(session *sessions.Session, w http.ResponseWriter, r *http.Request) bool {
// If no access token redirect to OAuth login page
if session.Values["access_token"] == nil {
@@ -424,25 +437,19 @@ func (a *Auth) IsAuthSupported() bool {
return true
}
-// CheckAuthenticationWithoutProject checks if user is authenticated and has a valid token
-func (a *Auth) CheckAuthenticationWithoutProject(w http.ResponseWriter, r *http.Request) bool {
-
- if a == nil {
- // No auth supported
- return false
- }
-
- session, err := a.checkSession(w, r)
- if err != nil {
- return true
- }
-
- if a.checkTokenExists(session, w, r) {
+func (a *Auth) checkAuthentication(w http.ResponseWriter, r *http.Request, projectID uint64) bool {
+ session := a.checkSessionIsValid(w, r)
+ if session == nil {
return true
}
// Access token exists, authorize request
- url := fmt.Sprintf(apiURLUserTemplate, a.gitLabServer)
+ var url string
+ if projectID > 0 {
+ url = fmt.Sprintf(apiURLProjectTemplate, a.gitLabServer, projectID)
+ } else {
+ url = fmt.Sprintf(apiURLUserTemplate, a.gitLabServer)
+ }
req, err := http.NewRequest("GET", url, nil)
if err != nil {
@@ -456,19 +463,16 @@ func (a *Auth) CheckAuthenticationWithoutProject(w http.ResponseWriter, r *http.
req.Header.Add("Authorization", "Bearer "+session.Values["access_token"].(string))
resp, err := a.apiClient.Do(req)
- if checkResponseForInvalidToken(resp, err) {
- logRequest(r).Warn("Access token was invalid, destroying session")
-
- destroySession(session, w, r)
+ if err == nil && checkResponseForInvalidToken(resp, session, w, r) {
return true
}
if err != nil || resp.StatusCode != 200 {
- // We return 404 if for some reason token is not valid to avoid (not) existence leak
if err != nil {
logRequest(r).WithError(err).Error("Failed to retrieve info with token")
}
+ // We return 404 if for some reason token is not valid to avoid (not) existence leak
httperrors.Serve404(w)
return true
}
@@ -476,63 +480,81 @@ func (a *Auth) CheckAuthenticationWithoutProject(w http.ResponseWriter, r *http.
return false
}
-// CheckAuthentication checks if user is authenticated and has access to the project
-func (a *Auth) CheckAuthentication(w http.ResponseWriter, r *http.Request, projectID uint64) bool {
- logRequest(r).Debug("Authenticate request")
+// CheckAuthenticationWithoutProject checks if user is authenticated and has a valid token
+func (a *Auth) CheckAuthenticationWithoutProject(w http.ResponseWriter, r *http.Request) bool {
if a == nil {
- logRequest(r).Error(errAuthNotConfigured)
- errortracking.Capture(errAuthNotConfigured, errortracking.WithRequest(r))
+ // No auth supported
+ return false
+ }
- httperrors.Serve500(w)
- return true
+ return a.checkAuthentication(w, r, 0)
+}
+
+// GetTokenIfExists returns the token if it exists
+func (a *Auth) GetTokenIfExists(w http.ResponseWriter, r *http.Request) (string, error) {
+ if a == nil {
+ return "", nil
}
session, err := a.checkSession(w, r)
if err != nil {
- return true
+ return "", errors.New("Error retrieving the session")
}
- if a.checkTokenExists(session, w, r) {
+ if session.Values["access_token"] != nil {
+ return session.Values["access_token"].(string), nil
+ }
+
+ return "", nil
+}
+
+// RequireAuth will trigger authentication flow if no token exists
+func (a *Auth) RequireAuth(w http.ResponseWriter, r *http.Request) bool {
+ session := a.checkSessionIsValid(w, r)
+ if session == nil {
return true
}
+ return false
+}
- // Access token exists, authorize request
- url := fmt.Sprintf(apiURLProjectTemplate, a.gitLabServer, projectID)
- req, err := http.NewRequest("GET", url, nil)
+// CheckAuthentication checks if user is authenticated and has access to the project
+func (a *Auth) CheckAuthentication(w http.ResponseWriter, r *http.Request, projectID uint64) bool {
+ logRequest(r).Debug("Authenticate request")
- if err != nil {
- errortracking.Capture(err, errortracking.WithRequest(req))
+ if a == nil {
+ logRequest(r).Error(errAuthNotConfigured)
+ errortracking.Capture(errAuthNotConfigured, errortracking.WithRequest(r))
httperrors.Serve500(w)
return true
}
- req.Header.Add("Authorization", "Bearer "+session.Values["access_token"].(string))
- resp, err := a.apiClient.Do(req)
+ return a.checkAuthentication(w, r, projectID)
+}
- if checkResponseForInvalidToken(resp, err) {
- logRequest(r).Warn("Access token was invalid, destroying session")
+// CheckResponseForInvalidToken checks response for invalid token and destroys session if it was invalid
+func (a *Auth) CheckResponseForInvalidToken(w http.ResponseWriter, r *http.Request,
+ resp *http.Response) bool {
+ if a == nil {
+ // No auth supported
+ return false
+ }
- destroySession(session, w, r)
+ session, err := a.checkSession(w, r)
+ if err != nil {
return true
}
- if err != nil || resp.StatusCode != 200 {
- if err != nil {
- logRequest(r).WithError(err).Error("Failed to retrieve info with token")
- }
-
- // We return 404 if user has no access to avoid user knowing if the pages really existed or not
- httperrors.Serve404(w)
+ if checkResponseForInvalidToken(resp, session, w, r) {
return true
}
return false
}
-func checkResponseForInvalidToken(resp *http.Response, err error) bool {
- if err == nil && resp.StatusCode == 401 {
+func checkResponseForInvalidToken(resp *http.Response, session *sessions.Session, w http.ResponseWriter, r *http.Request) bool {
+ if resp.StatusCode == http.StatusUnauthorized {
errResp := errorResponse{}
// Parse response
@@ -545,6 +567,9 @@ func checkResponseForInvalidToken(resp *http.Response, err error) bool {
if errResp.Error == "invalid_token" {
// Token is invalid
+ logRequest(r).Warn("Access token was invalid, destroying session")
+
+ destroySession(session, w, r)
return true
}
}
diff --git a/internal/auth/auth_test.go b/internal/auth/auth_test.go
index ad6550ac..c082cfdf 100644
--- a/internal/auth/auth_test.go
+++ b/internal/auth/auth_test.go
@@ -1,7 +1,9 @@
package auth
import (
+ "bytes"
"fmt"
+ "io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
@@ -332,3 +334,89 @@ func TestGenerateKeyPair(t *testing.T) {
require.Equal(t, len(signingSecret), 32)
require.Equal(t, len(encryptionSecret), 32)
}
+
+func TestGetTokenIfExistsWhenTokenExists(t *testing.T) {
+ store := sessions.NewCookieStore([]byte("something-very-secret"))
+ auth := New("pages.gitlab-example.com",
+ "something-very-secret",
+ "id",
+ "secret",
+ "http://pages.gitlab-example.com/auth",
+ "")
+
+ result := httptest.NewRecorder()
+ reqURL, err := url.Parse("/")
+ require.NoError(t, err)
+ r := &http.Request{URL: reqURL}
+ r = request.WithHTTPSFlag(r, false)
+
+ session, _ := store.Get(r, "gitlab-pages")
+ session.Values["access_token"] = "abc"
+ session.Save(r, result)
+
+ token, err := auth.GetTokenIfExists(result, r)
+ require.Equal(t, "abc", token)
+}
+
+func TestGetTokenIfExistsWhenTokenDoesNotExist(t *testing.T) {
+ store := sessions.NewCookieStore([]byte("something-very-secret"))
+ auth := New("pages.gitlab-example.com",
+ "something-very-secret",
+ "id",
+ "secret",
+ "http://pages.gitlab-example.com/auth",
+ "")
+
+ result := httptest.NewRecorder()
+ reqURL, err := url.Parse("http://pages.gitlab-example.com/test")
+ require.NoError(t, err)
+ r := &http.Request{URL: reqURL, Host: "pages.gitlab-example.com", RequestURI: "/test"}
+ r = request.WithHTTPSFlag(r, false)
+
+ session, _ := store.Get(r, "gitlab-pages")
+ session.Save(r, result)
+
+ token, err := auth.GetTokenIfExists(result, r)
+ require.Equal(t, "", token)
+ require.Equal(t, nil, err)
+}
+
+func TestCheckResponseForInvalidTokenWhenInvalidToken(t *testing.T) {
+ auth := New("pages.gitlab-example.com",
+ "something-very-secret",
+ "id",
+ "secret",
+ "http://pages.gitlab-example.com/auth",
+ "")
+
+ result := httptest.NewRecorder()
+ reqURL, err := url.Parse("http://pages.gitlab-example.com/test")
+ require.NoError(t, err)
+ r := &http.Request{URL: reqURL, Host: "pages.gitlab-example.com", RequestURI: "/test"}
+ r = request.WithHTTPSFlag(r, false)
+
+ resp := &http.Response{StatusCode: http.StatusUnauthorized, Body: ioutil.NopCloser(bytes.NewReader([]byte("{\"error\":\"invalid_token\"}")))}
+
+ require.Equal(t, true, auth.CheckResponseForInvalidToken(result, r, resp))
+ require.Equal(t, http.StatusFound, result.Result().StatusCode)
+ require.Equal(t, "http://pages.gitlab-example.com/test", result.Header().Get("Location"))
+}
+
+func TestCheckResponseForInvalidTokenWhenNotInvalidToken(t *testing.T) {
+ auth := New("pages.gitlab-example.com",
+ "something-very-secret",
+ "id",
+ "secret",
+ "http://pages.gitlab-example.com/auth",
+ "")
+
+ result := httptest.NewRecorder()
+ reqURL, err := url.Parse("/something")
+ require.NoError(t, err)
+ r := &http.Request{URL: reqURL}
+ r = request.WithHTTPSFlag(r, false)
+
+ resp := &http.Response{StatusCode: 200, Body: ioutil.NopCloser(bytes.NewReader([]byte("ok")))}
+
+ require.Equal(t, false, auth.CheckResponseForInvalidToken(result, r, resp))
+}
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go
new file mode 100644
index 00000000..5791e793
--- /dev/null
+++ b/internal/handlers/handlers.go
@@ -0,0 +1,67 @@
+package handlers
+
+import (
+ "net/http"
+
+ "gitlab.com/gitlab-org/gitlab-pages/internal"
+ "gitlab.com/gitlab-org/gitlab-pages/internal/logging"
+)
+
+// Handlers take care of handling specific requests
+type Handlers struct {
+ Auth internal.Auth
+ Artifact internal.Artifact
+}
+
+// New when provided the arguments defined herein, returns a pointer to an
+// Handlers that is used to handle requests.
+func New(auth internal.Auth, artifact internal.Artifact) *Handlers {
+ return &Handlers{
+ Auth: auth,
+ Artifact: artifact,
+ }
+}
+
+func (a *Handlers) checkIfLoginRequiredOrInvalidToken(w http.ResponseWriter, r *http.Request, token string) func(*http.Response) bool {
+ return func(resp *http.Response) bool {
+
+ if resp.StatusCode == http.StatusNotFound {
+
+ if token == "" {
+ if !a.Auth.IsAuthSupported() {
+ // Auth is not supported, probably means no access or does not exist but we cannot try with auth
+ return false
+ }
+
+ logging.LogRequest(r).Debug("Artifact API response was 404 without token, try with authentication")
+
+ // Authenticate user
+ if a.Auth.RequireAuth(w, r) {
+ return true
+ }
+ } else {
+ logging.LogRequest(r).Debug("Artifact API response was 404 with authentication")
+ }
+ }
+
+ if a.Auth.CheckResponseForInvalidToken(w, r, resp) {
+ return true
+ }
+
+ return false
+ }
+}
+
+// HandleArtifactRequest handles all artifact related requests, will return true if request was handled here
+func (a *Handlers) HandleArtifactRequest(host string, w http.ResponseWriter, r *http.Request) bool {
+ // In the event a host is prefixed with the artifact prefix an artifact
+ // value is created, and an attempt to proxy the request is made
+
+ // Always try to add token to the request if it exists
+ token, err := a.Auth.GetTokenIfExists(w, r)
+ if err != nil {
+ return true
+ }
+
+ return a.Artifact.TryMakeRequest(host, w, r, token, a.checkIfLoginRequiredOrInvalidToken(w, r, token))
+}
diff --git a/internal/handlers/handlers_test.go b/internal/handlers/handlers_test.go
new file mode 100644
index 00000000..aa664266
--- /dev/null
+++ b/internal/handlers/handlers_test.go
@@ -0,0 +1,161 @@
+package handlers
+
+import (
+ "errors"
+ "net/http"
+ "net/http/httptest"
+ "net/url"
+ "testing"
+
+ "gitlab.com/gitlab-org/gitlab-pages/internal/mocks"
+
+ "github.com/golang/mock/gomock"
+ "github.com/stretchr/testify/require"
+)
+
+func TestNotHandleArtifactRequestReturnsFalse(t *testing.T) {
+ mockCtrl := gomock.NewController(t)
+ defer mockCtrl.Finish()
+
+ mockArtifact := mocks.NewMockArtifact(mockCtrl)
+ mockArtifact.EXPECT().
+ TryMakeRequest(gomock.Any(), gomock.Any(), gomock.Any(), "", gomock.Any()).
+ Return(false).
+ Times(1)
+
+ mockAuth := mocks.NewMockAuth(mockCtrl)
+ mockAuth.EXPECT().
+ GetTokenIfExists(gomock.Any(), gomock.Any()).
+ Return("", nil).
+ Times(1)
+
+ handlers := New(mockAuth, mockArtifact)
+
+ result := httptest.NewRecorder()
+ reqURL, err := url.Parse("/something")
+ require.NoError(t, err)
+ r := &http.Request{URL: reqURL}
+
+ require.Equal(t, false, handlers.HandleArtifactRequest("host", result, r))
+}
+
+func TestHandleArtifactRequestedReturnsTrue(t *testing.T) {
+ mockCtrl := gomock.NewController(t)
+ defer mockCtrl.Finish()
+
+ mockArtifact := mocks.NewMockArtifact(mockCtrl)
+ mockArtifact.EXPECT().
+ TryMakeRequest(gomock.Any(), gomock.Any(), gomock.Any(), "", gomock.Any()).
+ Return(true).
+ Times(1)
+
+ mockAuth := mocks.NewMockAuth(mockCtrl)
+ mockAuth.EXPECT().
+ GetTokenIfExists(gomock.Any(), gomock.Any()).
+ Return("", nil).
+ Times(1)
+
+ handlers := New(mockAuth, mockArtifact)
+
+ result := httptest.NewRecorder()
+ reqURL, err := url.Parse("/something")
+ require.NoError(t, err)
+ r := &http.Request{URL: reqURL}
+
+ require.Equal(t, true, handlers.HandleArtifactRequest("host", result, r))
+}
+
+func TestNotFoundWithTokenIsNotHandled(t *testing.T) {
+ mockCtrl := gomock.NewController(t)
+ defer mockCtrl.Finish()
+
+ mockAuth := mocks.NewMockAuth(mockCtrl)
+ mockAuth.EXPECT().CheckResponseForInvalidToken(gomock.Any(), gomock.Any(), gomock.Any()).
+ Return(false)
+
+ handlers := New(mockAuth, nil)
+
+ w := httptest.NewRecorder()
+ reqURL, _ := url.Parse("/")
+ r := &http.Request{URL: reqURL}
+ response := &http.Response{StatusCode: http.StatusNotFound}
+ handled := handlers.checkIfLoginRequiredOrInvalidToken(w, r, "token")(response)
+
+ require.False(t, handled)
+}
+
+func TestNotFoundWithoutTokenIsNotHandledWhenNotAuthSupport(t *testing.T) {
+ mockCtrl := gomock.NewController(t)
+ defer mockCtrl.Finish()
+
+ mockAuth := mocks.NewMockAuth(mockCtrl)
+ mockAuth.EXPECT().IsAuthSupported().Return(false)
+
+ handlers := New(mockAuth, nil)
+
+ w := httptest.NewRecorder()
+ reqURL, _ := url.Parse("/")
+ r := &http.Request{URL: reqURL}
+ response := &http.Response{StatusCode: http.StatusNotFound}
+ handled := handlers.checkIfLoginRequiredOrInvalidToken(w, r, "")(response)
+
+ require.False(t, handled)
+}
+func TestNotFoundWithoutTokenIsHandled(t *testing.T) {
+ mockCtrl := gomock.NewController(t)
+ defer mockCtrl.Finish()
+
+ mockAuth := mocks.NewMockAuth(mockCtrl)
+ mockAuth.EXPECT().IsAuthSupported().Return(true)
+ mockAuth.EXPECT().RequireAuth(gomock.Any(), gomock.Any()).Times(1).Return(true)
+
+ handlers := New(mockAuth, nil)
+
+ w := httptest.NewRecorder()
+ reqURL, _ := url.Parse("/")
+ r := &http.Request{URL: reqURL}
+ response := &http.Response{StatusCode: http.StatusNotFound}
+ handled := handlers.checkIfLoginRequiredOrInvalidToken(w, r, "")(response)
+
+ require.True(t, handled)
+}
+func TestInvalidTokenResponseIsHandled(t *testing.T) {
+ mockCtrl := gomock.NewController(t)
+ defer mockCtrl.Finish()
+
+ mockAuth := mocks.NewMockAuth(mockCtrl)
+ mockAuth.EXPECT().CheckResponseForInvalidToken(gomock.Any(), gomock.Any(), gomock.Any()).
+ Return(true)
+
+ handlers := New(mockAuth, nil)
+
+ w := httptest.NewRecorder()
+ reqURL, _ := url.Parse("/")
+ r := &http.Request{URL: reqURL}
+ response := &http.Response{StatusCode: http.StatusUnauthorized}
+ handled := handlers.checkIfLoginRequiredOrInvalidToken(w, r, "token")(response)
+
+ require.True(t, handled)
+}
+
+func TestHandleArtifactRequestButGetTokenFails(t *testing.T) {
+ mockCtrl := gomock.NewController(t)
+ defer mockCtrl.Finish()
+
+ mockArtifact := mocks.NewMockArtifact(mockCtrl)
+ mockArtifact.EXPECT().
+ TryMakeRequest(gomock.Any(), gomock.Any(), gomock.Any(), "", gomock.Any()).
+ Times(0)
+
+ mockAuth := mocks.NewMockAuth(mockCtrl)
+ mockAuth.EXPECT().GetTokenIfExists(gomock.Any(), gomock.Any()).Return("", errors.New("error when retrieving token"))
+
+ handlers := New(mockAuth, mockArtifact)
+
+ result := httptest.NewRecorder()
+ reqURL, err := url.Parse("/something")
+ require.NoError(t, err)
+ r := &http.Request{URL: reqURL}
+
+ require.Equal(t, true, handlers.HandleArtifactRequest("host", result, r))
+}
diff --git a/internal/interface.go b/internal/interface.go
new file mode 100644
index 00000000..3e82ee3a
--- /dev/null
+++ b/internal/interface.go
@@ -0,0 +1,18 @@
+package internal
+
+import (
+ "net/http"
+)
+
+// Artifact allows to handle artifact related requests
+type Artifact interface {
+ TryMakeRequest(host string, w http.ResponseWriter, r *http.Request, token string, responseHandler func(*http.Response) bool) bool
+}
+
+// Auth handles the authentication logic
+type Auth interface {
+ IsAuthSupported() bool
+ RequireAuth(w http.ResponseWriter, r *http.Request) bool
+ GetTokenIfExists(w http.ResponseWriter, r *http.Request) (string, error)
+ CheckResponseForInvalidToken(w http.ResponseWriter, r *http.Request, resp *http.Response) bool
+}
diff --git a/internal/logging/logging.go b/internal/logging/logging.go
index 7c2f013e..28c43c2e 100644
--- a/internal/logging/logging.go
+++ b/internal/logging/logging.go
@@ -78,3 +78,11 @@ func AccessLogger(handler http.Handler, format string) (http.Handler, error) {
log.WithAccessLogger(accessLogger),
), nil
}
+
+// LogRequest will inject request host and path to the logged messages
+func LogRequest(r *http.Request) *logrus.Entry {
+ return log.WithFields(log.Fields{
+ "host": r.Host,
+ "path": r.URL.Path,
+ })
+}
diff --git a/internal/mocks/mocks.go b/internal/mocks/mocks.go
new file mode 100644
index 00000000..2816205d
--- /dev/null
+++ b/internal/mocks/mocks.go
@@ -0,0 +1,128 @@
+// Code generated by MockGen. DO NOT EDIT.
+// Source: internal/interface.go
+
+// Package mocks is a generated GoMock package.
+package mocks
+
+import (
+ gomock "github.com/golang/mock/gomock"
+ http "net/http"
+ reflect "reflect"
+)
+
+// MockArtifact is a mock of Artifact interface
+type MockArtifact struct {
+ ctrl *gomock.Controller
+ recorder *MockArtifactMockRecorder
+}
+
+// MockArtifactMockRecorder is the mock recorder for MockArtifact
+type MockArtifactMockRecorder struct {
+ mock *MockArtifact
+}
+
+// NewMockArtifact creates a new mock instance
+func NewMockArtifact(ctrl *gomock.Controller) *MockArtifact {
+ mock := &MockArtifact{ctrl: ctrl}
+ mock.recorder = &MockArtifactMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use
+func (m *MockArtifact) EXPECT() *MockArtifactMockRecorder {
+ return m.recorder
+}
+
+// TryMakeRequest mocks base method
+func (m *MockArtifact) TryMakeRequest(host string, w http.ResponseWriter, r *http.Request, token string, responseHandler func(*http.Response) bool) bool {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "TryMakeRequest", host, w, r, token, responseHandler)
+ ret0, _ := ret[0].(bool)
+ return ret0
+}
+
+// TryMakeRequest indicates an expected call of TryMakeRequest
+func (mr *MockArtifactMockRecorder) TryMakeRequest(host, w, r, token, responseHandler interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "TryMakeRequest", reflect.TypeOf((*MockArtifact)(nil).TryMakeRequest), host, w, r, token, responseHandler)
+}
+
+// MockAuth is a mock of Auth interface
+type MockAuth struct {
+ ctrl *gomock.Controller
+ recorder *MockAuthMockRecorder
+}
+
+// MockAuthMockRecorder is the mock recorder for MockAuth
+type MockAuthMockRecorder struct {
+ mock *MockAuth
+}
+
+// NewMockAuth creates a new mock instance
+func NewMockAuth(ctrl *gomock.Controller) *MockAuth {
+ mock := &MockAuth{ctrl: ctrl}
+ mock.recorder = &MockAuthMockRecorder{mock}
+ return mock
+}
+
+// EXPECT returns an object that allows the caller to indicate expected use
+func (m *MockAuth) EXPECT() *MockAuthMockRecorder {
+ return m.recorder
+}
+
+// IsAuthSupported mocks base method
+func (m *MockAuth) IsAuthSupported() bool {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "IsAuthSupported")
+ ret0, _ := ret[0].(bool)
+ return ret0
+}
+
+// IsAuthSupported indicates an expected call of IsAuthSupported
+func (mr *MockAuthMockRecorder) IsAuthSupported() *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsAuthSupported", reflect.TypeOf((*MockAuth)(nil).IsAuthSupported))
+}
+
+// RequireAuth mocks base method
+func (m *MockAuth) RequireAuth(w http.ResponseWriter, r *http.Request) bool {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "RequireAuth", w, r)
+ ret0, _ := ret[0].(bool)
+ return ret0
+}
+
+// RequireAuth indicates an expected call of RequireAuth
+func (mr *MockAuthMockRecorder) RequireAuth(w, r interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RequireAuth", reflect.TypeOf((*MockAuth)(nil).RequireAuth), w, r)
+}
+
+// GetTokenIfExists mocks base method
+func (m *MockAuth) GetTokenIfExists(w http.ResponseWriter, r *http.Request) (string, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "GetTokenIfExists", w, r)
+ ret0, _ := ret[0].(string)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// GetTokenIfExists indicates an expected call of GetTokenIfExists
+func (mr *MockAuthMockRecorder) GetTokenIfExists(w, r interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetTokenIfExists", reflect.TypeOf((*MockAuth)(nil).GetTokenIfExists), w, r)
+}
+
+// CheckResponseForInvalidToken mocks base method
+func (m *MockAuth) CheckResponseForInvalidToken(w http.ResponseWriter, r *http.Request, resp *http.Response) bool {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "CheckResponseForInvalidToken", w, r, resp)
+ ret0, _ := ret[0].(bool)
+ return ret0
+}
+
+// CheckResponseForInvalidToken indicates an expected call of CheckResponseForInvalidToken
+func (mr *MockAuthMockRecorder) CheckResponseForInvalidToken(w, r, resp interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CheckResponseForInvalidToken", reflect.TypeOf((*MockAuth)(nil).CheckResponseForInvalidToken), w, r, resp)
+}