Skip to content

Commit b59f1bc

Browse files
committed
DIfferentiate Between Subject and Email
This is kind inconsistent in that the subject is sometimes a service account ID and sometimes a user's email address. If we force it to be a user ID instead, this means lookups are quicker and less error prone, this forces clients into using the email claim, which they should use anyway, and may be slightly breaking.
1 parent 82c06de commit b59f1bc

File tree

6 files changed

+42
-35
lines changed

6 files changed

+42
-35
lines changed

pkg/handler/organizations/client.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ func (c *Client) List(ctx context.Context, rbacClient *rbac.RBAC) (openapi.Organ
187187
return nil, errors.OAuth2ServerError("failed to list organizations").WithError(err)
188188
}
189189

190-
user, err := rbacClient.GetActiveUser(ctx, info.Userinfo.Sub)
190+
user, err := rbacClient.GetActiveUserByID(ctx, info.Userinfo.Sub)
191191
if err != nil {
192192
return nil, errors.OAuth2ServerError("failed to list active subjects").WithError(err)
193193
}

pkg/middleware/audit/logging.go

+7-1
Original file line numberDiff line numberDiff line change
@@ -136,13 +136,19 @@ func (l *Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) {
136136
return
137137
}
138138

139+
subject := info.Userinfo.Sub
140+
141+
if info.Userinfo.Email != nil {
142+
subject = *info.Userinfo.Email
143+
}
144+
139145
logParams := []any{
140146
"component", &Component{
141147
Name: l.application,
142148
Version: l.version,
143149
},
144150
"actor", &Actor{
145-
Subject: info.Userinfo.Sub,
151+
Subject: subject,
146152
},
147153
"operation", &Operation{
148154
Verb: r.Method,

pkg/oauth2/oauth2.go

+12-15
Original file line numberDiff line numberDiff line change
@@ -926,7 +926,7 @@ func (a *Authenticator) Callback(w http.ResponseWriter, r *http.Request) {
926926
// Now we have done code exchange, we have access to the id_token and that
927927
// allows us to see if the user actually exists. If it doesn't then we
928928
// either deny entry or let them signup.
929-
user, err := a.rbac.GetUser(r.Context(), idToken.Email.Email)
929+
user, err := a.rbac.GetUserByEmail(r.Context(), idToken.Email.Email)
930930
if err != nil {
931931
if !goerrors.Is(err, rbac.ErrResourceReference) {
932932
redirector.raise(ErrorServerError, "user lookup failure")
@@ -1275,7 +1275,7 @@ func (a *Authenticator) Onboard(w http.ResponseWriter, r *http.Request) {
12751275
return
12761276
}
12771277

1278-
shadowUser, err := a.rbac.GetUser(r.Context(), state.IDToken.Email.Email)
1278+
shadowUser, err := a.rbac.GetUserByEmail(r.Context(), state.IDToken.Email.Email)
12791279
if err != nil {
12801280
redirector.raise(ErrorServerError, "failed to read shadow user")
12811281
return
@@ -1399,7 +1399,7 @@ func oidcHash(value string) string {
13991399
}
14001400

14011401
// oidcIDToken builds an OIDC ID token.
1402-
func (a *Authenticator) oidcIDToken(r *http.Request, idToken *oidc.IDToken, query url.Values, expiry time.Duration, atHash string, lastAuthenticationTime time.Time) (*string, error) {
1402+
func (a *Authenticator) oidcIDToken(r *http.Request, userID string, idToken *oidc.IDToken, query url.Values, expiry time.Duration, atHash string, lastAuthenticationTime time.Time) (*string, error) {
14031403
scope := strings.Split(query.Get("scope"), " ")
14041404

14051405
//nolint:nilnil
@@ -1409,9 +1409,8 @@ func (a *Authenticator) oidcIDToken(r *http.Request, idToken *oidc.IDToken, quer
14091409

14101410
claims := &oidc.IDToken{
14111411
Claims: jwt.Claims{
1412-
Issuer: "https://" + r.Host,
1413-
// TODO: we should use the user ID.
1414-
Subject: idToken.Email.Email,
1412+
Issuer: "https://" + r.Host,
1413+
Subject: userID,
14151414
Audience: []string{
14161415
query.Get("client_id"),
14171416
},
@@ -1480,8 +1479,8 @@ func (a *Authenticator) validateClientSecret(r *http.Request, query url.Values)
14801479
}
14811480

14821481
// revokeSession revokes all tokens for a clientID.
1483-
func (a *Authenticator) revokeSession(ctx context.Context, clientID, codeID, subject string) error {
1484-
user, err := a.rbac.GetActiveUser(ctx, subject)
1482+
func (a *Authenticator) revokeSession(ctx context.Context, clientID, codeID, userID string) error {
1483+
user, err := a.rbac.GetActiveUserByID(ctx, userID)
14851484
if err != nil {
14861485
return errors.OAuth2ServerError("failed to lookup user").WithError(err)
14871486
}
@@ -1542,7 +1541,7 @@ func (a *Authenticator) TokenAuthorizationCode(w http.ResponseWriter, r *http.Re
15421541
// authentication code, we just clear out anything associated with the client
15431542
// session.
15441543
if _, ok := a.codeCache.Get(codeRaw); !ok {
1545-
_ = a.revokeSession(r.Context(), clientID, code.ID, code.IDToken.Email.Email)
1544+
_ = a.revokeSession(r.Context(), clientID, code.ID, code.UserID)
15461545

15471546
return nil, errors.OAuth2InvalidGrant("code is not present in cache")
15481547
}
@@ -1552,12 +1551,10 @@ func (a *Authenticator) TokenAuthorizationCode(w http.ResponseWriter, r *http.Re
15521551
info := &IssueInfo{
15531552
Issuer: "https://" + r.Host,
15541553
Audience: r.Host,
1555-
// TODO: we should probably use the user ID here.
1556-
Subject: code.IDToken.Email.Email,
1557-
Type: TokenTypeFederated,
1554+
Subject: code.UserID,
1555+
Type: TokenTypeFederated,
15581556
Federated: &FederatedClaims{
15591557
ClientID: clientID,
1560-
UserID: code.UserID,
15611558
Provider: code.OAuth2Provider,
15621559
Scope: NewScope(clientQuery.Get("scope")),
15631560
},
@@ -1571,7 +1568,7 @@ func (a *Authenticator) TokenAuthorizationCode(w http.ResponseWriter, r *http.Re
15711568
}
15721569

15731570
// Handle OIDC.
1574-
idToken, err := a.oidcIDToken(r, code.IDToken, clientQuery, a.options.AccessTokenDuration, oidcHash(tokens.AccessToken), tokens.LastAuthenticationTime)
1571+
idToken, err := a.oidcIDToken(r, code.UserID, code.IDToken, clientQuery, a.options.AccessTokenDuration, oidcHash(tokens.AccessToken), tokens.LastAuthenticationTime)
15751572
if err != nil {
15761573
return nil, err
15771574
}
@@ -1625,7 +1622,7 @@ func (a *Authenticator) validateRefreshToken(ctx context.Context, r *http.Reques
16251622
return err
16261623
}
16271624

1628-
user, err := a.rbac.GetActiveUser(ctx, claims.Claims.Subject)
1625+
user, err := a.rbac.GetActiveUserByID(ctx, claims.Claims.Subject)
16291626
if err != nil {
16301627
return errors.OAuth2ServerError("failed to lookup user").WithError(err)
16311628
}

pkg/oauth2/oauth2_test.go

+5-7
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,11 @@ func TestTokens(t *testing.T) {
9999
time.Sleep(2 * josetesting.RefreshPeriod)
100100

101101
issueInfo := &oauth2.IssueInfo{
102-
Issuer: "https://foo.com",
103-
Audience: "foo.com",
104-
Subject: "[email protected]",
105-
Type: oauth2.TokenTypeFederated,
106-
Federated: &oauth2.FederatedClaims{
107-
UserID: "fake",
108-
},
102+
Issuer: "https://foo.com",
103+
Audience: "foo.com",
104+
Subject: "fake",
105+
Type: oauth2.TokenTypeFederated,
106+
Federated: &oauth2.FederatedClaims{},
109107
}
110108

111109
tokens, err := authenticator.Issue(ctx, issueInfo)

pkg/oauth2/tokens.go

+2-6
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,6 @@ type FederatedClaims struct {
6060
Provider string `json:"idp"`
6161
// ClientID is the oauth2 client that the user is using.
6262
ClientID string `json:"cid"`
63-
// UserID is set when the token is issued to a user.
64-
// TODO: this should be the subject.
65-
UserID string `json:"uid"`
6663
// Scope is the set of scopes requested by the client, and is used to
6764
// populate the userinfo response.
6865
Scope Scope `json:"sco"`
@@ -233,7 +230,7 @@ func (a *Authenticator) Issue(ctx context.Context, info *IssueInfo) (*Tokens, er
233230
}
234231

235232
if info.Federated != nil {
236-
user, err := a.getUser(ctx, info.Federated.UserID)
233+
user, err := a.getUser(ctx, info.Subject)
237234
if err != nil {
238235
return nil, err
239236
}
@@ -364,8 +361,7 @@ func (a *Authenticator) verifyUserSession(ctx context.Context, info *VerifyInfo,
364361
return nil
365362
}
366363

367-
// TODO: the subject should be the user ID anyway...
368-
user, err := a.rbac.GetActiveUser(ctx, claims.Subject)
364+
user, err := a.rbac.GetActiveUserByID(ctx, claims.Subject)
369365
if err != nil {
370366
return err
371367
}

pkg/rbac/rbac.go

+15-5
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,15 @@ func New(client client.Client, namespace string, options *Options) *RBAC {
6767
}
6868
}
6969

70-
func (r *RBAC) GetUser(ctx context.Context, subject string) (*unikornv1.User, error) {
70+
func (r *RBAC) GetUserByEmail(ctx context.Context, email string) (*unikornv1.User, error) {
7171
result := &unikornv1.UserList{}
7272

7373
if err := r.client.List(ctx, result, &client.ListOptions{}); err != nil {
7474
return nil, err
7575
}
7676

7777
index := slices.IndexFunc(result.Items, func(user unikornv1.User) bool {
78-
return user.Spec.Subject == subject
78+
return user.Spec.Subject == email
7979
})
8080

8181
if index < 0 {
@@ -85,9 +85,19 @@ func (r *RBAC) GetUser(ctx context.Context, subject string) (*unikornv1.User, er
8585
return &result.Items[index], nil
8686
}
8787

88+
func (r *RBAC) GetUserByID(ctx context.Context, userID string) (*unikornv1.User, error) {
89+
result := &unikornv1.User{}
90+
91+
if err := r.client.Get(ctx, client.ObjectKey{Namespace: r.namespace, Name: userID}, result); err != nil {
92+
return nil, err
93+
}
94+
95+
return result, nil
96+
}
97+
8898
// GetActiveUser returns a user that match the subject and is active.
89-
func (r *RBAC) GetActiveUser(ctx context.Context, subject string) (*unikornv1.User, error) {
90-
user, err := r.GetUser(ctx, subject)
99+
func (r *RBAC) GetActiveUserByID(ctx context.Context, userID string) (*unikornv1.User, error) {
100+
user, err := r.GetUserByID(ctx, userID)
91101
if err != nil {
92102
return nil, err
93103
}
@@ -398,7 +408,7 @@ func (r *RBAC) GetACL(ctx context.Context, organizationID string) (*openapi.Acl,
398408
default:
399409
// A subject may be part of any organization's group, so look for that user
400410
// and a record that indicates they are part of an organization.
401-
user, err := r.GetActiveUser(ctx, info.Userinfo.Sub)
411+
user, err := r.GetActiveUserByID(ctx, info.Userinfo.Sub)
402412
if err != nil {
403413
return nil, err
404414
}

0 commit comments

Comments
 (0)