91 lines
2.7 KiB
Go
91 lines
2.7 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"net"
|
|
"net/http"
|
|
|
|
"github.com/go-chi/chi/v5/middleware"
|
|
)
|
|
|
|
// Action identifies the operation being performed.
|
|
// Format: "resource:verb"
|
|
type Action string
|
|
|
|
const (
|
|
ActionRepoList Action = "repos:list"
|
|
ActionRepoCreate Action = "repos:create"
|
|
ActionRepoRead Action = "repos:read"
|
|
ActionRepoUpdate Action = "repos:update"
|
|
ActionRepoDelete Action = "repos:delete"
|
|
|
|
ActionCloneStart Action = "clone:start"
|
|
ActionCloneRead Action = "clone:read"
|
|
|
|
ActionSyncTrigger Action = "sync:trigger"
|
|
ActionSyncRead Action = "sync:read"
|
|
ActionSyncApprove Action = "sync:approve"
|
|
ActionSyncReject Action = "sync:reject"
|
|
ActionSyncBlock Action = "sync:block"
|
|
|
|
ActionSnapshotCreate Action = "snapshots:create"
|
|
ActionSnapshotRead Action = "snapshots:read"
|
|
ActionSnapshotDelete Action = "snapshots:delete"
|
|
ActionSnapshotRollback Action = "snapshots:rollback"
|
|
)
|
|
|
|
var ErrForbidden = errors.New("forbidden")
|
|
|
|
// Caller holds the identity injected into the request context by LocalhostAuth.
|
|
type Caller struct {
|
|
IP string
|
|
RequestID string
|
|
LocalhostPrivileged bool
|
|
}
|
|
|
|
type contextKey string
|
|
|
|
const callerKey contextKey = "caller"
|
|
|
|
// CallerFromContext returns the Caller injected by LocalhostAuth, or nil.
|
|
func CallerFromContext(ctx context.Context) *Caller {
|
|
c, _ := ctx.Value(callerKey).(*Caller)
|
|
return c
|
|
}
|
|
|
|
// Authorize checks whether the caller in ctx is allowed to perform action on repoID.
|
|
// repoID == 0 means the action is not scoped to a specific repo (e.g. list, create).
|
|
//
|
|
// If localhost_privileged is enabled, 127.0.0.1/::1 are always allowed without a token.
|
|
// Other callers are currently allowed too; token enforcement will be added with the web UI.
|
|
func Authorize(ctx context.Context, action Action, repoID int64) error {
|
|
caller := CallerFromContext(ctx)
|
|
if caller == nil {
|
|
return ErrForbidden
|
|
}
|
|
if caller.LocalhostPrivileged && (caller.IP == "127.0.0.1" || caller.IP == "::1") {
|
|
return nil
|
|
}
|
|
// Future: look up token permissions for non-localhost callers.
|
|
return nil
|
|
}
|
|
|
|
// LocalhostAuth injects a Caller into the context for downstream Authorize calls.
|
|
// localhostPrivileged controls whether 127.0.0.1/::1 bypass future token enforcement.
|
|
func LocalhostAuth(localhostPrivileged bool) func(http.Handler) http.Handler {
|
|
return func(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
ip, _, err := net.SplitHostPort(r.RemoteAddr)
|
|
if err != nil {
|
|
ip = r.RemoteAddr
|
|
}
|
|
caller := &Caller{
|
|
IP: ip,
|
|
RequestID: middleware.GetReqID(r.Context()),
|
|
LocalhostPrivileged: localhostPrivileged,
|
|
}
|
|
next.ServeHTTP(w, r.WithContext(context.WithValue(r.Context(), callerKey, caller)))
|
|
})
|
|
}
|
|
}
|