From 2c109a5e1e9f77e57d35906ed9fa8c0f48194153 Mon Sep 17 00:00:00 2001 From: Catherine Date: Wed, 22 Apr 2026 01:09:22 +0000 Subject: [PATCH] Factor out common authorization code. NFC This commit unifies most of the implementation of `AuthorizeDeletion` and `AuthorizeUpdateFromArchive`, with the latter additionally checking that the repository URL in the authorization grant follows the limits. This is done in preparation of adding a second forge authorization sub-mechanism that can handle non-wildcard domains. --- src/auth.go | 103 +++++++++++++++++++++++----------------------------- 1 file changed, 46 insertions(+), 57 deletions(-) diff --git a/src/auth.go b/src/auth.go index 58d60a4..06e5a68 100644 --- a/src/auth.go +++ b/src/auth.go @@ -463,21 +463,23 @@ func AuthorizeUpdateFromRepository(r *http.Request) (*Authorization, error) { return nil, joinErrors(causes...) } -func checkAllowedURLPrefix(repoURL string) error { +func checkAllowedURLPrefixes(repoURLs ...string) error { if len(config.Limits.AllowedRepositoryURLPrefixes) > 0 { - allowedPrefix := false - repoURL = strings.ToLower(repoURL) - for _, allowedRepoURLPrefix := range config.Limits.AllowedRepositoryURLPrefixes { - if strings.HasPrefix(repoURL, strings.ToLower(allowedRepoURLPrefix)) { - allowedPrefix = true - break + for _, repoURL := range repoURLs { + allowedPrefix := false + repoURL = strings.ToLower(repoURL) + for _, allowedRepoURLPrefix := range config.Limits.AllowedRepositoryURLPrefixes { + if strings.HasPrefix(repoURL, strings.ToLower(allowedRepoURLPrefix)) { + allowedPrefix = true + break + } } - } - if !allowedPrefix { - return AuthError{ - http.StatusUnauthorized, - fmt.Sprintf("clone URL not in prefix allowlist %v", - config.Limits.AllowedRepositoryURLPrefixes), + if !allowedPrefix { + return AuthError{ + http.StatusUnauthorized, + fmt.Sprintf("clone URL %v not in prefix allowlist %v", + repoURL, config.Limits.AllowedRepositoryURLPrefixes), + } } } } @@ -510,7 +512,7 @@ func AuthorizeRepository(repoURL string, auth *Authorization) error { return nil // any } - if err = checkAllowedURLPrefix(repoURL); err != nil { + if err = checkAllowedURLPrefixes(repoURL); err != nil { return err } @@ -730,48 +732,7 @@ func authorizeForgeWithToken(r *http.Request) (*Authorization, error) { return nil, joinErrors(errs...) } -func AuthorizeUpdateFromArchive(r *http.Request) (*Authorization, error) { - causes := []error{AuthError{http.StatusUnauthorized, "unauthorized"}} - - if err := CheckForbiddenDomain(r); err != nil { - return nil, err - } - - auth := authorizeInsecure(r) - if auth != nil { - return auth, nil - } - - // Token authorization allows updating a site on a wildcard domain from an archive. - auth, err := authorizeForgeWithToken(r) - if err != nil && IsUnauthorized(err) { - causes = append(causes, err) - } else if err != nil { // bad request - return nil, err - } else { - logc.Printf(r.Context(), "auth: forge token: allow\n") - return auth, nil - } - - if len(config.Limits.AllowedRepositoryURLPrefixes) > 0 { - causes = append(causes, AuthError{http.StatusUnauthorized, "DNS challenge not allowed"}) - } else { - // DNS challenge gives absolute authority. - auth, err = authorizeDNSChallenge(r) - if err != nil && IsUnauthorized(err) { - causes = append(causes, err) - } else if err != nil { // bad request - return nil, err - } else { - logc.Println(r.Context(), "auth: DNS challenge") - return auth, nil - } - } - - return nil, joinErrors(causes...) -} - -func AuthorizeDeletion(r *http.Request) (*Authorization, error) { +func authorizeDNSChallengeOrForgeWithToken(r *http.Request) (*Authorization, error) { causes := []error{AuthError{http.StatusUnauthorized, "unauthorized"}} if err := CheckForbiddenDomain(r); err != nil { @@ -783,16 +744,18 @@ func AuthorizeDeletion(r *http.Request) (*Authorization, error) { return auth, nil } + // DNS challenge gives absolute authority. auth, err := authorizeDNSChallenge(r) if err != nil && IsUnauthorized(err) { causes = append(causes, err) } else if err != nil { // bad request return nil, err } else { - logc.Printf(r.Context(), "auth: DNS challenge: allow *\n") + logc.Println(r.Context(), "auth: DNS challenge: allow *") return auth, nil } + // Token authorization allows updating a site on a wildcard domain from an archive. auth, err = authorizeForgeWithToken(r) if err != nil && IsUnauthorized(err) { causes = append(causes, err) @@ -806,6 +769,32 @@ func AuthorizeDeletion(r *http.Request) (*Authorization, error) { return nil, joinErrors(causes...) } +func AuthorizeUpdateFromArchive(r *http.Request) (*Authorization, error) { + auth, err := authorizeDNSChallengeOrForgeWithToken(r) + if err != nil { + return nil, err + } + + // If only uploads from specific repositories are allowed, then only forge authorization + // is acceptable, and the repository must match the configured limits. + if len(config.Limits.AllowedRepositoryURLPrefixes) > 0 { + if len(auth.repoURLs) == 0 { + logc.Println(r.Context(), "auth: DNS challenge: deny (limits)") + return nil, AuthError{http.StatusUnauthorized, "DNS challenge not allowed"} + } + + if err = checkAllowedURLPrefixes(auth.repoURLs...); err != nil { + return nil, err + } + } + + return auth, nil +} + +func AuthorizeDeletion(r *http.Request) (*Authorization, error) { + return authorizeDNSChallengeOrForgeWithToken(r) +} + func CheckForbiddenDomain(r *http.Request) error { host, err := GetHost(r) if err != nil {