Skip to content

Commit 7feca14

Browse files
committed
Check for sub claim, not username, when validating
Not all IdPs provide a `username` or `email` claim in the UserInfo response.
1 parent 7f2d675 commit 7feca14

File tree

8 files changed

+83
-19
lines changed

8 files changed

+83
-19
lines changed

.defaults.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ vouch:
3434
# key:
3535

3636
headers:
37+
sub: X-Vouch-Sub
3738
jwt: X-Vouch-Token
3839
user: X-Vouch-User
3940
success: X-Vouch-Success

handlers/handlers_test.go

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,12 @@ func setUp(configFile string) {
4646

4747
func TestVerifyUserPositiveUserInWhiteList(t *testing.T) {
4848
setUp("/config/testing/handler_whitelist.yml")
49-
user := &structs.User{Username: "[email protected]", Email: "[email protected]", Name: "Test Name"}
49+
user := &structs.User{
50+
Sub: "testsub",
51+
Username: "[email protected]",
52+
53+
Name: "Test Name",
54+
}
5055
ok, err := verifyUser(*user)
5156
assert.True(t, ok)
5257
assert.Nil(t, err)
@@ -55,7 +60,12 @@ func TestVerifyUserPositiveUserInWhiteList(t *testing.T) {
5560
func TestVerifyUserPositiveAllowAllUsers(t *testing.T) {
5661
setUp("/config/testing/handler_allowallusers.yml")
5762

58-
user := &structs.User{Username: "testuser", Email: "[email protected]", Name: "Test Name"}
63+
user := &structs.User{
64+
Sub: "testsub",
65+
Username: "testuser",
66+
67+
Name: "Test Name",
68+
}
5969

6070
ok, err := verifyUser(*user)
6171
assert.True(t, ok)
@@ -64,7 +74,12 @@ func TestVerifyUserPositiveAllowAllUsers(t *testing.T) {
6474

6575
func TestVerifyUserPositiveByEmail(t *testing.T) {
6676
setUp("/config/testing/handler_email.yml")
67-
user := &structs.User{Username: "testuser", Email: "[email protected]", Name: "Test Name"}
77+
user := &structs.User{
78+
Sub: "testsub",
79+
Username: "testuser",
80+
81+
Name: "Test Name",
82+
}
6883
ok, err := verifyUser(*user)
6984
assert.True(t, ok)
7085
assert.Nil(t, err)
@@ -74,7 +89,12 @@ func TestVerifyUserPositiveByTeam(t *testing.T) {
7489
setUp("/config/testing/handler_teams.yml")
7590

7691
// cfg.Cfg.TeamWhiteList = append(cfg.Cfg.TeamWhiteList, "org1/team2", "org1/team1")
77-
user := &structs.User{Username: "testuser", Email: "[email protected]", Name: "Test Name"}
92+
user := &structs.User{
93+
Sub: "testsub",
94+
Username: "testuser",
95+
96+
Name: "Test Name",
97+
}
7898
user.TeamMemberships = append(user.TeamMemberships, "org1/team3")
7999
user.TeamMemberships = append(user.TeamMemberships, "org1/team1")
80100
ok, err := verifyUser(*user)
@@ -84,7 +104,12 @@ func TestVerifyUserPositiveByTeam(t *testing.T) {
84104

85105
func TestVerifyUserNegativeByTeam(t *testing.T) {
86106
setUp("/config/testing/handler_teams.yml")
87-
user := &structs.User{Username: "testuser", Email: "[email protected]", Name: "Test Name"}
107+
user := &structs.User{
108+
Sub: "testsub",
109+
Username: "testuser",
110+
111+
Name: "Test Name",
112+
}
88113
// cfg.Cfg.TeamWhiteList = append(cfg.Cfg.TeamWhiteList, "org1/team1")
89114

90115
ok, err := verifyUser(*user)
@@ -95,7 +120,12 @@ func TestVerifyUserNegativeByTeam(t *testing.T) {
95120
func TestVerifyUserPositiveNoDomainsConfigured(t *testing.T) {
96121
setUp("/config/testing/handler_nodomains.yml")
97122

98-
user := &structs.User{Username: "testuser", Email: "[email protected]", Name: "Test Name"}
123+
user := &structs.User{
124+
Sub: "testsub",
125+
Username: "testuser",
126+
127+
Name: "Test Name",
128+
}
99129
cfg.Cfg.Domains = make([]string, 0)
100130
ok, err := verifyUser(*user)
101131

@@ -105,7 +135,12 @@ func TestVerifyUserPositiveNoDomainsConfigured(t *testing.T) {
105135

106136
func TestVerifyUserNegative(t *testing.T) {
107137
setUp("/config/testing/test_config.yml")
108-
user := &structs.User{Username: "testuser", Email: "[email protected]", Name: "Test Name"}
138+
user := &structs.User{
139+
Sub: "testsub",
140+
Username: "testuser",
141+
142+
Name: "Test Name",
143+
}
109144
ok, err := verifyUser(*user)
110145

111146
assert.False(t, ok)
@@ -116,6 +151,7 @@ func TestVerifyUserNegative(t *testing.T) {
116151
// it should live there but circular imports are resolved if it lives here
117152
var (
118153
u1 = structs.User{
154+
Sub: "test",
119155
Username: "[email protected]",
120156
Name: "Test Name",
121157
}
@@ -141,6 +177,7 @@ func init() {
141177
// log.SetLevel(log.DebugLevel)
142178

143179
lc = jwtmanager.VouchClaims{
180+
u1.Sub,
144181
u1.Username,
145182
jwtmanager.Sites,
146183
customClaims.Claims,

handlers/validate.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ import (
2525
)
2626

2727
var (
28-
errNoJWT = errors.New("no jwt found in request")
29-
errNoUser = errors.New("no User found in jwt")
28+
errNoJWT = errors.New("no jwt found in request")
29+
errNoSub = errors.New("no 'sub' found in jwt")
3030
)
3131

3232
// ValidateRequestHandler /validate
@@ -45,8 +45,8 @@ func ValidateRequestHandler(w http.ResponseWriter, r *http.Request) {
4545
return
4646
}
4747

48-
if claims.Username == "" {
49-
send401or200PublicAccess(w, r, errNoUser)
48+
if claims.Sub == "" {
49+
send401or200PublicAccess(w, r, errNoSub)
5050
return
5151
}
5252

@@ -59,7 +59,10 @@ func ValidateRequestHandler(w http.ResponseWriter, r *http.Request) {
5959
}
6060

6161
generateCustomClaimsHeaders(w, claims)
62-
w.Header().Add(cfg.Cfg.Headers.User, claims.Username)
62+
w.Header().Add(cfg.Cfg.Headers.Sub, claims.Sub)
63+
if claims.Username != "" {
64+
w.Header().Add(cfg.Cfg.Headers.User, claims.Username)
65+
}
6366
w.Header().Add(cfg.Cfg.Headers.Success, "true")
6467

6568
if cfg.Cfg.Headers.AccessToken != "" && claims.PAccessToken != "" {

handlers/validate_test.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,12 @@ import (
2828

2929
func BenchmarkValidateRequestHandler(b *testing.B) {
3030
setUp("/config/testing/handler_email.yml")
31-
user := &structs.User{Username: "testuser", Email: "[email protected]", Name: "Test Name"}
31+
user := &structs.User{
32+
Sub: "testsub",
33+
Username: "testuser",
34+
35+
Name: "Test Name",
36+
}
3237
tokens := structs.PTokens{}
3338
customClaims := structs.CustomClaims{}
3439

@@ -66,7 +71,12 @@ func TestValidateRequestHandlerPerf(t *testing.T) {
6671
}
6772

6873
setUp("/config/testing/handler_email.yml")
69-
user := &structs.User{Username: "testuser", Email: "[email protected]", Name: "Test Name"}
74+
user := &structs.User{
75+
Sub: "testsub",
76+
Username: "testuser",
77+
78+
Name: "Test Name",
79+
}
7080
tokens := structs.PTokens{}
7181
customClaims := structs.CustomClaims{}
7282

@@ -153,7 +163,12 @@ func TestValidateRequestHandlerWithGroupClaims(t *testing.T) {
153163

154164
tokens := structs.PTokens{}
155165

156-
user := &structs.User{Username: "testuser", Email: "[email protected]", Name: "Test Name"}
166+
user := &structs.User{
167+
Sub: "testsub",
168+
Username: "testuser",
169+
170+
Name: "Test Name",
171+
}
157172
userTokenString := jwtmanager.CreateUserTokenString(*user, customClaims, tokens)
158173

159174
req, err := http.NewRequest("GET", "/validate", nil)
@@ -205,7 +220,12 @@ func TestJWTCacheHandler(t *testing.T) {
205220
setUp("/config/testing/handler_logout_url.yml")
206221
handler := jwtmanager.JWTCacheHandler(http.HandlerFunc(ValidateRequestHandler))
207222

208-
user := &structs.User{Username: "testuser", Email: "[email protected]", Name: "Test Name"}
223+
user := &structs.User{
224+
Sub: "testsub",
225+
Username: "testuser",
226+
227+
Name: "Test Name",
228+
}
209229
tokens := structs.PTokens{}
210230
customClaims := structs.CustomClaims{}
211231

pkg/cfg/cfg.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ type Config struct {
5959
}
6060

6161
Headers struct {
62+
Sub string `mapstructure:"sub"`
6263
JWT string `mapstructure:"jwt"`
6364
User string `mapstructure:"user"`
6465
QueryString string `mapstructure:"querystring"`

pkg/jwtmanager/jwtmanager.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333

3434
// VouchClaims jwt Claims specific to vouch
3535
type VouchClaims struct {
36+
Sub string `json:"sub"`
3637
Username string `json:"username"`
3738
Sites []string `json:"sites"` // tempting to make this a map but the array is fewer characters in the jwt
3839
CustomClaims map[string]interface{}
@@ -78,6 +79,7 @@ func CreateUserTokenString(u structs.User, customClaims structs.CustomClaims, pt
7879
// User`token`
7980
// u.PrepareUserData()
8081
claims := VouchClaims{
82+
u.Sub,
8183
u.Username,
8284
Sites,
8385
customClaims.Claims,

pkg/jwtmanager/jwtmanager_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
var (
2424
u1 = structs.User{
25+
Sub: "testsub",
2526
Username: "[email protected]",
2627
Name: "Test Name",
2728
}
@@ -49,6 +50,7 @@ func init() {
4950
Configure()
5051

5152
lc = VouchClaims{
53+
u1.Sub,
5254
u1.Username,
5355
Sites,
5456
customClaims.Claims,

pkg/structs/structs.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type UserI interface {
2222

2323
// User is inherited.
2424
type User struct {
25+
Sub string `json:"sub"`
2526
// TODO: set Provider here so that we can pass it to db
2627
// populated by db (via mapstructure) or from provider (via json)
2728
// Provider string `json:"provider",mapstructure:"provider"`
@@ -47,7 +48,6 @@ func (u *User) PrepareUserData() {
4748
// AzureUser is a retrieved and authenticated user from Azure AD
4849
type AzureUser struct {
4950
User
50-
Sub string `json:"sub"`
5151
UPN string `json:"upn"`
5252
}
5353

@@ -71,7 +71,6 @@ func (u *AzureUser) PrepareUserData() {
7171
// https://golang.org/doc/effective_go.html#embedding
7272
type GoogleUser struct {
7373
User
74-
Sub string `json:"sub"`
7574
GivenName string `json:"given_name"`
7675
FamilyName string `json:"family_name"`
7776
Profile string `json:"profile"`
@@ -90,7 +89,6 @@ func (u *GoogleUser) PrepareUserData() {
9089
// ADFSUser Active Directory user record
9190
type ADFSUser struct {
9291
User
93-
Sub string `json:"sub"`
9492
UPN string `json:"upn"`
9593
// UniqueName string `json:"unique_name"`
9694
// PwdExp string `json:"pwd_exp"`

0 commit comments

Comments
 (0)