Skip to content

Commit b6d0b77

Browse files
authored
chore: implement user link claims as a typed golang object (coder#15502)
Move claims from a `debug` column to an actual typed column to be used. This does not functionally change anything, it just adds some Go typing to build on.
1 parent 6b1fafb commit b6d0b77

19 files changed

+105
-65
lines changed

coderd/coderdtest/oidctest/helper.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package oidctest
33
import (
44
"context"
55
"database/sql"
6-
"encoding/json"
76
"net/http"
87
"net/url"
98
"testing"
@@ -89,7 +88,7 @@ func (*LoginHelper) ExpireOauthToken(t *testing.T, db database.Store, user *code
8988
OAuthExpiry: time.Now().Add(time.Hour * -1),
9089
UserID: link.UserID,
9190
LoginType: link.LoginType,
92-
DebugContext: json.RawMessage("{}"),
91+
Claims: database.UserLinkClaims{},
9392
})
9493
require.NoError(t, err, "expire user link")
9594

coderd/database/dbauthz/dbauthz_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -1281,7 +1281,7 @@ func (s *MethodTestSuite) TestUser() {
12811281
OAuthExpiry: link.OAuthExpiry,
12821282
UserID: link.UserID,
12831283
LoginType: link.LoginType,
1284-
DebugContext: json.RawMessage("{}"),
1284+
Claims: database.UserLinkClaims{},
12851285
}).Asserts(rbac.ResourceUserObject(link.UserID), policy.ActionUpdatePersonal).Returns(link)
12861286
}))
12871287
s.Run("UpdateUserRoles", s.Subtest(func(db database.Store, check *expects) {

coderd/database/dbgen/dbgen.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,7 @@ func UserLink(t testing.TB, db database.Store, orig database.UserLink) database.
726726
OAuthRefreshToken: takeFirst(orig.OAuthRefreshToken, uuid.NewString()),
727727
OAuthRefreshTokenKeyID: takeFirst(orig.OAuthRefreshTokenKeyID, sql.NullString{}),
728728
OAuthExpiry: takeFirst(orig.OAuthExpiry, dbtime.Now().Add(time.Hour*24)),
729-
DebugContext: takeFirstSlice(orig.DebugContext, json.RawMessage("{}")),
729+
Claims: orig.Claims,
730730
})
731731

732732
require.NoError(t, err, "insert link")

coderd/database/dbmem/dbmem.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -7857,7 +7857,7 @@ func (q *FakeQuerier) InsertUserLink(_ context.Context, args database.InsertUser
78577857
OAuthRefreshToken: args.OAuthRefreshToken,
78587858
OAuthRefreshTokenKeyID: args.OAuthRefreshTokenKeyID,
78597859
OAuthExpiry: args.OAuthExpiry,
7860-
DebugContext: args.DebugContext,
7860+
Claims: args.Claims,
78617861
}
78627862

78637863
q.userLinks = append(q.userLinks, link)
@@ -9318,7 +9318,7 @@ func (q *FakeQuerier) UpdateUserLink(_ context.Context, params database.UpdateUs
93189318
link.OAuthRefreshToken = params.OAuthRefreshToken
93199319
link.OAuthRefreshTokenKeyID = params.OAuthRefreshTokenKeyID
93209320
link.OAuthExpiry = params.OAuthExpiry
9321-
link.DebugContext = params.DebugContext
9321+
link.Claims = params.Claims
93229322

93239323
q.userLinks[i] = link
93249324
return link, nil

coderd/database/dump.sql

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ALTER TABLE user_links RENAME COLUMN claims TO debug_context;
2+
3+
COMMENT ON COLUMN user_links.debug_context IS 'Debug information includes information like id_token and userinfo claims.';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
ALTER TABLE user_links RENAME COLUMN debug_context TO claims;
2+
3+
COMMENT ON COLUMN user_links.claims IS 'Claims from the IDP for the linked user. Includes both id_token and userinfo claims. ';

coderd/database/models.go

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries.sql.go

+33-33
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/queries/user_links.sql

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ INSERT INTO
3232
oauth_refresh_token,
3333
oauth_refresh_token_key_id,
3434
oauth_expiry,
35-
debug_context
35+
claims
3636
)
3737
VALUES
3838
( $1, $2, $3, $4, $5, $6, $7, $8, $9 ) RETURNING *;
@@ -54,6 +54,6 @@ SET
5454
oauth_refresh_token = $3,
5555
oauth_refresh_token_key_id = $4,
5656
oauth_expiry = $5,
57-
debug_context = $6
57+
claims = $6
5858
WHERE
5959
user_id = $7 AND login_type = $8 RETURNING *;

coderd/database/sqlc.yaml

+3
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ sql:
7979
- column: "provisioner_job_stats.*_secs"
8080
go_type:
8181
type: "float64"
82+
- column: "user_links.claims"
83+
go_type:
84+
type: "UserLinkClaims"
8285
rename:
8386
group_member: GroupMemberTable
8487
group_members_expanded: GroupMember

coderd/database/types.go

+22
Original file line numberDiff line numberDiff line change
@@ -207,3 +207,25 @@ func (p *AgentIDNamePair) Scan(src interface{}) error {
207207
func (p AgentIDNamePair) Value() (driver.Value, error) {
208208
return fmt.Sprintf(`(%s,%s)`, p.ID.String(), p.Name), nil
209209
}
210+
211+
// UserLinkClaims is the returned IDP claims for a given user link.
212+
// These claims are fetched at login time. These are the claims that were
213+
// used for IDP sync.
214+
type UserLinkClaims struct {
215+
IDTokenClaims map[string]interface{} `json:"id_token_claims"`
216+
UserInfoClaims map[string]interface{} `json:"user_info_claims"`
217+
}
218+
219+
func (a *UserLinkClaims) Scan(src interface{}) error {
220+
switch v := src.(type) {
221+
case string:
222+
return json.Unmarshal([]byte(v), &a)
223+
case []byte:
224+
return json.Unmarshal(v, &a)
225+
}
226+
return xerrors.Errorf("unexpected type %T", src)
227+
}
228+
229+
func (a UserLinkClaims) Value() (driver.Value, error) {
230+
return json.Marshal(a)
231+
}

coderd/httpmw/apikey.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ func ExtractAPIKey(rw http.ResponseWriter, r *http.Request, cfg ExtractAPIKeyCon
377377
OAuthExpiry: link.OAuthExpiry,
378378
// Refresh should keep the same debug context because we use
379379
// the original claims for the group/role sync.
380-
DebugContext: link.DebugContext,
380+
Claims: link.Claims,
381381
})
382382
if err != nil {
383383
return write(http.StatusInternalServerError, codersdk.Response{

coderd/provisionerdserver/provisionerdserver.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -2083,7 +2083,7 @@ func obtainOIDCAccessToken(ctx context.Context, db database.Store, oidcConfig pr
20832083
OAuthRefreshToken: link.OAuthRefreshToken,
20842084
OAuthRefreshTokenKeyID: sql.NullString{}, // set by dbcrypt if required
20852085
OAuthExpiry: link.OAuthExpiry,
2086-
DebugContext: link.DebugContext,
2086+
Claims: link.Claims,
20872087
})
20882088
if err != nil {
20892089
return "", xerrors.Errorf("update user link: %w", err)

coderd/userauth.go

+7-11
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package coderd
33
import (
44
"context"
55
"database/sql"
6-
"encoding/json"
76
"errors"
87
"fmt"
98
"net/http"
@@ -966,7 +965,7 @@ func (api *API) userOAuth2Github(rw http.ResponseWriter, r *http.Request) {
966965
Username: username,
967966
AvatarURL: ghUser.GetAvatarURL(),
968967
Name: normName,
969-
DebugContext: OauthDebugContext{},
968+
UserClaims: database.UserLinkClaims{},
970969
GroupSync: idpsync.GroupParams{
971970
SyncEntitled: false,
972971
},
@@ -1324,7 +1323,7 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
13241323
OrganizationSync: orgSync,
13251324
GroupSync: groupSync,
13261325
RoleSync: roleSync,
1327-
DebugContext: OauthDebugContext{
1326+
UserClaims: database.UserLinkClaims{
13281327
IDTokenClaims: idtokenClaims,
13291328
UserInfoClaims: userInfoClaims,
13301329
},
@@ -1421,7 +1420,9 @@ type oauthLoginParams struct {
14211420
GroupSync idpsync.GroupParams
14221421
RoleSync idpsync.RoleParams
14231422

1424-
DebugContext OauthDebugContext
1423+
// UserClaims should only be populated for OIDC logins.
1424+
// It is used to save the user's claims on login.
1425+
UserClaims database.UserLinkClaims
14251426

14261427
commitLock sync.Mutex
14271428
initAuditRequest func(params *audit.RequestParams) *audit.Request[database.User]
@@ -1591,11 +1592,6 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
15911592
dormantConvertAudit.New = user
15921593
}
15931594

1594-
debugContext, err := json.Marshal(params.DebugContext)
1595-
if err != nil {
1596-
return xerrors.Errorf("marshal debug context: %w", err)
1597-
}
1598-
15991595
if link.UserID == uuid.Nil {
16001596
//nolint:gocritic // System needs to insert the user link (linked_id, oauth_token, oauth_expiry).
16011597
link, err = tx.InsertUserLink(dbauthz.AsSystemRestricted(ctx), database.InsertUserLinkParams{
@@ -1607,7 +1603,7 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
16071603
OAuthRefreshToken: params.State.Token.RefreshToken,
16081604
OAuthRefreshTokenKeyID: sql.NullString{}, // set by dbcrypt if required
16091605
OAuthExpiry: params.State.Token.Expiry,
1610-
DebugContext: debugContext,
1606+
Claims: params.UserClaims,
16111607
})
16121608
if err != nil {
16131609
return xerrors.Errorf("insert user link: %w", err)
@@ -1624,7 +1620,7 @@ func (api *API) oauthLogin(r *http.Request, params *oauthLoginParams) ([]*http.C
16241620
OAuthRefreshToken: params.State.Token.RefreshToken,
16251621
OAuthRefreshTokenKeyID: sql.NullString{}, // set by dbcrypt if required
16261622
OAuthExpiry: params.State.Token.Expiry,
1627-
DebugContext: debugContext,
1623+
Claims: params.UserClaims,
16281624
})
16291625
if err != nil {
16301626
return xerrors.Errorf("update user link: %w", err)

0 commit comments

Comments
 (0)