Documentation
¶
Overview ¶
Package authorization decides whether the identity in a request context may proceed. A Check is a function of the context, so the same check guards HTTP routes, WebSocket messages, jobs and tests. Checks compose with All, Any, Not and Real; the primitives read the auth.Identity a provider put into the context.
Refusals are *auth.Error: ErrUnauthenticated when there is no identity, ErrForbidden otherwise, with a Reason for the wide event that never reaches the client. Not and every primitive except IsAuthenticated require an active identity, so a deactivated admin is not an admin. A wiring mistake (a nil Check, All or Any without checks, HasPermission with an invalid permission) fails with a plain error, answered 500, so it fails closed and Not cannot invert it into a pass.
Entity checks (may this identity see this object) stay in the applications. Checks are pure and safe for concurrent use.
Index ¶
- type Check
- func All(checks ...Check) Check
- func Any(checks ...Check) Check
- func HasGitHubTeam(team string) Check
- func HasOIDCGroup(group string) Check
- func HasOIDCRole(role string) Check
- func HasPermission(resource string, access auth.Access) Check
- func HasRole(role string) Check
- func HasUserID(id uuid.UUID) Check
- func IsActive() Check
- func IsAdmin() Check
- func IsAuthenticated() Check
- func IsUser() Check
- func Not(check Check) Check
- func Real(check Check) Check
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Check ¶
Check decides whether the identity in ctx may proceed: nil allows, an error refuses. Refusals are *auth.Error with Kind ErrUnauthenticated (no identity) or ErrForbidden (Reason says why).
func All ¶
All passes when every check passes and returns the first refusal or failure otherwise. Without checks it fails (a wiring mistake).
Example ¶
package main
import (
"context"
"fmt"
"github.com/govengers/auth"
"github.com/govengers/auth/authorization"
)
func main() {
editor := authorization.All(authorization.HasOIDCGroup("/engineering"), authorization.Not(authorization.HasOIDCRole("read-only")))
engineer := auth.WithIdentity(context.Background(), &auth.Identity{Provider: "keycloak", Subject: "1", Active: true, Groups: []string{"/engineering"}})
reader := auth.WithIdentity(context.Background(), &auth.Identity{Provider: "keycloak", Subject: "2", Active: true, Groups: []string{"/engineering"}, OIDCRoles: []string{"read-only"}})
fmt.Println(editor(engineer))
status, response := auth.ResponseFor(editor(reader))
fmt.Println(status, response.Code)
}
Output: <nil> 403 forbidden
func Any ¶
Any passes when one check passes. When none does it returns the first failure (an error answered 500: that check decided nothing, so Not must not invert the result), else the first refusal. Without checks it fails (a wiring mistake).
Example ¶
package main
import (
"context"
"fmt"
"github.com/google/uuid"
"github.com/govengers/auth"
"github.com/govengers/auth/authorization"
)
func main() {
ownerID := uuid.MustParse("0b6f2c1e-7a3d-4c55-9f0e-2d8b1a6c4e71")
ownerOrAdmin := authorization.Any(authorization.IsAdmin(), authorization.HasUserID(ownerID))
owner := auth.WithIdentity(context.Background(), &auth.Identity{Provider: "password", Subject: "ada", UserID: ownerID, Active: true})
stranger := auth.WithIdentity(context.Background(), &auth.Identity{Provider: "password", Subject: "eve", UserID: uuid.New(), Active: true})
fmt.Println(ownerOrAdmin(owner))
fmt.Println(ownerOrAdmin(stranger))
fmt.Println(ownerOrAdmin(context.Background()))
}
Output: <nil> forbidden: missing role admin unauthenticated: no identity
func HasGitHubTeam ¶
HasGitHubTeam passes an active identity that is a member of team ("org/team-slug", matched case-insensitively; the GitHub provider stores Teams in lower case).
func HasOIDCGroup ¶
HasOIDCGroup passes an active identity with group in Groups.
func HasOIDCRole ¶
HasOIDCRole passes an active identity with role in OIDCRoles (Keycloak realm roles).
func HasPermission ¶
HasPermission passes an active identity that Permits access on resource (auth.Permission.Grants: "*" covers every resource, write includes read). An empty or invalid resource, or an access other than read and write, is a wiring mistake: every request fails. The effective identity counts; wrap the check in Real to check the impersonator. Interactive users have no permissions unless an OnLogin hook grants them: compose Any(HasRole(role), HasPermission(resource, access)) or grant them at login.
Example ¶
package main
import (
"context"
"fmt"
"github.com/govengers/auth"
"github.com/govengers/auth/authorization"
)
func main() {
continuousIntegration := auth.WithIdentity(context.Background(), &auth.Identity{
Provider: "apikey",
Subject: "ci",
Active: true,
Permissions: []auth.Permission{{Resource: "builds", Access: auth.AccessWrite}, {Resource: "*", Access: auth.AccessRead}},
})
fmt.Println(authorization.HasPermission("builds", auth.AccessWrite)(continuousIntegration))
fmt.Println(authorization.HasPermission("deployments", auth.AccessRead)(continuousIntegration))
fmt.Println(authorization.HasPermission("deployments", auth.AccessWrite)(continuousIntegration))
}
Output: <nil> <nil> forbidden: missing permission deployments:write
func HasUserID ¶
HasUserID passes an active identity whose UserID is id. uuid.Nil never matches, so an identity without an application user ID never owns anything.
func IsAuthenticated ¶
func IsAuthenticated() Check
IsAuthenticated passes any identity, active or not.
func IsUser ¶
func IsUser() Check
IsUser passes an active identity; it is IsActive under the name application code uses for "any user".
func Not ¶
Not inverts check for an active identity: it passes when check refuses with ErrForbidden and refuses with ErrForbidden when check passes; other errors pass through. The refusal is classified like auth.ResponseFor does, by the Kind of the outermost *auth.Error, so an unauthenticated refusal with a forbidden cause is no forbidden refusal. Like the primitives it refuses a missing identity with ErrUnauthenticated and an inactive one with ErrForbidden before it evaluates check, so Not(HasRole(role)) never admits an anonymous or deactivated identity.