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:
authorKassio Borges <kborges@gitlab.com>2023-04-04 12:32:18 +0300
committerVladimir Shushlin <vshushlin@gitlab.com>2023-04-04 12:32:18 +0300
commit7946cb0dc92ac6a306b55af1ec3c17dc936d742c (patch)
tree006e691eb52cb4e33987e11e80026ae17646ebd9
parent7817434f90966c6cd5dbee8fd1894c039eddc8af (diff)
Redirect to unique domain
-rw-r--r--app.go2
-rw-r--r--internal/serving/lookup_path.go1
-rw-r--r--internal/source/gitlab/api/lookup_path.go1
-rw-r--r--internal/source/gitlab/factory.go1
-rw-r--r--internal/uniqueDomain/middleware.go74
-rw-r--r--test/acceptance/uniqueDomain_test.go97
-rw-r--r--test/gitlabstub/api_responses.go87
-rw-r--r--test/gitlabstub/handlers.go2
8 files changed, 258 insertions, 7 deletions
diff --git a/app.go b/app.go
index f35df26c..96aa084b 100644
--- a/app.go
+++ b/app.go
@@ -40,6 +40,7 @@ import (
"gitlab.com/gitlab-org/gitlab-pages/internal/source"
"gitlab.com/gitlab-org/gitlab-pages/internal/source/gitlab"
"gitlab.com/gitlab-org/gitlab-pages/internal/tls"
+ "gitlab.com/gitlab-org/gitlab-pages/internal/uniqueDomain"
"gitlab.com/gitlab-org/gitlab-pages/internal/urilimiter"
"gitlab.com/gitlab-org/gitlab-pages/metrics"
)
@@ -133,6 +134,7 @@ func setRequestScheme(r *http.Request) *http.Request {
func (a *theApp) buildHandlerPipeline() (http.Handler, error) {
// Handlers should be applied in a reverse order
handler := a.serveFileOrNotFoundHandler()
+ handler = uniqueDomain.NewMiddleware(handler)
handler = a.Auth.AuthorizationMiddleware(handler)
handler = routing.NewMiddleware(handler, a.source)
diff --git a/internal/serving/lookup_path.go b/internal/serving/lookup_path.go
index 421e2a51..6a283b6e 100644
--- a/internal/serving/lookup_path.go
+++ b/internal/serving/lookup_path.go
@@ -10,4 +10,5 @@ type LookupPath struct {
IsHTTPSOnly bool
HasAccessControl bool
ProjectID uint64
+ UniqueHost string
}
diff --git a/internal/source/gitlab/api/lookup_path.go b/internal/source/gitlab/api/lookup_path.go
index 834767bd..a61d2f7c 100644
--- a/internal/source/gitlab/api/lookup_path.go
+++ b/internal/source/gitlab/api/lookup_path.go
@@ -7,6 +7,7 @@ type LookupPath struct {
HTTPSOnly bool `json:"https_only,omitempty"`
Prefix string `json:"prefix,omitempty"`
Source Source `json:"source,omitempty"`
+ UniqueHost string `json:"unique_host,omitempty"`
}
// Source describes GitLab Page serving variant
diff --git a/internal/source/gitlab/factory.go b/internal/source/gitlab/factory.go
index 312d09bf..1c5d15f5 100644
--- a/internal/source/gitlab/factory.go
+++ b/internal/source/gitlab/factory.go
@@ -31,6 +31,7 @@ func fabricateLookupPath(size int, lookup api.LookupPath) *serving.LookupPath {
IsHTTPSOnly: lookup.HTTPSOnly,
HasAccessControl: lookup.AccessControl,
ProjectID: uint64(lookup.ProjectID),
+ UniqueHost: lookup.UniqueHost,
}
}
diff --git a/internal/uniqueDomain/middleware.go b/internal/uniqueDomain/middleware.go
new file mode 100644
index 00000000..b0af761a
--- /dev/null
+++ b/internal/uniqueDomain/middleware.go
@@ -0,0 +1,74 @@
+package uniqueDomain
+
+import (
+ "net"
+ "net/http"
+ "strings"
+
+ "gitlab.com/gitlab-org/gitlab-pages/internal/domain"
+ "gitlab.com/gitlab-org/gitlab-pages/internal/logging"
+)
+
+func NewMiddleware(handler http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ uniqueURL := getUniqueURL(r)
+ if uniqueURL == "" {
+ logging.
+ LogRequest(r).
+ WithField("uniqueURL", uniqueURL).
+ Debug("unique domain: doing nothing")
+
+ handler.ServeHTTP(w, r)
+ return
+ }
+
+ logging.
+ LogRequest(r).
+ WithField("uniqueURL", uniqueURL).
+ Info("redirecting to unique domain")
+
+ http.Redirect(w, r, uniqueURL, http.StatusPermanentRedirect)
+ })
+}
+
+func getUniqueURL(r *http.Request) string {
+ domain := domain.FromRequest(r)
+ lookupPath, err := domain.GetLookupPath(r)
+ if err != nil {
+ logging.
+ LogRequest(r).
+ WithError(err).
+ Error("uniqueDomain: failed to get lookupPath")
+ return ""
+ }
+
+ // No uniqueHost to redirect
+ if lookupPath.UniqueHost == "" {
+ return ""
+ }
+
+ requestHost, port, err := net.SplitHostPort(r.Host)
+ if err != nil {
+ requestHost = r.Host
+ }
+
+ // Already serving the uniqueHost
+ if lookupPath.UniqueHost == requestHost {
+ return ""
+ }
+
+ uniqueURL := *r.URL
+ if port == "" {
+ uniqueURL.Host = lookupPath.UniqueHost
+ } else {
+ uniqueURL.Host = net.JoinHostPort(lookupPath.UniqueHost, port)
+ }
+
+ // Ensure to redirect to the same path requested
+ uniqueURL.Path = strings.TrimPrefix(
+ r.URL.Path,
+ strings.TrimSuffix(lookupPath.Prefix, "/"),
+ )
+
+ return uniqueURL.String()
+}
diff --git a/test/acceptance/uniqueDomain_test.go b/test/acceptance/uniqueDomain_test.go
new file mode 100644
index 00000000..1f423170
--- /dev/null
+++ b/test/acceptance/uniqueDomain_test.go
@@ -0,0 +1,97 @@
+package acceptance_test
+
+import (
+ "net/http"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ "gitlab.com/gitlab-org/gitlab-pages/internal/testhelpers"
+)
+
+func TestRedirectToUniqueDomain(t *testing.T) {
+ RunPagesProcess(t)
+
+ tests := []struct {
+ name string
+ requestDomain string
+ requestPath string
+ redirectURL string
+ httpStatus int
+ }{
+ {
+ name: "when project has unique domain",
+ requestDomain: "group.unique-url.gitlab-example.com",
+ requestPath: "with-unique-url/",
+ redirectURL: "https://unique-url-group-unique-url-a1b2c3d4e5f6.gitlab-example.com/",
+ httpStatus: http.StatusPermanentRedirect,
+ },
+ {
+ name: "when requesting implicit index.html",
+ requestDomain: "group.unique-url.gitlab-example.com",
+ requestPath: "with-unique-url",
+ redirectURL: "https://unique-url-group-unique-url-a1b2c3d4e5f6.gitlab-example.com",
+ httpStatus: http.StatusPermanentRedirect,
+ },
+ {
+ name: "when project is nested",
+ requestDomain: "group.unique-url.gitlab-example.com",
+ requestPath: "subgroup1/subgroup2/with-unique-url/subdir/index.html",
+ redirectURL: "https://unique-url-group-unique-url-a1b2c3d4e5f6.gitlab-example.com/subdir/index.html",
+ httpStatus: http.StatusPermanentRedirect,
+ },
+ {
+ name: "when serving with a port",
+ requestDomain: "group.unique-url.gitlab-example.com:8080",
+ requestPath: "with-unique-url/",
+ redirectURL: "https://unique-url-group-unique-url-a1b2c3d4e5f6.gitlab-example.com:8080/",
+ httpStatus: http.StatusPermanentRedirect,
+ },
+ {
+ name: "when serving a path with a port",
+ requestDomain: "group.unique-url.gitlab-example.com:8080",
+ requestPath: "with-unique-url/subdir/index.html",
+ redirectURL: "https://unique-url-group-unique-url-a1b2c3d4e5f6.gitlab-example.com:8080/subdir/index.html",
+ httpStatus: http.StatusPermanentRedirect,
+ },
+ {
+ name: "when already serving the unique domain",
+ requestDomain: "unique-url-group-unique-url-a1b2c3d4e5f6.gitlab-example.com",
+ httpStatus: http.StatusOK,
+ },
+ {
+ name: "when already serving the unique domain with a port",
+ requestDomain: "unique-url-group-unique-url-a1b2c3d4e5f6.gitlab-example.com:8080",
+ httpStatus: http.StatusOK,
+ },
+ {
+ name: "when already serving the unique domain with path",
+ requestDomain: "unique-url-group-unique-url-a1b2c3d4e5f6.gitlab-example.com",
+ requestPath: "subdir/index.html",
+ httpStatus: http.StatusOK,
+ },
+ {
+ name: "when project does not have unique domain with a path",
+ requestDomain: "group.unique-url.gitlab-example.com",
+ requestPath: "without-unique-url/subdir/index.html",
+ httpStatus: http.StatusOK,
+ },
+ {
+ name: "when project does not exist",
+ requestDomain: "group.unique-url.gitlab-example.com",
+ requestPath: "inexisting-project/",
+ httpStatus: http.StatusNotFound,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.name, func(t *testing.T) {
+ rsp, err := GetRedirectPage(t, httpsListener, test.requestDomain, test.requestPath)
+ require.NoError(t, err)
+ testhelpers.Close(t, rsp.Body)
+
+ require.Equal(t, test.httpStatus, rsp.StatusCode)
+ require.Equal(t, test.redirectURL, rsp.Header.Get("Location"))
+ })
+ }
+}
diff --git a/test/gitlabstub/api_responses.go b/test/gitlabstub/api_responses.go
index 93bc8995..b9459160 100644
--- a/test/gitlabstub/api_responses.go
+++ b/test/gitlabstub/api_responses.go
@@ -14,6 +14,15 @@ import (
type responseFn func(string) api.VirtualDomain
+type projectConfig struct {
+ // refer to makeGitLabPagesAccessStub for custom HTTP responses per projectID
+ projectID int
+ accessControl bool
+ https bool
+ pathOnDisk string
+ uniqueHost string
+}
+
// domainResponses holds the predefined API responses for certain domains
// that can be used with the GitLab API stub in acceptance tests
// Assume the working dir is inside shared/pages/
@@ -114,6 +123,43 @@ var domainResponses = map[string]responseFn{
projectID: 1008,
pathOnDisk: "group.acme/with.redirects",
}),
+ "group.unique-url.gitlab-example.com": generateVirtualDomain(map[string]projectConfig{
+ "/with-unique-url": {
+ uniqueHost: "unique-url-group-unique-url-a1b2c3d4e5f6.gitlab-example.com",
+ pathOnDisk: "group/project",
+ },
+ "/subgroup1/subgroup2/with-unique-url": {
+ uniqueHost: "unique-url-group-unique-url-a1b2c3d4e5f6.gitlab-example.com",
+ pathOnDisk: "group/project",
+ },
+ "/with-unique-url-with-port": {
+ uniqueHost: "unique-url-group-unique-url-a1b2c3d4e5f6.gitlab-example.com",
+ pathOnDisk: "group/project",
+ },
+ "/with-malformed-unique-url": {
+ uniqueHost: "unique-url@gitlab-example.com:",
+ pathOnDisk: "group/project",
+ },
+ "/with-different-protocol": {
+ uniqueHost: "unique-url-group-unique-url-a1b2c3d4e5f6.gitlab-example.com",
+ pathOnDisk: "group/project",
+ },
+ "/without-unique-url": {
+ pathOnDisk: "group/project",
+ },
+ }),
+ "unique-url-group-unique-url-a1b2c3d4e5f6.gitlab-example.com": generateVirtualDomain(map[string]projectConfig{
+ "/": {
+ uniqueHost: "unique-url-group-unique-url-a1b2c3d4e5f6.gitlab-example.com",
+ pathOnDisk: "group/project",
+ },
+ }),
+ "unique-url-group-unique-url-a1b2c3d4e5f6.gitlab-example.com:8080": generateVirtualDomain(map[string]projectConfig{
+ "/": {
+ uniqueHost: "unique-url-group-unique-url-a1b2c3d4e5f6.gitlab-example.com",
+ pathOnDisk: "group/project",
+ },
+ }),
// NOTE: before adding more domains here, generate the zip archive by running (per project)
// make zip PROJECT_SUBDIR=group/serving
// make zip PROJECT_SUBDIR=group/project2
@@ -187,6 +233,7 @@ func generateVirtualDomainFromDir(dir, rootDomain string, perPrefixConfig map[st
Path: sourcePath,
SHA256: sha,
},
+ UniqueHost: cfg.uniqueHost,
}
lookupPaths = append(lookupPaths, lookupPath)
@@ -198,12 +245,39 @@ func generateVirtualDomainFromDir(dir, rootDomain string, perPrefixConfig map[st
}
}
-type projectConfig struct {
- // refer to makeGitLabPagesAccessStub for custom HTTP responses per projectID
- projectID int
- accessControl bool
- https bool
- pathOnDisk string
+func generateVirtualDomain(projectConfigs map[string]projectConfig) responseFn {
+ return func(wd string) api.VirtualDomain {
+ nextID := 1000
+ lookupPaths := make([]api.LookupPath, 0, len(projectConfigs))
+
+ for project, config := range projectConfigs {
+ if config.projectID == 0 {
+ config.projectID = nextID
+ nextID++
+ }
+
+ sourcePath := fmt.Sprintf("file://%s/%s/public.zip", wd, config.pathOnDisk)
+ sum := sha256.Sum256([]byte(sourcePath))
+ sha := hex.EncodeToString(sum[:])
+
+ lookupPaths = append(lookupPaths, api.LookupPath{
+ ProjectID: config.projectID,
+ AccessControl: config.accessControl,
+ HTTPSOnly: config.https,
+ Prefix: ensureEndingSlash(project),
+ UniqueHost: config.uniqueHost,
+ Source: api.Source{
+ Type: "zip",
+ Path: sourcePath,
+ SHA256: sha,
+ },
+ })
+ }
+
+ return api.VirtualDomain{
+ LookupPaths: lookupPaths,
+ }
+ }
}
// customDomain with per project config
@@ -230,6 +304,7 @@ func customDomain(config projectConfig) responseFn {
SHA256: sha,
Path: sourcePath,
},
+ UniqueHost: config.uniqueHost,
},
},
}
diff --git a/test/gitlabstub/handlers.go b/test/gitlabstub/handlers.go
index df09d914..8089277b 100644
--- a/test/gitlabstub/handlers.go
+++ b/test/gitlabstub/handlers.go
@@ -29,7 +29,7 @@ func defaultAPIHandler(delay time.Duration, pagesRoot string) http.HandlerFunc {
// check if predefined response exists
if responseFn, ok := domainResponses[domain]; ok {
if err := json.NewEncoder(w).Encode(responseFn(pagesRoot)); err != nil {
- log.Fatal(err)
+ log.Fatalf("fail to encode response for domain %q: %v", domain, err)
}
return
}