Documentation/Authorization decisions and query scopes

Authorization decisions and query scopes

简体中文

Integration from other modules

Integration here means other Go packages in this repository. Go internal import rules restrict imports from outside the repository; HTTP clients use mounted gateway endpoints.

After authentication, construct a request-scoped Guard and authorize a concrete Action/Object. For lists, apply Scope to the database query before reading rows.

Entry point or resource Usage and result
New(db) Construct an authorizer with IAM
Guard(ctx, actor) Load current relationships; stop on errors
Authorize(ctx, guard, action, object) Check the action and trusted resource ownership
Scope(ctx, guard, action) Return a scope that must constrain the query

Integration demo

This is a Go integration helper for a host. It compiles as a separate package inside the repository; callers must supply the real dependencies described below before execution.

package demo

import (
	"context"
	"github.com/sunqirui1987/xhub/internal/auth"
	"github.com/sunqirui1987/xhub/internal/authz"
	"github.com/sunqirui1987/xhub/internal/iam"
)

// CanReadUser 演示已鉴权请求的授权;传入真实 IAM、身份和目标,返回授权错误,不写入数据。
func CanReadUser(ctx context.Context, db *iam.DB, p *auth.Principal, userID string) error {
	az := authz.New(db)
	g, err := az.Guard(ctx, p.Actor())
	if err != nil {
		return err
	}
	return az.Authorize(ctx, g, authz.ActionUserRead, authz.Object{Type: authz.ObjectUser, ID: userID})
}

This integration helper compiles but requires real IAM and an authenticated principal. A nil error permits the specified read; it does not perform it.

Errors, state, and side effects

Do not trust ownership supplied in request bodies. Do not reuse Guards across requests. Deny access when relationships cannot be loaded, and distinguish unauthenticated, forbidden, not-found, and internal errors.

Overview

internal/authz is the gateway's central policy layer. It receives an authenticated Actor, resolves the stored ownership and lifecycle state of an Object, decides one Action, and builds SQL scopes for list operations. Handlers supply resource identity and requested action; they do not establish authority by claiming a team, organization, project, or owner. Existing rows are reloaded from IAM before a session decision, and dependency uncertainty closes access.

The policy distinguishes session, virtual-key, and emergency-master actors. Session reach is derived from platform role, team memberships, team roles, and explicit organization-administrator grants. Keys have a narrow, live-revalidated path. The master credential is limited to user and audit reads. The package also exposes sorted capabilities and team filters for the console, but server-side Authorize and Scope remain authoritative.

External interfaces

  • New(*iam.DB) *Authorizer binds policy to IAM storage; DB() exposes the same store to code that executes an already-authorized query. Guard(ctx, Actor) (*Guard, error) creates request-local state. For a session user it loads team memberships and organization-admin grants once into maps; either read failure returns InternalError. Key, master, missing-store, and incomplete-session actors start with empty maps and are rejected or constrained by later decisions.
  • Authorize(ctx, guard, action, object) error is the single-object decision entry point. A nil guard or unknown actor kind is unauthenticated. Success is nil. ErrUnauthenticated, ErrForbidden, and ErrNotFound map to 401, 403, and 404; invisible and absent resources deliberately share 404. InternalError represents ownership, membership, audit, or other dependency failure and maps to 500. IsUnauthenticated, IsForbidden, IsNotFound, and IsInternal classify wrapped outcomes.
  • Actor records proof kind and relevant identity: session user/role or key ID, owner type, team, and project. Object names a resource and carries resolved ownership and status. Supported object types cover user, organization, team/member, project, key, route template, usage, log, audit, budget, and model. Actions cover user lifecycle/password/budget, organization and team administration, membership, projects, keys, route templates, usage/log/audit reads, and inference.
  • Guard helpers expose the loaded snapshot: Actor, PlatformAdmin, TeamRole, InTeam, TeamAdminOf, InOrg, OrgAdminOf, and sorted TeamRoles, AdminOrgIDs, ManagedTeamIDs, and Capabilities. VisibleTeamIDs/TeamFilter have a strict contract: nil means all teams and is returned only for a platform-administrator session; an empty non-nil slice means no teams, including for the master credential. Callers must preserve that distinction when querying.
  • CheckKey(ctx) revalidates a key against current IAM state. KeysScope, UsageScope, and LogsScope return a Scope containing a SQL condition plus resolved dimensions. Scope.Apply, ApplyTeam, and ApplyUser append those restrictions to xorm sessions. Nil scopes, invalid conditions, empty team dimensions, and empty user dimensions inject an always-false condition; only explicit All leaves a query unrestricted.

Gateway identity, key, usage, model, session, inference, and route-template handlers are the principal callers. The UI consumes capabilities for presentation, while handlers still authorize every action and apply scopes before fetching rows.

Core implementation

A session request starts by building one Guard. Membership rows populate team-role and organization-membership maps; explicit organization administration is kept separately because belonging to an organization's team does not grant administration. Sorted helper results make API output deterministic. Existing objects with IDs are resolved by type from IAM, including their owner path and parent lifecycle states. A request-local map memoizes each type:id result. Creation objects have no row; when they name a team, that team and its organization are still loaded so the hint cannot manufacture reach. Usage, log, audit, model, and route-template objects follow their specialized ownership inputs. Unknown resolvable types and failed reads deny rather than silently trust supplied ownership.

The decision pipeline is: reject a missing/unknown identity; branch master and key actors into their narrow matrices; resolve session-object ownership; deny blocked objects and blocked team/organization ancestors to non-platform administrators; run the action matrix; then, for a platform administrator reading another person's log content, write a durable log.read_content audit record. If that audit write fails, the read fails with InternalError, so privileged content is never returned without attribution.

Master permits only user.read and audit.read. A key must have an ID and pass CheckKey on every request, then may infer, read itself, and receive key-bound usage/log scopes; all management actions are forbidden. Revalidation requires an active key. A teamless key must be a personal key with an active owner and no project. A team-bound key requires an active team, a non-blocked organization when present, an active bound project, and, for personal ownership, an active user who remains a member of that team. Missing rows become not-found; other reads become internal failures.

The session matrix separates visibility from mutation. Users may read/write themselves; platform administrators manage accounts, while delegated password reset additionally resolves target role, memberships, and organization-admin status. Organization members can see their organization, organization administrators manage its roster and teams, and platform-only actions retain organization deletion/write and team move/model controls. Team members can read their team, members, and projects; team or organization administrators perform the corresponding management actions. Personal keys remain visible and mutable by their owner or platform administrator; team administrators manage limits without gaining access to members' personal keys. Inference requires membership in the selected team unless the session is a platform administrator.

Route-template rules distinguish read, write, and select. Platform templates are readable by everyone; organization and team templates are visible within their ownership path. Writes require the administrator of the owning level. Selection is broader than writing: a member may select for a team or organization they belong to and for their own personal key. Usage and log decisions allow own personal data and supervised-team data; invisible data returns not-found. Platform audit reads are allowed, and privileged personal-log reads acquire the durable audit described above.

List scopes repeat these boundaries at SQL level. Master receives an always-false scope. Platform administrators receive explicit All unless they request a narrower team. Keys are revalidated and restricted to their own ID. A session's key scope includes only its own personal keys. Usage combines the user's rows with supervised teams; a requested team is accepted only as full supervised access or own rows within a membership. Logs include only the user's own personal-key logs. Team supervision permits aggregate usage, not other users' request bodies. assertLoaded rechecks that the session user is still active before scoped listing, while Scope.Apply* prevents malformed or empty scope state from falling through to an unfiltered query.

Test dependencies

authz_test.go builds a real IAM fixture and covers the session action matrix, team quota/model boundaries, key actors' management prohibition, live key invalidation, log-scope separation, narrow master behavior, fail-closed scopes, nil-versus-empty team filters, unknown kinds, and delegated quota/password administration. personal_keys_test.go covers creation and live use of teamless personal keys. These tests exercise local storage behavior; no network service is required.

Source responsibilities and entry points

  • authz.go defines error categories, actor/object models, the authorizer and request-local guard, membership loading, role and organization helpers, the nil-versus-empty team visibility contract, and sorted UI capabilities. Start at Authorizer.Guard to understand per-request state.
  • decide.go defines all actions, object resolution and memoization, master/key/session decision paths, live key validation, route-template policy, audit-on-log-read behavior, and key/usage/log query scopes. Start at Authorizer.Authorize for single-object checks and KeysScope, UsageScope, or LogsScope for listings.
  • scope.go applies resolved scopes to xorm queries and supplies the final fail-closed barrier for nil, invalid, or empty dimensions.
  • authz_test.go contains the broad policy and scope regression matrix; personal_keys_test.go covers teamless personal-key boundaries.

Search full documentation: budgets, routing, Docker…