Skip to content

Commit 1ce6bed

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 41c96cf commit 1ce6bed

File tree

7 files changed

+52
-36
lines changed

7 files changed

+52
-36
lines changed

pkg/handler/organizations/client.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ func (c *Client) getUserbyEmail(ctx context.Context, rbacClient *rbac.RBAC, info
171171
}
172172
}
173173

174-
user, err := rbacClient.GetActiveUser(ctx, email)
174+
user, err := rbacClient.GetActiveUserByEmail(ctx, email)
175175
if err != nil {
176176
return nil, errors.HTTPNotFound().WithError(err)
177177
}
@@ -202,7 +202,7 @@ func (c *Client) organizationIDs(ctx context.Context, rbacClient *rbac.RBAC, ema
202202
return nil, err
203203
}
204204
} else {
205-
user, err = rbacClient.GetActiveUser(ctx, info.Userinfo.Sub)
205+
user, err = rbacClient.GetActiveUserByID(ctx, info.Userinfo.Sub)
206206
if err != nil {
207207
return nil, errors.HTTPNotFound().WithError(err)
208208
}

pkg/middleware/audit/logging.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ func (l *Logger) ServeHTTP(w http.ResponseWriter, r *http.Request) {
143143
},
144144
"actor", &Actor{
145145
Subject: info.Userinfo.Sub,
146+
Email: info.Userinfo.Email,
146147
},
147148
"operation", &Operation{
148149
Verb: r.Method,

pkg/middleware/audit/types.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@ type Component struct {
2222
}
2323

2424
type Actor struct {
25-
Subject string `json:"subject"`
25+
Subject string `json:"subject"`
26+
Email *string `json:"email,omitempty"`
2627
}
2728

2829
type Resource struct {

pkg/oauth2/oauth2.go

Lines changed: 12 additions & 15 deletions
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.Subject)
1625+
user, err := a.rbac.GetActiveUserByID(ctx, claims.Subject)
16291626
if err != nil {
16301627
return errors.OAuth2ServerError("failed to lookup user").WithError(err)
16311628
}

pkg/oauth2/oauth2_test.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,13 +97,11 @@ func TestTokens(t *testing.T) {
9797
time.Sleep(2 * josetesting.RefreshPeriod)
9898

9999
issueInfo := &oauth2.IssueInfo{
100-
Issuer: "https://foo.com",
101-
Audience: "foo.com",
102-
Subject: "[email protected]",
103-
Type: oauth2.TokenTypeFederated,
104-
Federated: &oauth2.FederatedClaims{
105-
UserID: "fake",
106-
},
100+
Issuer: "https://foo.com",
101+
Audience: "foo.com",
102+
Subject: "fake",
103+
Type: oauth2.TokenTypeFederated,
104+
Federated: &oauth2.FederatedClaims{},
107105
}
108106

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

pkg/oauth2/tokens.go

Lines changed: 2 additions & 6 deletions
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

Lines changed: 28 additions & 5 deletions
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,32 @@ func (r *RBAC) GetUser(ctx context.Context, subject string) (*unikornv1.User, er
8585
return &result.Items[index], nil
8686
}
8787

88+
func (r *RBAC) GetActiveUserByEmail(ctx context.Context, email string) (*unikornv1.User, error) {
89+
user, err := r.GetUserByEmail(ctx, email)
90+
if err != nil {
91+
return nil, err
92+
}
93+
94+
if user.Spec.State != unikornv1.UserStateActive {
95+
return nil, fmt.Errorf("%w: user is not active", ErrResourceReference)
96+
}
97+
98+
return user, nil
99+
}
100+
101+
func (r *RBAC) GetUserByID(ctx context.Context, userID string) (*unikornv1.User, error) {
102+
result := &unikornv1.User{}
103+
104+
if err := r.client.Get(ctx, client.ObjectKey{Namespace: r.namespace, Name: userID}, result); err != nil {
105+
return nil, err
106+
}
107+
108+
return result, nil
109+
}
110+
88111
// 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)
112+
func (r *RBAC) GetActiveUserByID(ctx context.Context, userID string) (*unikornv1.User, error) {
113+
user, err := r.GetUserByID(ctx, userID)
91114
if err != nil {
92115
return nil, err
93116
}
@@ -398,7 +421,7 @@ func (r *RBAC) GetACL(ctx context.Context, organizationID string) (*openapi.Acl,
398421
default:
399422
// A subject may be part of any organization's group, so look for that user
400423
// and a record that indicates they are part of an organization.
401-
user, err := r.GetActiveUser(ctx, info.Userinfo.Sub)
424+
user, err := r.GetActiveUserByID(ctx, info.Userinfo.Sub)
402425
if err != nil {
403426
return nil, err
404427
}

0 commit comments

Comments
 (0)