1
0
mirror of https://github.com/quay/quay.git synced 2026-10-23 09:49:06 +03:00
Files
quay/internal/auth/basic.go
jbpratt 019a463ea8 NO-ISSUE: feat(auth): add robot token authentication (#6392)
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.
2026-07-10 12:59:38 -05:00

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")
}