Documentation
¶
Overview ¶
web_ui/middleware.go
**************************************************************
* * Copyright (C) 2024, Pelican Project, Morgridge Institute for Research * * Licensed under the Apache License, Version 2.0 (the "License"); you * may not use this file except in compliance with the License. You may * obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * **************************************************************
Index ¶
- Constants
- Variables
- func AdminAuthHandler(ctx *gin.Context)
- func AuthHandler(ctx *gin.Context)
- func CaptureAuthMethod(ctx *gin.Context) (database.AuthMethod, string)
- func CheckAdmin(identity UserIdentity) (isAdmin bool, message string)
- func CheckCollectionAdmin(identity UserIdentity) (bool, string)
- func CheckUserAdmin(identity UserIdentity) (bool, string)
- func ConfigOAuthClientAPIs(engine *gin.Engine) error
- func ConfigureEmbeddedPrometheus(ctx context.Context, engine *gin.Engine, ...) error
- func ConfigureServerWebAPI(ctx context.Context, engine *gin.Engine, egrp *errgroup.Group) error
- func CurrentAUPVersion() (path string, version string, err error)
- func DowntimeAuthHandler(ctx *gin.Context)
- func EffectiveScopesForIdentity(identity UserIdentity) []token_scopes.TokenScope
- func GenerateCSRFCookie(ctx *gin.Context, metadata map[string]string) (string, error)
- func GenerateOAuthState(metadata map[string]string) string
- func GetEngine() (*gin.Engine, error)
- func GetSessionHandler() (gin.HandlerFunc, error)
- func GetUserGroups(ctx *gin.Context) (user string, userId string, groups []string, err error)
- func HandleCreateDowntime(ctx *gin.Context)
- func HandleDeleteDowntime(ctx *gin.Context)
- func HandleDeleteLogLevel(ctx *gin.Context)
- func HandleGetDowntime(ctx *gin.Context)
- func HandleGetDowntimeByUUID(ctx *gin.Context)
- func HandleGetFederationInfo(ctx *gin.Context)
- func HandleGetLogLevel(ctx *gin.Context)
- func HandleGetServerLocalMetadata(ctx *gin.Context)
- func HandleGetServerLocalMetadataHistory(ctx *gin.Context)
- func HandleSetLogLevel(ctx *gin.Context)
- func HandleUpdateDowntime(ctx *gin.Context)
- func InitServerWebLogin(ctx context.Context) error
- func IsSystemAdminUserID(db *gorm.DB, userID string) bool
- func ParseOAuthState(state string) (metadata map[string]string, err error)
- func ReadOnlyMiddleware(ctx *gin.Context)
- func RegisterAuthEndpoints(ctx context.Context, routerGroup *gin.RouterGroup, egrp *errgroup.Group) error
- func RequireAUPCompliance(ctx *gin.Context)
- func RequireAuthMiddleware(ctx *gin.Context)
- func RunEngine(ctx context.Context, engine *gin.Engine, egrp *errgroup.Group) error
- func RunEngineRoutine(ctx context.Context, engine *gin.Engine, egrp *errgroup.Group, curRoutine bool) error
- func RunEngineRoutineWithListener(ctx context.Context, engine *gin.Engine, egrp *errgroup.Group, curRoutine bool, ...) error
- func ServerHeaderMiddleware(ctx *gin.Context)
- func UserAdminAuthHandler(ctx *gin.Context)
- func WritePasswordEntry(user, password string) error
- type AUPDocumentResp
- type AUPSource
- type AddGroupMemberReq
- type AddUserIdentityReq
- type AddUserReq
- type CreateApiTokenReq
- type CreateGroupReq
- type CreateInviteLinkReq
- type DowntimeInput
- type GroupView
- type InitLogin
- type InviteLinkView
- type LogLevelChangeResponse
- type LogLevelStatusResponse
- type Login
- type LogrusAdapter
- type OIDCEnabledServerRes
- type ParameterLevelStatus
- type PasswordReset
- type ReadyHandler
- type RecordAUPAgreementReq
- type RedeemInviteLinkReq
- type SetLogLevelRequest
- type UpdateGroupOwnershipReq
- type UpdateGroupReq
- type UpdateMeReq
- type UpdateMyPasswordReq
- type UpdateUserReq
- type UpdateUserStatusReq
- type UserIdentity
- type UserRole
- type WhoAmIRes
Constants ¶
const ( GroupSourceTypeOIDC string = "oidc" GroupSourceTypeFile string = "file" GroupSourceTypeInternal string = "internal" GroupSourceTypeGitHub string = "github" )
Group source types
const ( MaxLabelLimit = 128 MaxLabelNameLengthLimit = 256 MaxLabelValueLengthLimit = 4096 MaxSampleLimit = 800 )
Variables ¶
var ErrNotReady = errors.New("Scrape manager not ready")
ErrNotReady is returned if the underlying scrape manager is not ready yet.
Functions ¶
func AdminAuthHandler ¶
AdminAuthHandler checks the admin status of a logged-in user. This middleware should be cascaded behind the web_ui.AuthHandler
func AuthHandler ¶
Check if user is authenticated by checking if the "login" cookie is present and set the user identity to ctx
func CaptureAuthMethod ¶
func CaptureAuthMethod(ctx *gin.Context) (database.AuthMethod, string)
captureAuthMethod inspects the request and reports how the calling user was authenticated. This is intended for *audit fields* on records the handler is about to create (e.g. invite links, API token activity logs); it is not an authorization decision.
Returns ("", "") if no recognised credential is present — the caller should still be running behind AuthHandler, so this only happens when we're called outside an authenticated route or with a malformed request.
API token IDs are the 5-character prefix of the `<id>.<secret>` token format (see api_token.ApiTokenRegex). Web-cookie sessions don't have a stable session id today, so AuthMethodID stays empty for that case. CaptureAuthMethod is the exported counterpart to captureAuthMethod for callers in sibling packages (e.g. origin/collections.go) that need to record the same audit fields when minting their own records (ownership-invite links, etc.). It just delegates.
func CheckAdmin ¶
func CheckAdmin(identity UserIdentity) (isAdmin bool, message string)
CheckAdmin reports whether the identity holds the server.admin scope. The decision is delegated to EffectiveScopesForIdentity, which unions DB-stored user_scopes/group_scopes grants with the historical config-derived sources (Server.UIAdminUsers, Server.AdminGroups, the built-in "admin" username).
All matches are username-based — never ID, never OIDC Sub — so a malicious IdP cannot mint a token with sub == an admin's name and inherit privileges. See the user/group design doc for the reason usernames are the only authorization handle.
func CheckCollectionAdmin ¶
func CheckCollectionAdmin(identity UserIdentity) (bool, string)
CheckCollectionAdmin reports whether the identity holds the server.collection_admin scope. server.admin implies it.
func CheckUserAdmin ¶
func CheckUserAdmin(identity UserIdentity) (bool, string)
CheckUserAdmin reports whether the identity holds the server.user_admin scope. server.admin implies it (the implication is applied inside EffectiveScopesForIdentity).
func ConfigOAuthClientAPIs ¶
Configure OAuth2 client and register related authentication endpoints for Web UI
func ConfigureServerWebAPI ¶ added in v1.0.4
Configure endpoints for server web APIs. This function does not configure any UI specific paths but just redirect root path to /view.
You need to mount the static resources for UI in a separate function
func CurrentAUPVersion ¶
CurrentAUPVersion is a thin convenience wrapper around resolveAUP for callers that only need the version string (e.g. the AUP gate). Keeps the file path in the result for diagnostic logging — empty when Server.AUPFile is unset (default) or "none" (disabled).
func DowntimeAuthHandler ¶
DowntimeAuthHandler allows EITHER: 1. Admin cookie authentication (req from this server itself), OR 2. Server bearer token authentication (req from another server, i.e. origin/cache)
func EffectiveScopesForIdentity ¶
func EffectiveScopesForIdentity(identity UserIdentity) []token_scopes.TokenScope
EffectiveScopesForIdentity returns the union of every scope the user holds. Combines DB-stored grants with config-derived ones so the historical Server.UIAdminUsers / Server.AdminGroups / ... config keys keep working without each operator having to migrate them into user_scopes rows.
The set is deduplicated and filtered to user-grantable scopes (token_scopes.IsUserGrantable). Returns an empty slice for an empty/anonymous identity.
func GenerateCSRFCookie ¶
Generate a 16B random string and set as the value of ctx session key "oauthstate" return a string for OAuth2 "state" query parameter including the random string and other metadata
func GenerateOAuthState ¶
Generate the state for the authentication request in OAuth2 code flow. The metadata are formatted similar to url query parameters:
key1=val1&key2=val2
where values are url-encoded. We then base64 encode the resulting string in order to ensure that over-zealous providers do not treat the final URL as a double-encoding attack or somesuch.
func GetSessionHandler ¶
func GetSessionHandler() (gin.HandlerFunc, error)
Setup and return the session handler for web UI APIs. Calling multiple times will only set up the handler once
func GetUserGroups ¶
Get user information including userId from the login cookie or Bearer token. Returns username, userId, sub, issuer, groups, and error.
func HandleCreateDowntime ¶
func HandleDeleteDowntime ¶
func HandleDeleteLogLevel ¶
HandleDeleteLogLevel handles DELETE requests to remove a temporary log level change
func HandleGetDowntime ¶
func HandleGetDowntimeByUUID ¶
func HandleGetFederationInfo ¶
HandleGetFederationInfo returns the resolved federation discovery info (director, registry, JWK URLs, etc.) for any authenticated user. These values are otherwise only reachable via the admin-only /config endpoint, which prevents non-admin users from rendering basic federation links on the dashboard. The values are not sensitive: directors publish them at /.well-known/pelican-configuration for anonymous client discovery.
func HandleGetLogLevel ¶
HandleGetLogLevel handles GET requests to retrieve current log level status
func HandleGetServerLocalMetadata ¶
HandleGetServerLocalMetadata returns the most recent locally cached server metadata entry (just the current name/type) for Origins/Caches.
func HandleGetServerLocalMetadataHistory ¶
HandleGetServerLocalMetadataHistory returns the locally cached server metadata history for Origins/Caches.
func HandleSetLogLevel ¶
HandleSetLogLevel handles POST requests to temporarily change log level
func HandleUpdateDowntime ¶
func InitServerWebLogin ¶ added in v1.0.4
Setup the initial server web login by sending the one-time code to stdout and record health status of the WebUI based on the success of the initialization
func IsSystemAdminUserID ¶
IsSystemAdminUserID checks whether the given user ID belongs to a system admin. This is used to prevent user administrators from modifying system admin accounts.
func ParseOAuthState ¶
Parse the OAuth2 callback state into a key-val map. Error if keys are duplicated state is the url-decoded value of the query parameter "state" in the the OAuth2 callback request
func ReadOnlyMiddleware ¶
func RegisterAuthEndpoints ¶
func RegisterAuthEndpoints(ctx context.Context, routerGroup *gin.RouterGroup, egrp *errgroup.Group) error
Configure the authentication endpoints for the server web UI
func RequireAUPCompliance ¶
RequireAUPCompliance is a gate that runs after AuthHandler and blocks the request when the caller has not yet agreed to the current AUP.
AUP signing is a hard precondition for using the system per the Pelican design contract: a user who refuses must not be able to proceed with anything other than (a) reading the AUP, (b) signing it, or (c) logging out. Those endpoints are intentionally NOT wrapped in this middleware; everything else that touches user-affecting state is.
The 403 body carries `requires_aup: true` so the frontend can route the user to the AUP page; the version field matches the one returned by /whoami, so the UI can compute "this is the version I need to sign" without a separate fetch.
func RequireAuthMiddleware ¶
Require auth; if missing, redirect to the login endpoint.
The current implementation forces the OAuth2 endpoint; future work may instead use a generic login page.
func RunEngine ¶
Run the gin engine in the current goroutine.
Will use a background golang routine to periodically reload the certificate utilized by the UI.
func RunEngineRoutine ¶
func RunEngineRoutine(ctx context.Context, engine *gin.Engine, egrp *errgroup.Group, curRoutine bool) error
Run the gin engine; if curRoutine is false, it will run in a background goroutine.
func RunEngineRoutineWithListener ¶
func RunEngineRoutineWithListener(ctx context.Context, engine *gin.Engine, egrp *errgroup.Group, curRoutine bool, ln net.Listener) error
Run the web engine connected to a provided listener `ln`.
func ServerHeaderMiddleware ¶
func UserAdminAuthHandler ¶
UserAdminAuthHandler accepts callers whose effective scope set contains EITHER server.admin OR server.user_admin. It is the route-level gate for the /api/v1.0/users/* surface and the onboarding-invite endpoint: surfaces a system administrator must be able to use, and which a "user administrator" (per the design contract: manage non-admin users and unprivileged groups) is also expected to use. Per-target guards inside the handlers (notably IsSystemAdminUserID) prevent a user-admin from acting on a system-admin account; this gate just decides who clears the door.
Cascade behind AuthHandler (cookie/bearer parsing must have run first so the identity is in context).
func WritePasswordEntry ¶ added in v1.0.4
Types ¶
type AUPDocumentResp ¶
type AUPDocumentResp struct {
Content string `json:"content"`
Version string `json:"version"`
Source AUPSource `json:"source"`
LastUpdated string `json:"lastUpdated,omitempty"`
CanonicalURL string `json:"canonicalUrl,omitempty"`
// Editable indicates that the active copy is operator-managed
// in-DB (Source == AUPSourceDB) — the Settings UI uses this to
// decide whether to show "you are editing the active version" vs
// "you are about to override the embedded/file default with a
// fresh DB copy."
Editable bool `json:"editable"`
}
AUPDocumentResp is everything a caller needs to render the AUP: the content (Markdown), the version hash, the source the content came from, and the optional last-updated / canonical-link footer fields.
type AUPSource ¶
type AUPSource string
AUPSource describes where the served AUP came from. Useful so the UI can label which kind of policy is active and so logs make it obvious which copy was used during a given request.
type AddGroupMemberReq ¶
type AddGroupMemberReq struct {
UserID string `json:"userId"`
}
type AddUserIdentityReq ¶
type AddUserReq ¶
type AddUserReq struct {
Username string `json:"username"`
Sub string `json:"sub,omitempty"`
Issuer string `json:"issuer,omitempty"`
DisplayName string `json:"displayName,omitempty"`
}
AddUserReq describes a user-create request. There are two flavors:
- Local user (username/password login): supply Username (and optionally DisplayName). The user is created without a password; the admin then mints a password-set invite (POST /users/:id/password-invite) and hands the link to the user, who picks their own password. Admins never see or set passwords directly — that's the whole point.
- External (OIDC) user: supply Username, Sub, and Issuer.
type CreateApiTokenReq ¶
type CreateGroupReq ¶
type CreateGroupReq struct {
// Name is the machine-readable group identifier. Goes into policy
// strings, admin-group lists, and ACL grants. Validated by
// ValidateIdentifier.
Name string `json:"name"`
// DisplayName is the human-friendly label rendered in the UI.
// Optional; UIs typically fall back to Name when empty.
DisplayName string `json:"displayName,omitempty"`
Description string `json:"description"`
// CreatedForCollectionID ties the new group to a specific collection's
// onboarding pass. Set by the collection-onboarding form so a later
// ownership transfer of that collection cascades to its onboarded
// groups; empty for standalone group creation.
CreatedForCollectionID string `json:"createdForCollectionId,omitempty"`
// AuthTemplateEligible asks the server to mark this new group as
// eligible to match Issuer.AuthorizationTemplates and the
// Server.*AdminGroups config lists. Only an admin / user-admin
// caller may set this to true — for everyone else the request
// field is silently forced to false (the handler enforces this).
AuthTemplateEligible bool `json:"authTemplateEligible,omitempty"`
}
type CreateInviteLinkReq ¶
type DowntimeInput ¶
type DowntimeInput struct {
CreatedBy string `json:"createdBy"` // Person who created this downtime
UpdatedBy string `json:"updatedBy"` // Person who last updated this downtime
ServerName string `json:"serverName"` // Empty for Origin/Cache input; Not empty for Registry input
ServerID string `json:"serverId"`
Source string `json:"source"` // Automatically set by the server; should only be set by input during testing
Class server_structs.Class `json:"class"`
Description string `json:"description"`
Severity server_structs.Severity `json:"severity"`
StartTime int64 `json:"startTime"` // Epoch UTC in seconds
EndTime int64 `json:"endTime"` // Epoch UTC in seconds
}
type GroupView ¶
type GroupView struct {
database.Group
OwnerUser *database.UserCard `json:"ownerUser,omitempty"`
AdminUser *database.UserCard `json:"adminUser,omitempty"`
AdminGroup *database.GroupCard `json:"adminGroup,omitempty"`
CreatedByUser *database.UserCard `json:"createdByUser,omitempty"`
}
GroupView wraps a database.Group with resolved user/group summaries for owner, admin (when adminType=user), createdBy, and (when adminType=group) the admin group itself. The frontend renders these as "Display Name (username)" without needing follow-up lookups, and without requiring user-listing privileges.
type InviteLinkView ¶
type InviteLinkView struct {
database.GroupInviteLink
RedeemedByUser *database.UserCard `json:"redeemedByUser,omitempty"`
}
InviteLinkView wraps a GroupInviteLink with a resolved RedeemedByUser summary. Embedding the card avoids a follow-up GET /users/:id call (which most callers don't have permission for) and lets the UI render the same UserPill component used elsewhere.
type LogLevelChangeResponse ¶
type LogLevelChangeResponse struct {
ChangeID string `json:"changeId"`
Level string `json:"level"`
ParameterName string `json:"parameterName,omitempty"`
EndTime time.Time `json:"endTime"`
Remaining int `json:"remainingSeconds"`
RequiresRestart bool `json:"requiresRestart,omitempty"`
EffectiveAt *time.Time `json:"effectiveAt,omitempty"`
}
LogLevelChangeResponse represents a log level change with its metadata
type LogLevelStatusResponse ¶
type LogLevelStatusResponse struct {
CurrentLevel string `json:"currentLevel"`
BaseLevel string `json:"baseLevel"`
ActiveChanges []LogLevelChangeResponse `json:"activeChanges"`
Parameters []ParameterLevelStatus `json:"parameters"`
}
LogLevelStatusResponse represents the current log level status
type LogrusAdapter ¶ added in v1.0.4
func (LogrusAdapter) Log ¶ added in v1.0.4
func (a LogrusAdapter) Log(keyvals ...interface{}) error
Log method which satisfies the kitlog.Logger interface. It also propragates field level and field message to top level log
type OIDCEnabledServerRes ¶
type OIDCEnabledServerRes struct {
ODICEnabledServers []string `json:"oidc_enabled_servers"`
}
type ParameterLevelStatus ¶
type ParameterLevelStatus struct {
ParameterName string `json:"parameterName"`
CurrentLevel string `json:"currentLevel"`
BaseLevel string `json:"baseLevel"`
}
ParameterLevelStatus summarizes the current/base level for a parameter.
type PasswordReset ¶ added in v1.0.4
type PasswordReset struct {
Password string `form:"password"`
}
type ReadyHandler ¶
type ReadyHandler struct {
// contains filtered or unexported fields
}
func (*ReadyHandler) SetReady ¶
func (h *ReadyHandler) SetReady(v bool)
type RecordAUPAgreementReq ¶
type RecordAUPAgreementReq struct {
Version string `json:"version"`
}
type RedeemInviteLinkReq ¶
type RedeemInviteLinkReq struct {
Token string `json:"token"`
}
type SetLogLevelRequest ¶
type SetLogLevelRequest struct {
Level string `json:"level" binding:"required"` // Log level (e.g., "debug", "info", "warn", "error")
Duration int `json:"duration" binding:"required,min=1"` // Duration in seconds
ParameterName string `json:"parameterName"` // Parameter name like "Logging.Level" or "Logging.Origin.Xrootd"
}
SetLogLevelRequest represents a request to temporarily change log level
type UpdateGroupOwnershipReq ¶
type UpdateGroupReq ¶
type UpdateGroupReq struct {
Name *string `json:"name"`
DisplayName *string `json:"displayName"`
Description *string `json:"description"`
// AuthTemplateEligible may only be flipped by an admin or
// user-admin (the database layer also enforces this). nil = no
// change; &true / &false = explicit set.
AuthTemplateEligible *bool `json:"authTemplateEligible"`
}
UpdateGroupReq carries optional updates for PATCH /groups/:id.
- Name (machine identifier) may only be changed by a system admin. Owners/group-admins attempting it get 403.
- DisplayName and Description are owner-editable.
type UpdateMeReq ¶
type UpdateMeReq struct {
DisplayName *string `json:"displayName"`
}
UpdateMeReq is the body for PATCH /me. Only displayName is mutable self-service; username/sub/issuer changes go through admin endpoints.
type UpdateMyPasswordReq ¶
type UpdateMyPasswordReq struct {
CurrentPassword string `json:"currentPassword"`
NewPassword string `json:"newPassword"`
}
UpdateMyPasswordReq is the body for PUT /me/password — a password rotation. Both fields are required: the caller proves they hold the current credential before being allowed to set a new one (a stolen cookie alone shouldn't be enough to lock the rightful owner out).
type UpdateUserReq ¶
type UpdateUserReq struct {
// Per the user-record contract (see comment on database.User):
// - Username is the authorization handle; admin-only renames go here.
// - DisplayName is a human label; admins can override one as a courtesy
// even though users normally edit their own via PATCH /me.
// - Sub/Issuer are linked OIDC identities; managed via
// /users/:id/identities, never directly here.
Username *string `json:"username"`
DisplayName *string `json:"displayName"`
}
type UpdateUserStatusReq ¶
type UpdateUserStatusReq struct {
Status *database.UserStatus `json:"status"`
}
UpdateUserStatusReq is for the admin-only PUT /users/:id/status endpoint. Status flips an account between active and inactive — purely an authorization concern. Display name (a human label) used to ride along here; it now goes through PATCH /users/:id (admin) or PATCH /me (self), matching the user-record contract on database.User.
type UserIdentity ¶
UserIdentity encapsulates all available information about a user's identity
type WhoAmIRes ¶
type WhoAmIRes struct {
Authenticated bool `json:"authenticated"`
Role UserRole `json:"role"`
User string `json:"user"`
// DisplayName is the human label from the User row (if any).
// Surfaced here so the navbar's user menu can render it
// without a second /me round-trip on every page mount.
// Empty when the row has no display name set.
DisplayName string `json:"displayName,omitempty"`
// Scopes is the caller's effective user-grantable scope set
// (DB user_scopes ∪ DB group_scopes via membership ∪
// config-derived grants ∪ admin implications). Used by
// the frontend to gate UI surfaces below the granularity of
// Role: e.g. /settings/users/ is reachable by anyone with
// server.user_admin (which is a subset of server.admin),
// and the navbar toggles its visibility off scopes rather
// than role membership. Empty for unauthenticated callers.
Scopes []string `json:"scopes,omitempty"`
RequiresAUP bool `json:"requires_aup,omitempty"`
AUPVersion string `json:"aup_version,omitempty"`
}