diff options
author | Tuomo Ala-Vannesluoma <tuomoav@gmail.com> | 2018-09-22 14:52:47 +0300 |
---|---|---|
committer | Tuomo Ala-Vannesluoma <tuomoav@gmail.com> | 2018-09-22 14:52:47 +0300 |
commit | 0cb2e9714fc66486b8deaeeb06c60ae7b701698f (patch) | |
tree | db18b677b5bc60582e9ad054e15c270519883eb8 /internal/domain | |
parent | 2c766aba4f008c4a80e328a4eabbaae186276fc9 (diff) |
Add special handling for namespace projects to avoid existence leak
Diffstat (limited to 'internal/domain')
-rw-r--r-- | internal/domain/domain.go | 72 | ||||
-rw-r--r-- | internal/domain/domain_test.go | 86 | ||||
-rw-r--r-- | internal/domain/map.go | 7 |
3 files changed, 110 insertions, 55 deletions
diff --git a/internal/domain/domain.go b/internal/domain/domain.go index d98a0b91..01ee1a46 100644 --- a/internal/domain/domain.go +++ b/internal/domain/domain.go @@ -24,9 +24,10 @@ type locationDirectoryError struct { } type project struct { - HTTPSOnly bool - AccessControl bool - ID uint64 + NamespaceProject bool + HTTPSOnly bool + AccessControl bool + ID uint64 } type projects map[string]*project @@ -154,6 +155,26 @@ func (d *D) IsAccessControlEnabled(r *http.Request) bool { return false } +// IsNamespaceProject figures out if the request is to a namespace project +func (d *D) IsNamespaceProject(r *http.Request) bool { + if d == nil { + return false + } + + // If request is to a custom domain, we do not handle it as a namespace project + // as there can't be multiple projects under the same custom domain + if d.config != nil { + return false + } + + // Check projects served under the group domain, including the default one + if project := d.getProject(r); project != nil { + return project.NamespaceProject + } + + return false +} + // GetID figures out what is the ID of the project user tries to access func (d *D) GetID(r *http.Request) uint64 { if d == nil { @@ -324,20 +345,27 @@ func (d *D) tryFile(w http.ResponseWriter, r *http.Request, projectName, pathSuf return d.serveFile(w, r, fullPath) } -func (d *D) serveFromGroup(w http.ResponseWriter, r *http.Request) { +func (d *D) serveFileFromGroup(w http.ResponseWriter, r *http.Request) bool { // The Path always contains "/" at the beginning split := strings.SplitN(r.URL.Path, "/", 3) // Try to serve file for http://group.example.com/subpath/... => /group/subpath/... if len(split) >= 2 && d.tryFile(w, r, split[1], split[1], split[2:]...) == nil { - return + return true } // Try to serve file for http://group.example.com/... => /group/group.example.com/... if r.Host != "" && d.tryFile(w, r, strings.ToLower(r.Host), "", r.URL.Path) == nil { - return + return true } + return false +} + +func (d *D) serveNotFoundFromGroup(w http.ResponseWriter, r *http.Request) { + // The Path always contains "/" at the beginning + split := strings.SplitN(r.URL.Path, "/", 3) + // Try serving not found page for http://group.example.com/subpath/ => /group/subpath/404.html if len(split) >= 2 && d.tryNotFound(w, r, split[1]) == nil { return @@ -352,12 +380,16 @@ func (d *D) serveFromGroup(w http.ResponseWriter, r *http.Request) { httperrors.Serve404(w) } -func (d *D) serveFromConfig(w http.ResponseWriter, r *http.Request) { +func (d *D) serveFileFromConfig(w http.ResponseWriter, r *http.Request) bool { // Try to serve file for http://host/... => /group/project/... if d.tryFile(w, r, d.projectName, "", r.URL.Path) == nil { - return + return true } + return false +} + +func (d *D) serveNotFoundFromConfig(w http.ResponseWriter, r *http.Request) { // Try serving not found page for http://host/ => /group/project/404.html if d.tryNotFound(w, r, d.projectName) == nil { return @@ -384,18 +416,32 @@ func (d *D) EnsureCertificate() (*tls.Certificate, error) { return d.certificate, d.certificateError } -// ServeHTTP implements http.Handler. -func (d *D) ServeHTTP(w http.ResponseWriter, r *http.Request) { +// ServeFileHTTP implements http.Handler. Returns true if something was served, false if not. +func (d *D) ServeFileHTTP(w http.ResponseWriter, r *http.Request) bool { + if d == nil { + httperrors.Serve404(w) + return true + } + + if d.config != nil { + return d.serveFileFromConfig(w, r) + } + + return d.serveFileFromGroup(w, r) +} + +// ServeNotFoundHTTP implements http.Handler. Serves the not found pages from the projects. +func (d *D) ServeNotFoundHTTP(w http.ResponseWriter, r *http.Request) { if d == nil { httperrors.Serve404(w) return } if d.config != nil { - d.serveFromConfig(w, r) - } else { - d.serveFromGroup(w, r) + d.serveNotFoundFromConfig(w, r) } + + d.serveNotFoundFromGroup(w, r) } func endsWithSlash(path string) bool { diff --git a/internal/domain/domain_test.go b/internal/domain/domain_test.go index 64d11a29..4c08aee5 100644 --- a/internal/domain/domain_test.go +++ b/internal/domain/domain_test.go @@ -17,6 +17,14 @@ import ( "gitlab.com/gitlab-org/gitlab-pages/internal/fixture" ) +func serveFileOrNotFound(domain *D) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + if !domain.ServeFileHTTP(w, r) { + domain.ServeNotFoundHTTP(w, r) + } + } +} + func TestGroupServeHTTP(t *testing.T) { setUpTests() @@ -25,27 +33,27 @@ func TestGroupServeHTTP(t *testing.T) { projectName: "", } - assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/", nil, "main-dir") - assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/index.html", nil, "main-dir") - assert.HTTPRedirect(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project", nil) - assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project", nil, + assert.HTTPBodyContains(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/", nil, "main-dir") + assert.HTTPBodyContains(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/index.html", nil, "main-dir") + assert.HTTPRedirect(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/project", nil) + assert.HTTPBodyContains(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/project", nil, `<a href="//group.test.io/project/">Found</a>`) - assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project/", nil, "project-subdir") - assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project/index.html", nil, "project-subdir") - assert.HTTPRedirect(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project/subdir", nil) - assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project/subdir", nil, + assert.HTTPBodyContains(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/project/", nil, "project-subdir") + assert.HTTPBodyContains(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/project/index.html", nil, "project-subdir") + assert.HTTPRedirect(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/project/subdir", nil) + assert.HTTPBodyContains(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/project/subdir", nil, `<a href="//group.test.io/project/subdir/">Found</a>`) - assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project/subdir/", nil, "project-subsubdir") - assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project2/", nil, "project2-main") - assert.HTTPBodyContains(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project2/index.html", nil, "project2-main") - assert.HTTPRedirect(t, testGroup.ServeHTTP, "GET", "http://group.test.io/private.project/", nil) - assert.HTTPError(t, testGroup.ServeHTTP, "GET", "http://group.test.io//about.gitlab.com/%2e%2e", nil) - assert.HTTPError(t, testGroup.ServeHTTP, "GET", "http://group.test.io/symlink", nil) - assert.HTTPError(t, testGroup.ServeHTTP, "GET", "http://group.test.io/symlink/index.html", nil) - assert.HTTPError(t, testGroup.ServeHTTP, "GET", "http://group.test.io/symlink/subdir/", nil) - assert.HTTPError(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project/fifo", nil) - assert.HTTPError(t, testGroup.ServeHTTP, "GET", "http://group.test.io/not-existing-file", nil) - assert.HTTPError(t, testGroup.ServeHTTP, "GET", "http://group.test.io/project//about.gitlab.com/%2e%2e", nil) + assert.HTTPBodyContains(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/project/subdir/", nil, "project-subsubdir") + assert.HTTPBodyContains(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/project2/", nil, "project2-main") + assert.HTTPBodyContains(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/project2/index.html", nil, "project2-main") + assert.HTTPRedirect(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/private.project/", nil) + assert.HTTPError(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io//about.gitlab.com/%2e%2e", nil) + assert.HTTPError(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/symlink", nil) + assert.HTTPError(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/symlink/index.html", nil) + assert.HTTPError(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/symlink/subdir/", nil) + assert.HTTPError(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/project/fifo", nil) + assert.HTTPError(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/not-existing-file", nil) + assert.HTTPError(t, serveFileOrNotFound(testGroup), "GET", "http://group.test.io/project//about.gitlab.com/%2e%2e", nil) } func TestDomainServeHTTP(t *testing.T) { @@ -59,15 +67,15 @@ func TestDomainServeHTTP(t *testing.T) { }, } - assert.HTTPBodyContains(t, testDomain.ServeHTTP, "GET", "/", nil, "project2-main") - assert.HTTPBodyContains(t, testDomain.ServeHTTP, "GET", "/index.html", nil, "project2-main") - assert.HTTPRedirect(t, testDomain.ServeHTTP, "GET", "/subdir", nil) - assert.HTTPBodyContains(t, testDomain.ServeHTTP, "GET", "/subdir", nil, + assert.HTTPBodyContains(t, serveFileOrNotFound(testDomain), "GET", "/", nil, "project2-main") + assert.HTTPBodyContains(t, serveFileOrNotFound(testDomain), "GET", "/index.html", nil, "project2-main") + assert.HTTPRedirect(t, serveFileOrNotFound(testDomain), "GET", "/subdir", nil) + assert.HTTPBodyContains(t, serveFileOrNotFound(testDomain), "GET", "/subdir", nil, `<a href="/subdir/">Found</a>`) - assert.HTTPBodyContains(t, testDomain.ServeHTTP, "GET", "/subdir/", nil, "project2-subdir") - assert.HTTPBodyContains(t, testDomain.ServeHTTP, "GET", "/subdir/index.html", nil, "project2-subdir") - assert.HTTPError(t, testDomain.ServeHTTP, "GET", "//about.gitlab.com/%2e%2e", nil) - assert.HTTPError(t, testDomain.ServeHTTP, "GET", "/not-existing-file", nil) + assert.HTTPBodyContains(t, serveFileOrNotFound(testDomain), "GET", "/subdir/", nil, "project2-subdir") + assert.HTTPBodyContains(t, serveFileOrNotFound(testDomain), "GET", "/subdir/index.html", nil, "project2-subdir") + assert.HTTPError(t, serveFileOrNotFound(testDomain), "GET", "//about.gitlab.com/%2e%2e", nil) + assert.HTTPError(t, serveFileOrNotFound(testDomain), "GET", "/not-existing-file", nil) } func TestIsHTTPSOnly(t *testing.T) { @@ -238,7 +246,7 @@ func TestGroupServeHTTPGzip(t *testing.T) { } for _, tt := range testSet { - testHTTPGzip(t, testGroup.ServeHTTP, tt.mode, tt.url, tt.params, tt.acceptEncoding, tt.body, tt.ungzip) + testHTTPGzip(t, serveFileOrNotFound(testGroup), tt.mode, tt.url, tt.params, tt.acceptEncoding, tt.body, tt.ungzip) } } @@ -262,13 +270,13 @@ func TestGroup404ServeHTTP(t *testing.T) { projectName: "", } - testHTTP404(t, testGroup.ServeHTTP, "GET", "http://group.404.test.io/project.404/not/existing-file", nil, "Custom 404 project page") - testHTTP404(t, testGroup.ServeHTTP, "GET", "http://group.404.test.io/project.404/", nil, "Custom 404 project page") - testHTTP404(t, testGroup.ServeHTTP, "GET", "http://group.404.test.io/project.no.404/not/existing-file", nil, "Custom 404 group page") - testHTTP404(t, testGroup.ServeHTTP, "GET", "http://group.404.test.io/not/existing-file", nil, "Custom 404 group page") - testHTTP404(t, testGroup.ServeHTTP, "GET", "http://group.404.test.io/not-existing-file", nil, "Custom 404 group page") - testHTTP404(t, testGroup.ServeHTTP, "GET", "http://group.404.test.io/", nil, "Custom 404 group page") - assert.HTTPBodyNotContains(t, testGroup.ServeHTTP, "GET", "http://group.404.test.io/project.404.symlink/not/existing-file", nil, "Custom 404 project page") + testHTTP404(t, serveFileOrNotFound(testGroup), "GET", "http://group.404.test.io/project.404/not/existing-file", nil, "Custom 404 project page") + testHTTP404(t, serveFileOrNotFound(testGroup), "GET", "http://group.404.test.io/project.404/", nil, "Custom 404 project page") + testHTTP404(t, serveFileOrNotFound(testGroup), "GET", "http://group.404.test.io/project.no.404/not/existing-file", nil, "Custom 404 group page") + testHTTP404(t, serveFileOrNotFound(testGroup), "GET", "http://group.404.test.io/not/existing-file", nil, "Custom 404 group page") + testHTTP404(t, serveFileOrNotFound(testGroup), "GET", "http://group.404.test.io/not-existing-file", nil, "Custom 404 group page") + testHTTP404(t, serveFileOrNotFound(testGroup), "GET", "http://group.404.test.io/", nil, "Custom 404 group page") + assert.HTTPBodyNotContains(t, serveFileOrNotFound(testGroup), "GET", "http://group.404.test.io/project.404.symlink/not/existing-file", nil, "Custom 404 project page") } func TestDomain404ServeHTTP(t *testing.T) { @@ -282,8 +290,8 @@ func TestDomain404ServeHTTP(t *testing.T) { }, } - testHTTP404(t, testDomain.ServeHTTP, "GET", "http://group.404.test.io/not-existing-file", nil, "Custom 404 group page") - testHTTP404(t, testDomain.ServeHTTP, "GET", "http://group.404.test.io/", nil, "Custom 404 group page") + testHTTP404(t, serveFileOrNotFound(testDomain), "GET", "http://group.404.test.io/not-existing-file", nil, "Custom 404 group page") + testHTTP404(t, serveFileOrNotFound(testDomain), "GET", "http://group.404.test.io/", nil, "Custom 404 group page") } func TestPredefined404ServeHTTP(t *testing.T) { @@ -293,7 +301,7 @@ func TestPredefined404ServeHTTP(t *testing.T) { group: "group", } - testHTTP404(t, testDomain.ServeHTTP, "GET", "http://group.test.io/not-existing-file", nil, "The page you're looking for could not be found") + testHTTP404(t, serveFileOrNotFound(testDomain), "GET", "http://group.test.io/not-existing-file", nil, "The page you're looking for could not be found") } func TestGroupCertificate(t *testing.T) { @@ -348,7 +356,7 @@ func TestCacheControlHeaders(t *testing.T) { require.NoError(t, err) now := time.Now() - testGroup.ServeHTTP(w, req) + serveFileOrNotFound(testGroup)(w, req) assert.Equal(t, http.StatusOK, w.Code) assert.Equal(t, "max-age=600", w.Header().Get("Cache-Control")) diff --git a/internal/domain/map.go b/internal/domain/map.go index b832ffb3..d2e7c74f 100644 --- a/internal/domain/map.go +++ b/internal/domain/map.go @@ -58,9 +58,10 @@ func (dm Map) updateGroupDomain(rootDomain, group, projectName string, httpsOnly } groupDomain.projects[strings.ToLower(projectName)] = &project{ - HTTPSOnly: httpsOnly, - AccessControl: accessControl, - ID: id, + NamespaceProject: domainName == strings.ToLower(projectName), + HTTPSOnly: httpsOnly, + AccessControl: accessControl, + ID: id, } dm[domainName] = groupDomain |