/
authorizers.go
164 lines (136 loc) · 4.16 KB
/
authorizers.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
package auth
import (
"crypto/rsa"
"os"
errors "github.com/go-openapi/errors"
models "github.com/go-swagger/go-swagger/examples/composed-auth/models"
jwt "github.com/golang-jwt/jwt/v5"
)
const (
// currently unused: privateKeyPath = "keys/apiKey.prv"
publicKeyPath = "keys/apiKey.pem"
issuerName = "example.com"
)
var (
userDb map[string]string
// Keys used to sign and verify our tokens
verifyKey *rsa.PublicKey
// currently unused: signKey *rsa.PrivateKey
)
// roleClaims describes the format of our JWT token's claims
type roleClaims struct {
Roles []string `json:"roles"`
jwt.MapClaims
}
func init() {
// emulates the loading of a local users database
userDb = map[string]string{
"fred": "scrum",
"ivan": "terrible",
}
// loads public keys to verify our tokens
verifyKeyBuf, err := os.ReadFile(publicKeyPath)
if err != nil {
panic("Cannot load public key for tokens")
}
verifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyKeyBuf)
if err != nil {
panic("Invalid public key for tokens")
}
}
// Customized authorizer methods for our sample API
// IsRegistered determines if the user is properly registered,
// i.e if a valid username:password pair has been provided
func IsRegistered(user, pass string) (*models.Principal, error) {
password, ok := userDb[user]
if !ok || pass != password {
return nil, errors.New(401, "Unauthorized: not a registered user")
}
return &models.Principal{
Name: user,
}, nil
}
// IsReseller tells if the API key is a JWT signed by us with a claim to be a reseller
func IsReseller(token string) (*models.Principal, error) {
claims, err := parseAndCheckToken(token)
if err != nil {
return nil, errors.New(401, "Unauthorized: invalid API key token: %v", err)
}
issuer, err := claims.GetIssuer()
if issuer != issuerName {
return nil, errors.New(401, "Unauthorized: invalid Bearer token: invalid issuer %v", err)
}
id, err := claims.GetSubject()
if err != nil {
return nil, errors.New(401, "Unauthorized: invalid Bearer token: missing subject: %v", err)
}
if issuer != issuerName || id == "" {
return nil, errors.New(403, "Forbidden: insufficient API key privileges")
}
isReseller := false
for _, role := range claims.Roles {
if role == "reseller" {
isReseller = true
break
}
}
if !isReseller {
return nil, errors.New(403, "Forbidden: insufficient API key privileges")
}
return &models.Principal{
Name: id,
}, nil
}
// HasRole tells if the Bearer token is a JWT signed by us with a claim to be
// member of an authorization scope.
// We verify that the claimed role is one of the passed scopes
func HasRole(token string, scopes []string) (*models.Principal, error) {
claims, err := parseAndCheckToken(token)
if err != nil {
return nil, errors.New(401, "Unauthorized: invalid Bearer token: %v", err)
}
issuer, err := claims.GetIssuer()
if err != nil {
return nil, errors.New(401, "Unauthorized: invalid Bearer token: invalid issuer %v", err)
}
if issuer != issuerName {
return nil, errors.New(401, "Unauthorized: invalid Bearer token: invalid issuer %s", issuer)
}
id, err := claims.GetSubject()
if err != nil {
return nil, errors.New(401, "Unauthorized: invalid Bearer token: missing subject: %v", err)
}
isInScopes := false
claimedRoles := []string{}
for _, scope := range scopes {
for _, role := range claims.Roles {
if role == scope {
isInScopes = true
// we enrich the principal with all claimed roles within scope (hence: not breaking here)
claimedRoles = append(claimedRoles, role)
}
}
}
if !isInScopes {
return nil, errors.New(403, "Forbidden: insufficient privileges")
}
return &models.Principal{
Name: id,
Roles: claimedRoles,
}, nil
}
func parseAndCheckToken(token string) (*roleClaims, error) {
// the API key is a JWT signed by us with a claim to be a reseller
parsedToken, err := jwt.ParseWithClaims(token, &roleClaims{}, func(parsedToken *jwt.Token) (interface{}, error) {
// the key used to validate tokens
return verifyKey, nil
})
if err != nil {
return nil, err
}
claims, ok := parsedToken.Claims.(*roleClaims)
if !ok || !parsedToken.Valid {
return nil, errors.New(401, "invalid token missing expected claims")
}
return claims, nil
}