@@ -4,9 +4,11 @@ import (
44 "context"
55 "encoding/json"
66 "net/http"
7+ "time"
78
89 "github.com/contiamo/jwt"
910 uuid "github.com/satori/go.uuid"
11+ "github.com/sirupsen/logrus"
1012)
1113
1214// authContextKey is an unexported type for keys defined in middleware.
@@ -28,51 +30,85 @@ var (
2830 }
2931)
3032
31- // Claims represents the expected claims that should be in a JWT sent to labs
32- //
33- // The IDP defined the token as
34- // type RequestToken struct {
35- // ID string `protobuf:"bytes,1,opt,name=ID,json=id,proto3" json:"ID,omitempty"`
36- // IssuedAt float64 `protobuf:"fixed64,2,opt,name=IssuedAt,json=iat,proto3" json:"IssuedAt,omitempty"`
37- // NotBefore float64 `protobuf:"fixed64,3,opt,name=NotBefore,json=nbf,proto3" json:"NotBefore,omitempty"`
38- // Expires float64 `protobuf:"fixed64,4,opt,name=Expires,json=exp,proto3" json:"Expires,omitempty"`
39- // Issuer string `protobuf:"bytes,5,opt,name=Issuer,json=iss,proto3" json:"Issuer,omitempty"`
40- // UserID string `protobuf:"bytes,6,opt,name=UserID,json=sub,proto3" json:"UserID,omitempty"`
41- // UserName string `protobuf:"bytes,7,opt,name=UserName,json=name,proto3" json:"UserName,omitempty"`
42- // TenantID string `protobuf:"bytes,8,opt,name=TenantID,json=tenantID,proto3" json:"TenantID,omitempty"`
43- // Email string `protobuf:"bytes,9,opt,name=Email,json=email,proto3" json:"Email,omitempty"`
44- // RealmIDs []string `protobuf:"bytes,10,rep,name=RealmIDs,json=realmIDs,proto3" json:"RealmIDs,omitempty"`
45- // GroupIDs []string `protobuf:"bytes,11,rep,name=GroupIDs,json=groupIDs,proto3" json:"GroupIDs,omitempty"`
46- // ResourceTokenIDs []string `protobuf:"bytes,12,rep,name=ResourceTokenIDs,json=resourceTokenIDs,proto3" json:"ResourceTokenIDs,omitempty"`
47- // AllowedIPs []string `protobuf:"bytes,13,rep,name=AllowedIPs,json=allowedIPs,proto3" json:"AllowedIPs,omitempty"`
48- // IsTenantAdmin bool `protobuf:"varint,14,opt,name=IsTenantAdmin,json=isTenantAdmin,proto3" json:"IsTenantAdmin,omitempty"`
49- // AdminRealmIDs []string `protobuf:"bytes,15,rep,name=AdminRealmIDs,json=adminRealmIDs,proto3" json:"AdminRealmIDs,omitempty"`
50- // AuthenticationMethodReferences []string `protobuf:"bytes,16,rep,name=AuthenticationMethodReferences,json=amr,proto3" json:"AuthenticationMethodReferences,omitempty"`
51- // }
33+ // Claims represents the expected claims that should be in JWT claims of an X-Request-Token
5234type Claims struct {
53- ID string `json:"id"`
54- IssuedAt Timestamp `json:"iat"`
55- NotBefore Timestamp `json:"nbf"`
56- Expires Timestamp `json:"exp"`
57- Issuer string `json:"iss"`
58- UserID string `json:"sub"`
59- UserName string `json:"name"`
60- TenantID string `json:"tenantID"`
61- Email string `json:"email"`
62- RealmIDs []string `json:"realmIDs"`
63- GroupIDs []string `json:"groupIDs"`
64- ResourceTokenIDs []string `json:"resourceTokenIDs"`
65- AllowedIPs []string `json:"allowedIPs"`
66- IsTenantAdmin bool `json:"isTenantAdmin"`
67- AdminRealmIDs []string `json:"adminRealmIDs"`
68- SourceToken string `json:"-"`
69- AuthenticationMethodReferences []string `json:"amr"`
35+ // standard oidc claims
36+ ID string `json:"id"`
37+ Issuer string `json:"iss"`
38+ IssuedAt Timestamp `json:"iat"`
39+ NotBefore Timestamp `json:"nbf"`
40+ Expires Timestamp `json:"exp"`
41+ Audience string `json:"aud,omitempty"`
42+
43+ UserID string `json:"sub"`
44+ UserName string `json:"name"`
45+ Email string `json:"email"`
46+
47+ // Contiamo specific claims
48+ TenantID string `json:"tenantID"`
49+ RealmIDs []string `json:"realmIDs"`
50+ GroupIDs []string `json:"groupIDs"`
51+ AllowedIPs []string `json:"allowedIPs"`
52+ IsTenantAdmin bool `json:"isTenantAdmin"`
53+ AdminRealmIDs []string `json:"adminRealmIDs"`
54+
55+ AuthenticationMethodReferences []string `json:"amr"`
56+ // AuthorizedParty is used to indicate that the request is authorizing as a
57+ // service request, giving it super-admin privileges to completely any request.
58+ // This replaces the "project admin" behavior of the current tokens.
59+ AuthorizedParty string `json:"azp,omitempty"`
60+
61+ // SourceToken is for internal usage only
62+ SourceToken string `json:"-"`
7063}
7164
7265// Valid tests if the Claims object contains the minimal required information
7366// to be used for authorization checks.
67+ //
68+ // Deprecated: Use the Validate method to get a precise error message. This
69+ // method remains for backward compatibility.
7470func (a * Claims ) Valid () bool {
75- return a .UserID != "" || len (a .ResourceTokenIDs ) > 0
71+
72+ return a .Validate () == nil
73+ }
74+
75+ // Validate verifies the token claims.
76+ func (a Claims ) Validate () (err error ) {
77+ defer func () {
78+ logrus .WithError (err ).Error ("claims validation error" )
79+ }()
80+
81+ now := TimeFunc ()
82+
83+ // this validation is specific to contiamo
84+ if a .UserID == "" {
85+ return ErrMissingSub
86+ }
87+
88+ // the middleware parsing will generally run this validation, but
89+ // adding it here marks the exp as a required claim
90+ if ! a .VerifyExpiresAt (now , true ) {
91+ return ErrExpiration
92+ }
93+
94+ // the middleware parsing will generally run this validation, but
95+ // adding it here marks the nbf as a required claim
96+ if ! a .VerifyNotBefore (now , true ) {
97+ return ErrTooEarly
98+ }
99+
100+ // the middleware parsing will generally run this validation, but
101+ // adding it here marks the iat as a required claim
102+ if ! a .VerifyIssuedAt (now , true ) {
103+ return ErrTooSoon
104+ }
105+
106+ // this validation is specific to contiamo
107+ if ! a .VerifyAuthorizedParty () {
108+ return ErrInvalidParty
109+ }
110+
111+ return nil
76112}
77113
78114// FromClaimsMap loads the claim information from a jwt.Claims object, this is a simple
@@ -112,10 +148,62 @@ func (a *Claims) ToJWT(privateKey interface{}) (string, error) {
112148func (a * Claims ) Entities () (entities []string ) {
113149 entities = append (entities , a .UserID )
114150 entities = append (entities , a .GroupIDs ... )
115- entities = append (entities , a .ResourceTokenIDs ... )
116151 return entities
117152}
118153
154+ // VerifyAudience compares the aud claim against cmp.
155+ func (a Claims ) VerifyAudience (cmp string , required bool ) bool {
156+ if a .Audience == "" {
157+ return ! required
158+ }
159+
160+ return a .Audience == cmp
161+ }
162+
163+ // VerifyExpiresAt compares the exp claim against the cmp time.
164+ func (a Claims ) VerifyExpiresAt (cmp time.Time , required bool ) bool {
165+ if a .Expires .time .IsZero () {
166+ return ! required
167+ }
168+
169+ return cmp .Before (a .Expires .time )
170+ }
171+
172+ // VerifyNotBefore compares the nbf claim against the cmp time.
173+ func (a Claims ) VerifyNotBefore (cmp time.Time , required bool ) bool {
174+ if a .NotBefore .time .IsZero () {
175+ return ! required
176+ }
177+
178+ return cmp .After (a .NotBefore .time ) || cmp .Equal (a .NotBefore .time )
179+ }
180+
181+ // VerifyIssuedAt compares the iat claim against the cmp time.
182+ func (a Claims ) VerifyIssuedAt (cmp time.Time , required bool ) bool {
183+ if a .IssuedAt .time .IsZero () {
184+ return ! required
185+ }
186+
187+ return cmp .After (a .IssuedAt .time ) || cmp .Equal (a .IssuedAt .time )
188+ }
189+
190+ // VerifyIssuer compares the iss claim against cmp.
191+ func (a Claims ) VerifyIssuer (cmp string , required bool ) bool {
192+ if a .Issuer == "" {
193+ return ! required
194+ }
195+
196+ return a .Issuer == cmp
197+ }
198+
199+ // VerifyAuthorizedParty verify that azp matches the iss value, if set.
200+ func (a Claims ) VerifyAuthorizedParty () bool {
201+ if a .AuthorizedParty == "" {
202+ return true
203+ }
204+ return a .VerifyIssuer (a .AuthorizedParty , true )
205+ }
206+
119207// GetClaims retrieves the Claims object from the request context
120208func GetClaims (r * http.Request ) (Claims , bool ) {
121209 claims , ok := GetClaimsFromCtx (r .Context ())
0 commit comments