This repository has been archived by the owner on Jun 12, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 1
/
claims.go
225 lines (188 loc) · 6.14 KB
/
claims.go
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
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
package authorization
import (
"context"
"encoding/json"
"net/http"
"time"
"github.com/contiamo/jwt"
uuid "github.com/satori/go.uuid"
"github.com/sirupsen/logrus"
)
// authContextKey is an unexported type for keys defined in middleware.
// This prevents collisions with keys defined in other packages.
type authContextKey string
func (c authContextKey) String() string {
return authContextKeyPrefix + string(c)
}
var (
authContextKeyPrefix = "com.contiamo.labs.auth.contextKey."
// proxyAuthTokenClaimsKey is the context key used to set/access the JWT claims
authClaimsKey = authContextKey("DatastoreClaims")
// DataStoreClaims used for setting the service itself as an author of a record
DataStoreClaims = Claims{
UserID: uuid.Nil.String(),
UserName: "datastore",
}
)
// Claims represents the expected claims that should be in JWT claims of an X-Request-Token
type Claims struct {
// standard oidc claims
ID string `json:"id"`
Issuer string `json:"iss"`
IssuedAt Timestamp `json:"iat"`
NotBefore Timestamp `json:"nbf"`
Expires Timestamp `json:"exp"`
Audience string `json:"aud,omitempty"`
UserID string `json:"sub"`
UserName string `json:"name"`
Email string `json:"email"`
// Contiamo specific claims
TenantID string `json:"tenantID"`
RealmIDs []string `json:"realmIDs"`
GroupIDs []string `json:"groupIDs"`
AllowedIPs []string `json:"allowedIPs"`
IsTenantAdmin bool `json:"isTenantAdmin"`
AdminRealmIDs []string `json:"adminRealmIDs"`
AuthenticationMethodReferences []string `json:"amr"`
// AuthorizedParty is used to indicate that the request is authorizing as a
// service request, giving it super-admin privileges to completely any request.
// This replaces the "project admin" behavior of the current tokens.
AuthorizedParty string `json:"azp,omitempty"`
// SourceToken is for internal usage only
SourceToken string `json:"-"`
}
// Valid tests if the Claims object contains the minimal required information
// to be used for authorization checks.
//
// Deprecated: Use the Validate method to get a precise error message. This
// method remains for backward compatibility.
func (a *Claims) Valid() bool {
return a.Validate() == nil
}
// Validate verifies the token claims.
func (a Claims) Validate() (err error) {
defer func() {
if err != nil {
logrus.WithError(err).Error("claims validation error")
}
}()
now := TimeFunc()
// this validation is specific to contiamo
if a.UserID == "" {
return ErrMissingSub
}
// the middleware parsing will generally run this validation, but
// adding it here marks the exp as a required claim
if !a.VerifyExpiresAt(now, true) {
return ErrExpiration
}
// the middleware parsing will generally run this validation, but
// adding it here marks the nbf as a required claim
if !a.VerifyNotBefore(now, true) {
return ErrTooEarly
}
// the middleware parsing will generally run this validation, but
// adding it here marks the iat as a required claim
if !a.VerifyIssuedAt(now, true) {
return ErrTooSoon
}
// this validation is specific to contiamo
if !a.VerifyAuthorizedParty() {
return ErrInvalidParty
}
return nil
}
// FromClaimsMap loads the claim information from a jwt.Claims object, this is a simple
// map[string]interface{}
func (a *Claims) FromClaimsMap(claims jwt.Claims) error {
bs, err := json.Marshal(claims)
if err != nil {
return err
}
return json.Unmarshal(bs, a)
}
// ToClaims encodes the token as jwt.Claims
func (a *Claims) ToClaims() (jwt.Claims, error) {
claimBytes, err := json.Marshal(a)
if err != nil {
return nil, err
}
claims := make(jwt.Claims)
if err = json.Unmarshal(claimBytes, &claims); err != nil {
return nil, err
}
return claims, nil
}
// ToJWT encodes the token to a valid jwt
func (a *Claims) ToJWT(privateKey interface{}) (string, error) {
claims, err := a.ToClaims()
if err != nil {
return "", err
}
return jwt.CreateToken(claims, privateKey)
}
// Entities returns a slice of the entity ids that the auth claims contains. These are ids
// that permissions may be assigned to. Currently, this is the UserID, GroupIDs, and ResourceTokenIDs
func (a *Claims) Entities() (entities []string) {
entities = append(entities, a.UserID)
entities = append(entities, a.GroupIDs...)
return entities
}
// VerifyAudience compares the aud claim against cmp.
func (a Claims) VerifyAudience(cmp string, required bool) bool {
if a.Audience == "" {
return !required
}
return a.Audience == cmp
}
// VerifyExpiresAt compares the exp claim against the cmp time.
func (a Claims) VerifyExpiresAt(cmp time.Time, required bool) bool {
if a.Expires.time.IsZero() {
return !required
}
return cmp.Before(a.Expires.time)
}
// VerifyNotBefore compares the nbf claim against the cmp time.
func (a Claims) VerifyNotBefore(cmp time.Time, required bool) bool {
if a.NotBefore.time.IsZero() {
return !required
}
return cmp.After(a.NotBefore.time) || cmp.Equal(a.NotBefore.time)
}
// VerifyIssuedAt compares the iat claim against the cmp time.
func (a Claims) VerifyIssuedAt(cmp time.Time, required bool) bool {
if a.IssuedAt.time.IsZero() {
return !required
}
return cmp.After(a.IssuedAt.time) || cmp.Equal(a.IssuedAt.time)
}
// VerifyIssuer compares the iss claim against cmp.
func (a Claims) VerifyIssuer(cmp string, required bool) bool {
if a.Issuer == "" {
return !required
}
return a.Issuer == cmp
}
// VerifyAuthorizedParty verify that azp matches the iss value, if set.
func (a Claims) VerifyAuthorizedParty() bool {
if a.AuthorizedParty == "" {
return true
}
return a.VerifyIssuer(a.AuthorizedParty, true)
}
// GetClaims retrieves the Claims object from the request context
func GetClaims(r *http.Request) (Claims, bool) {
claims, ok := GetClaimsFromCtx(r.Context())
return claims, ok
}
// GetClaimsFromCtx retrieves the Claims object from the given context
func GetClaimsFromCtx(ctx context.Context) (Claims, bool) {
claims, ok := ctx.Value(authClaimsKey).(Claims)
return claims, ok
}
// SetClaims add the Claims instance to the request Context
func SetClaims(r *http.Request, claims Claims) *http.Request {
ctx := r.Context()
ctx = context.WithValue(ctx, authClaimsKey, claims)
return r.WithContext(ctx)
}