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:
authorTuomo Ala-Vannesluoma <tuomoav@gmail.com>2019-09-26 16:35:28 +0300
committerNick Thomas <nick@gitlab.com>2019-09-26 16:35:28 +0300
commitc781a7ccd3147f750faeda631db15d06de455949 (patch)
treec2d190d278bb270eed5b2ecae0ba9e77f84e1577
parent218376d484a8ec55882b037475ec3201d1c897cf (diff)
Add support for previewing artifacts that are not public
Remove some duplicate logic on Auth module Separate handling artifact to own handlers package Unit test handlers by mocking auth and artifact modules Add generate-mock step to Makefile Use additional handler func to simplify TryMakeRequest return type Always try with token if exists Do not log RequestURI, log path only Remove not used logRequest func
-rw-r--r--.gitlab-ci.yml1
-rw-r--r--Makefile2
-rw-r--r--Makefile.build.mk6
-rw-r--r--acceptance_test.go235
-rw-r--r--app.go8
-rw-r--r--go.mod1
-rw-r--r--go.sum2
-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
16 files changed, 854 insertions, 104 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/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/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 9be4409c..e65d7510 100644
--- a/app.go
+++ b/app.go
@@ -24,6 +24,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"
@@ -51,6 +52,7 @@ type theApp struct {
lock sync.RWMutex
Artifact *artifact.Artifact
Auth *auth.Auth
+ Handlers *handlers.Handlers
AcmeMiddleware *acme.Middleware
CustomHeaders http.Header
}
@@ -145,9 +147,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
}
@@ -488,6 +488,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..b4a09ad6 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=
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 77bc7d8e..5b85a44e 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 8be5e835..6ab5739f 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"
@@ -333,3 +335,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 f0682573..9269261d 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)
+}