mirror of
https://github.com/openshift/image-registry.git
synced 2026-02-05 09:45:55 +01:00
the users api is specific to openshift, and is not available on every openshift cluster, i.e when OIDC is configured with external users.
114 lines
3.4 KiB
Go
114 lines
3.4 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
|
|
dcontext "github.com/distribution/distribution/v3/context"
|
|
"github.com/openshift/image-registry/pkg/dockerregistry/server/auth"
|
|
"github.com/openshift/image-registry/pkg/dockerregistry/server/client"
|
|
|
|
kerrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
)
|
|
|
|
type tokenHandler struct {
|
|
ctx context.Context
|
|
client client.RegistryClient
|
|
}
|
|
|
|
// NewTokenHandler returns a handler that implements the docker token protocol
|
|
func NewTokenHandler(ctx context.Context, client client.RegistryClient) http.Handler {
|
|
return &tokenHandler{
|
|
ctx: ctx,
|
|
client: client,
|
|
}
|
|
}
|
|
|
|
// bearer token issued to token requests that present no credentials
|
|
// recognized by the openshift auth provider as identifying the anonymous user
|
|
const anonymousToken = "anonymous"
|
|
|
|
func (t *tokenHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
ctx := dcontext.WithRequest(t.ctx, req)
|
|
|
|
params := req.URL.Query()
|
|
if len(params.Get("scope")) > 0 {
|
|
accessRecords := auth.ResolveScopeSpecifiers(ctx, params["scope"])
|
|
for _, access := range accessRecords {
|
|
switch access.Resource.Type {
|
|
case "repository", "signature":
|
|
_, _, err := getNamespaceName(access.Resource.Name)
|
|
if err != nil {
|
|
dcontext.GetRequestLogger(ctx).Errorf("auth token request for unsupported resource name: %s", access.Resource.Name)
|
|
t.writeError(w, req, err.Error())
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// If no authorization is provided, return a token the auth provider will treat as an anonymous user
|
|
if len(req.Header.Get("Authorization")) == 0 {
|
|
dcontext.GetRequestLogger(ctx).Debugf("anonymous token request")
|
|
t.writeToken(anonymousToken, w, req)
|
|
return
|
|
}
|
|
|
|
// use the password as the token
|
|
_, token, ok := req.BasicAuth()
|
|
if !ok {
|
|
dcontext.GetRequestLogger(ctx).Debugf("no basic auth credentials provided")
|
|
t.writeUnauthorized(w, req)
|
|
return
|
|
}
|
|
|
|
// TODO: if this doesn't validate as an API token, attempt to obtain an API token using the given username/password
|
|
osClient, err := t.client.ClientFromToken(token)
|
|
if err != nil {
|
|
dcontext.GetRequestLogger(ctx).Errorf("error building client: %v", err)
|
|
t.writeError(w, req, "invalid request")
|
|
return
|
|
}
|
|
|
|
if _, _, err := verifyOpenShiftUser(ctx, osClient); err != nil {
|
|
dcontext.GetRequestLogger(ctx).Errorf("invalid token: %v", err)
|
|
if kerrors.IsUnauthorized(err) {
|
|
t.writeUnauthorized(w, req)
|
|
} else {
|
|
msg := "unable to validate token"
|
|
if reason := kerrors.ReasonForError(err); reason != metav1.StatusReasonUnknown {
|
|
msg = fmt.Sprintf("%s: %s", msg, reason)
|
|
}
|
|
t.writeError(w, req, msg)
|
|
}
|
|
return
|
|
}
|
|
|
|
t.writeToken(token, w, req)
|
|
}
|
|
|
|
func (t *tokenHandler) writeError(w http.ResponseWriter, req *http.Request, msg string) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(401)
|
|
// TODO(dmage): log error?
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{"details": msg})
|
|
}
|
|
|
|
func (t *tokenHandler) writeToken(token string, w http.ResponseWriter, req *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(200)
|
|
// TODO(dmage): log error?
|
|
_ = json.NewEncoder(w).Encode(map[string]interface{}{
|
|
"token": token,
|
|
"access_token": token,
|
|
})
|
|
}
|
|
|
|
func (t *tokenHandler) writeUnauthorized(w http.ResponseWriter, req *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(401)
|
|
}
|