Repository navigation
Expand file tree
/
Copy pathattribute.go
More file actions
110 lines (89 loc) · 4.74 KB
/
Copy pathattribute.go
File metadata and controls
110 lines (89 loc) · 4.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
// Copyright 2026 Hyperscale. All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.
package security
import "context"
// Attribute names one requirement of a resource: the role, scope,
// authority, permission or authentication state an access decision must
// check. The attributes a route lists are the policy — each [Voter] of the
// [AccessDecisionManager] votes on the attributes it supports, comparing
// their value with the authentication, and an attribute that no voter
// supports denies the decision. Five concrete attributes are shipped below;
// applications can define their own (they just need to implement String()).
type Attribute interface {
// String returns a stable, log-friendly form of the attribute. It is used
// by [AccessDecisionManager] for OTel attributes; it MUST NOT include
// any secret or PII.
String() string
}
// Authority prefixes. A principal holds a role as the authority
// RolePrefix + name ("ROLE_ADMIN", the Spring Security convention) and a
// scope as the authority ScopePrefix + name ("scope:read:mail", one scope
// per authority — what jwtsec.DefaultAuthorityResolver emits). The voter
// package matches roles and scopes in these namespaces only: a bare
// "admin" authority is neither a role nor a scope.
const (
RolePrefix = "ROLE_"
ScopePrefix = "scope:"
)
// RoleAttribute names a role expected on the authenticated principal: the
// bare name, matched against the authority RolePrefix + name. A name that
// is empty or already starts with RolePrefix cannot be required (no stock
// voter accepts it, so the decision is denied).
type RoleAttribute string
// String implements [Attribute]. Output is "ROLE_<name>" — Spring-compatible
// for ops tooling that already keys off that convention.
func (r RoleAttribute) String() string { return RolePrefix + string(r) }
// Name returns the bare role name (without the ROLE_ prefix).
func (r RoleAttribute) Name() string { return string(r) }
// Role constructs a [RoleAttribute] from a bare role name.
func Role(name string) Attribute { return RoleAttribute(name) }
// ScopeAttribute names an OAuth2 scope expected on the authenticated
// principal: an RFC 6749 §3.3 scope-token, matched against the authority
// ScopePrefix + name.
type ScopeAttribute string
// String implements [Attribute]. Output is "scope:<name>".
func (s ScopeAttribute) String() string { return ScopePrefix + string(s) }
// Name returns the bare scope name.
func (s ScopeAttribute) Name() string { return string(s) }
// Scope constructs a [ScopeAttribute].
func Scope(name string) Attribute { return ScopeAttribute(name) }
// AuthorityAttribute names a free-form authority string. Unlike
// [RoleAttribute] it carries no convention — the configured voter compares
// the value verbatim against [Authentication.Authorities].
type AuthorityAttribute string
// String implements [Attribute]. Output is the bare authority name.
func (a AuthorityAttribute) String() string { return string(a) }
// Authority constructs an [AuthorityAttribute].
func Authority(name string) Attribute { return AuthorityAttribute(name) }
// PermissionAttribute carries an arbitrary predicate evaluated by the
// permission voter. It is the escape hatch for application-specific
// authorization (ABAC, ownership checks, time-of-day windows, ...).
// The predicate MUST be pure (no I/O) and safe for concurrent use.
type PermissionAttribute struct {
// Name is the human-readable label of the permission. It populates the
// OTel attributes; keep it stable across deployments.
Name string
// Predicate is invoked by the permission voter with the live
// authentication. A nil predicate is treated as DecisionDeny.
Predicate func(ctx context.Context, auth Authentication) bool
}
// String implements [Attribute]. Output is "permission:<Name>".
func (p PermissionAttribute) String() string { return "permission:" + p.Name }
// Permission constructs a [PermissionAttribute] in one call.
func Permission(name string, predicate func(ctx context.Context, auth Authentication) bool) Attribute {
return PermissionAttribute{Name: name, Predicate: predicate}
}
// StateAttribute requires a state of the authentication itself rather than
// an authority. The voter package's Authenticated and Anonymous voters
// evaluate it, and no other stock voter does.
type StateAttribute string
const (
// IsAuthenticated requires an authenticated principal.
IsAuthenticated StateAttribute = "authenticated"
// IsAnonymous requires an unauthenticated request — e.g. a signup or
// password-reset request page reserved to logged-out clients.
IsAnonymous StateAttribute = "anonymous"
)
// String implements [Attribute]. Output is "state:<state>".
func (s StateAttribute) String() string { return "state:" + string(s) }