mirror of
https://github.com/quay/quay.git
synced 2026-10-23 09:49:06 +03:00
Add a verifier abstraction behind BasicAuthenticator so user password auth and static robot token auth share credential verification without coupling robot-specific behavior to HTTP Basic parsing. Implement Python-compatible legacy robot token handling for RobotAccountToken rows, including DATABASE_SECRET_KEY conversion, encrypted-field AES-CCM decryption, robot allowlist enforcement, ASCII credential validation, robot and owner enabled checks, and last_accessed updates. Wire the database verifier into API and distribution authentication, then enforce pull, push, delete, and create-on-push authorization for user and robot principals, including public pulls, disabled namespace checks, repository state/kind checks, and superuser create behavior. Extend the Go migration E2E job to create a robot through old OMR's session and CSRF API flow, verify that token can push before migration, and verify the same migrated token can pull and push against the Go registry. Generate a migrated runtime config that preserves OMR secret keys while pointing Go at the migrated database and storage paths.
92 lines
2.9 KiB
Go
92 lines
2.9 KiB
Go
// Package auth provides shared authentication helpers for Go services.
|
|
package auth
|
|
|
|
import (
|
|
"database/sql"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
// PrincipalKind identifies the kind of authenticated identity.
|
|
type PrincipalKind string
|
|
|
|
const (
|
|
// PrincipalUser is a regular user account.
|
|
PrincipalUser PrincipalKind = "user"
|
|
// PrincipalRobot is a robot account.
|
|
PrincipalRobot PrincipalKind = "robot"
|
|
// PrincipalAnonymous is an unauthenticated caller.
|
|
PrincipalAnonymous PrincipalKind = "anonymous"
|
|
)
|
|
|
|
// Principal is the identity evaluated by API and registry authorization.
|
|
type Principal struct {
|
|
ID int64
|
|
UUID sql.NullString
|
|
Username string
|
|
Email string
|
|
Kind PrincipalKind
|
|
}
|
|
|
|
// AnonymousPrincipal returns the shared anonymous identity.
|
|
func AnonymousPrincipal() *Principal {
|
|
return &Principal{Username: "anonymous", Kind: PrincipalAnonymous}
|
|
}
|
|
|
|
// IsAnonymous reports whether p represents an unauthenticated caller.
|
|
func (p *Principal) IsAnonymous() bool {
|
|
return p == nil || p.Kind == PrincipalAnonymous
|
|
}
|
|
|
|
// Result describes the outcome of an authentication attempt.
|
|
type Result struct {
|
|
// Principal is populated only when authentication succeeds.
|
|
Principal Principal
|
|
// Username is populated when credentials were presented, even if they were
|
|
// invalid. This lets callers distinguish failed credentials from missing
|
|
// credentials without re-parsing the request.
|
|
Username string
|
|
// Presented reports whether the request included credentials for this
|
|
// authenticator. For Basic auth, this means an Authorization: Basic header
|
|
// was present; it does not imply the credentials were valid.
|
|
Presented bool
|
|
// Authenticated reports whether the presented credentials were valid.
|
|
Authenticated bool
|
|
}
|
|
|
|
// BasicAuthenticator adapts HTTP Basic credentials to a credential verifier.
|
|
type BasicAuthenticator struct {
|
|
verifier Verifier
|
|
}
|
|
|
|
// NewBasicAuthenticator creates a BasicAuthenticator backed by verifier.
|
|
func NewBasicAuthenticator(verifier Verifier) *BasicAuthenticator {
|
|
return &BasicAuthenticator{verifier: verifier}
|
|
}
|
|
|
|
// Authenticate extracts request Basic credentials and delegates verification.
|
|
//
|
|
// Result.Username is set when credentials were presented, even if they did not
|
|
// validate. Callers can distinguish missing credentials from failed credentials.
|
|
func (a *BasicAuthenticator) Authenticate(r *http.Request) Result {
|
|
basicPresented := hasBasicAuthorizationHeader(r.Header.Get("Authorization"))
|
|
username, password, presented := r.BasicAuth()
|
|
if !presented {
|
|
if basicPresented {
|
|
return Result{Presented: true}
|
|
}
|
|
return Result{}
|
|
}
|
|
|
|
if a == nil || a.verifier == nil {
|
|
return Result{Username: username, Presented: true}
|
|
}
|
|
|
|
return a.verifier.Verify(r.Context(), Credentials{Username: username, Secret: password})
|
|
}
|
|
|
|
func hasBasicAuthorizationHeader(authHeader string) bool {
|
|
fields := strings.Fields(authHeader)
|
|
return len(fields) > 0 && strings.EqualFold(fields[0], "Basic")
|
|
}
|