From 3cc7bc556307adbc8fac6942240bafceb0ff5d5d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 26 Sep 2019 14:14:51 +0200 Subject: Unify how we handle custom and group domain in serving --- internal/domain/domain.go | 138 ++++++++++++++++++++++++---------------------- 1 file changed, 73 insertions(+), 65 deletions(-) (limited to 'internal/domain/domain.go') diff --git a/internal/domain/domain.go b/internal/domain/domain.go index 361c3b87..1dd5be51 100644 --- a/internal/domain/domain.go +++ b/internal/domain/domain.go @@ -5,31 +5,23 @@ import ( "errors" "net/http" "sync" - "time" "gitlab.com/gitlab-org/gitlab-pages/internal/httperrors" "gitlab.com/gitlab-org/gitlab-pages/internal/serving" ) -// GroupConfig represents a per-request config for a group domain -type GroupConfig interface { - IsHTTPSOnly(*http.Request) bool - HasAccessControl(*http.Request) bool - IsNamespaceProject(*http.Request) bool - ProjectID(*http.Request) uint64 - ProjectExists(*http.Request) bool - ProjectWithSubpath(*http.Request) (string, string, error) -} - // Domain is a domain that gitlab-pages can serve. type Domain struct { - Group string - Project string + Name string + Location string + CertificateCert string + CertificateKey string + Customized bool // TODO we should get rid of this - GroupConfig GroupConfig // handles group domain config - ProjectConfig *ProjectConfig + Resolver Resolver - serving serving.Serving + lookupPaths map[string]*Project + serving serving.Serving certificate *tls.Certificate certificateError error @@ -38,15 +30,7 @@ type Domain struct { // String implements Stringer. func (d *Domain) String() string { - if d.Group != "" && d.Project != "" { - return d.Group + "/" + d.Project - } - - if d.Group != "" { - return d.Group - } - - return d.Project + return d.Name } func (d *Domain) isCustomDomain() bool { @@ -54,7 +38,7 @@ func (d *Domain) isCustomDomain() bool { panic("project config and group config should not be nil at the same time") } - return d.ProjectConfig != nil && d.GroupConfig == nil + return d.Customized } func (d *Domain) isUnconfigured() bool { @@ -62,22 +46,52 @@ func (d *Domain) isUnconfigured() bool { return true } - return d.ProjectConfig == nil && d.GroupConfig == nil + return d.Resolver == nil +} + +func (d *Domain) resolve(r *http.Request) (*Project, string) { + // TODO use lookupPaths first + + project, subpath, _ := d.Resolver.Resolve(r) + // current implementation does not return errors in any case + + if project == nil { + return nil, "" + } + + return project, subpath +} + +func (d *Domain) getProject(r *http.Request) *Project { + project, _ := d.resolve(r) + + return project } // Serving returns domain serving driver func (d *Domain) Serving() serving.Serving { if d.serving == nil { - if d.isCustomDomain() { - d.serving = serving.NewProjectDiskServing(d.Project, d.Group) - } else { - d.serving = serving.NewGroupDiskServing(d.Group, d.GroupConfig) - } + d.serving = serving.NewDiskServing(d.Name, d.Location) } return d.serving } +func (d *Domain) toHandler(w http.ResponseWriter, r *http.Request) *handler { + project, subpath := d.resolve(r) + + return &handler{ + writer: w, + request: r, + project: project, + subpath: subpath, + } +} + +func (d *Domain) hasProject(r *http.Request) bool { + return d.getProject(r) != nil +} + // IsHTTPSOnly figures out if the request should be handled with HTTPS // only by looking at group and project level config. func (d *Domain) IsHTTPSOnly(r *http.Request) bool { @@ -85,13 +99,11 @@ func (d *Domain) IsHTTPSOnly(r *http.Request) bool { return false } - // Check custom domain config (e.g. http://example.com) - if d.isCustomDomain() { - return d.ProjectConfig.HTTPSOnly + if project := d.getProject(r); project != nil { + return project.IsHTTPSOnly } - // Check projects served under the group domain, including the default one - return d.GroupConfig.IsHTTPSOnly(r) + return false } // IsAccessControlEnabled figures out if the request is to a project that has access control enabled @@ -100,22 +112,20 @@ func (d *Domain) IsAccessControlEnabled(r *http.Request) bool { return false } - // Check custom domain config (e.g. http://example.com) - if d.isCustomDomain() { - return d.ProjectConfig.AccessControl + if project := d.getProject(r); project != nil { + return project.HasAccessControl } - // Check projects served under the group domain, including the default one - return d.GroupConfig.HasAccessControl(r) + return false } // HasAcmeChallenge checks domain directory contains particular acme challenge -func (d *Domain) HasAcmeChallenge(token string) bool { - if d.isUnconfigured() || !d.isCustomDomain() { +func (d *Domain) HasAcmeChallenge(r *http.Request, token string) bool { + if d.isUnconfigured() || !d.isCustomDomain() || !d.hasProject(r) { return false } - return d.Serving().HasAcmeChallenge(token) + return d.Serving().HasAcmeChallenge(d.toHandler(nil, r), token) // TODO } // IsNamespaceProject figures out if the request is to a namespace project @@ -126,12 +136,15 @@ func (d *Domain) IsNamespaceProject(r *http.Request) bool { // 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.isCustomDomain() { + if d.isCustomDomain() { // TODO do we need a separate path for this return false } - // Check projects served under the group domain, including the default one - return d.GroupConfig.IsNamespaceProject(r) + if project := d.getProject(r); project != nil { + return project.IsNamespaceProject + } + + return false } // GetID figures out what is the ID of the project user tries to access @@ -140,11 +153,11 @@ func (d *Domain) GetID(r *http.Request) uint64 { return 0 } - if d.isCustomDomain() { - return d.ProjectConfig.ProjectID + if project := d.getProject(r); project != nil { + return project.ID } - return d.GroupConfig.ProjectID(r) + return 0 } // HasProject figures out if the project exists that the user tries to access @@ -153,15 +166,16 @@ func (d *Domain) HasProject(r *http.Request) bool { return false } - if d.isCustomDomain() { + if project := d.getProject(r); project != nil { return true } - return d.GroupConfig.ProjectExists(r) + return false } // EnsureCertificate parses the PEM-encoded certificate for the domain func (d *Domain) EnsureCertificate() (*tls.Certificate, error) { + // TODO check len certificates instead of custom domain! if d.isUnconfigured() || !d.isCustomDomain() { return nil, errors.New("tls certificates can be loaded only for pages with configuration") } @@ -169,8 +183,8 @@ func (d *Domain) EnsureCertificate() (*tls.Certificate, error) { d.certificateOnce.Do(func() { var cert tls.Certificate cert, d.certificateError = tls.X509KeyPair( - []byte(d.ProjectConfig.Certificate), - []byte(d.ProjectConfig.Key), + []byte(d.CertificateCert), + []byte(d.CertificateKey), ) if d.certificateError == nil { d.certificate = &cert @@ -182,26 +196,20 @@ func (d *Domain) EnsureCertificate() (*tls.Certificate, error) { // ServeFileHTTP returns true if something was served, false if not. func (d *Domain) ServeFileHTTP(w http.ResponseWriter, r *http.Request) bool { - if d.isUnconfigured() { + if d.isUnconfigured() || !d.hasProject(r) { httperrors.Serve404(w) return true } - if !d.IsAccessControlEnabled(r) { - // Set caching headers - w.Header().Set("Cache-Control", "max-age=600") - w.Header().Set("Expires", time.Now().Add(10*time.Minute).Format(time.RFC1123)) - } - - return d.Serving().ServeFileHTTP(w, r) + return d.Serving().ServeFileHTTP(d.toHandler(w, r)) } // ServeNotFoundHTTP serves the not found pages from the projects. func (d *Domain) ServeNotFoundHTTP(w http.ResponseWriter, r *http.Request) { - if d.isUnconfigured() { + if d.isUnconfigured() || !d.hasProject(r) { httperrors.Serve404(w) return } - d.Serving().ServeNotFoundHTTP(w, r) + d.Serving().ServeNotFoundHTTP(d.toHandler(w, r)) } -- cgit v1.2.3