Skip to content

Commit f0832f1

Browse files
committed
add api endpoints
1 parent 0b70d36 commit f0832f1

File tree

5 files changed

+100
-3
lines changed

5 files changed

+100
-3
lines changed

coderd/database/oidcclaims_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,15 @@ func TestOIDCClaims(t *testing.T) {
109109
charlie,
110110
)...,
111111
).Do()
112+
orgC := dbfake.Organization(t, db).Members().Do()
112113

113114
// Verify the OIDC claim fields
114115
always := []string{"array", "map", "nil", "number"}
115116
expectA := append([]string{"sub", "alice-id", "bob-id", "bob-info"}, always...)
116117
expectB := append([]string{"sub", "bob-id", "bob-info", "charlie-id", "charlie-info"}, always...)
117118
requireClaims(t, db, orgA.Org.ID, expectA)
118119
requireClaims(t, db, orgB.Org.ID, expectB)
120+
requireClaims(t, db, orgC.Org.ID, []string{})
119121
requireClaims(t, db, uuid.Nil, slice.Unique(append(expectA, expectB...)))
120122
}
121123

codersdk/idpsync.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,3 +137,31 @@ func (c *Client) PatchOrganizationIDPSyncSettings(ctx context.Context, req Organ
137137
var resp OrganizationSyncSettings
138138
return resp, json.NewDecoder(res.Body).Decode(&resp)
139139
}
140+
141+
func (c *Client) GetAvailableIDPSyncFields(ctx context.Context) ([]string, error) {
142+
res, err := c.Request(ctx, http.MethodGet, "/api/v2/settings/idpsync/available-fields", nil)
143+
if err != nil {
144+
return nil, xerrors.Errorf("make request: %w", err)
145+
}
146+
defer res.Body.Close()
147+
148+
if res.StatusCode != http.StatusOK {
149+
return nil, ReadBodyAsError(res)
150+
}
151+
var resp []string
152+
return resp, json.NewDecoder(res.Body).Decode(&resp)
153+
}
154+
155+
func (c *Client) GetOrganizationAvailableIDPSyncFields(ctx context.Context, orgID string) ([]string, error) {
156+
res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/settings/idpsync/available-fields", orgID), nil)
157+
if err != nil {
158+
return nil, xerrors.Errorf("make request: %w", err)
159+
}
160+
defer res.Body.Close()
161+
162+
if res.StatusCode != http.StatusOK {
163+
return nil, ReadBodyAsError(res)
164+
}
165+
var resp []string
166+
return resp, json.NewDecoder(res.Body).Decode(&resp)
167+
}

enterprise/coderd/coderd.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -291,9 +291,12 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
291291
r.Use(
292292
apiKeyMiddleware,
293293
)
294-
r.Route("/settings/idpsync/organization", func(r chi.Router) {
295-
r.Get("/", api.organizationIDPSyncSettings)
296-
r.Patch("/", api.patchOrganizationIDPSyncSettings)
294+
r.Route("/settings/idpsync", func(r chi.Router) {
295+
r.Route("/organization", func(r chi.Router) {
296+
r.Get("/", api.organizationIDPSyncSettings)
297+
r.Patch("/", api.patchOrganizationIDPSyncSettings)
298+
})
299+
r.Get("/available-fields", api.deploymentIDPSyncClaimFields)
297300
})
298301
})
299302

@@ -303,6 +306,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
303306
httpmw.ExtractOrganizationParam(api.Database),
304307
)
305308
r.Route("/organizations/{organization}/settings", func(r chi.Router) {
309+
r.Get("/idpsync/available-fields", api.organizationIDPSyncClaimFields)
306310
r.Get("/idpsync/groups", api.groupIDPSyncSettings)
307311
r.Patch("/idpsync/groups", api.patchGroupIDPSyncSettings)
308312
r.Get("/idpsync/roles", api.roleIDPSyncSettings)

enterprise/coderd/idpsync.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package coderd
22

33
import (
4+
"fmt"
45
"net/http"
56

7+
"github.com/google/uuid"
8+
69
"github.com/coder/coder/v2/coderd/database/dbauthz"
710
"github.com/coder/coder/v2/coderd/httpapi"
811
"github.com/coder/coder/v2/coderd/httpmw"
@@ -255,3 +258,50 @@ func (api *API) patchOrganizationIDPSyncSettings(rw http.ResponseWriter, r *http
255258
AssignDefault: settings.AssignDefault,
256259
})
257260
}
261+
262+
// @Summary Get the available idp sync claim fields
263+
// @ID get-the-available-idp-sync-claim-fields
264+
// @Security CoderSessionToken
265+
// @Produce json
266+
// @Tags Enterprise
267+
// @Param organization path string true "Organization ID" format(uuid)
268+
// @Success 200 {array} string
269+
// @Router /organizations/{organization}/settings/idpsync/available-fields [get]
270+
func (api *API) organizationIDPSyncClaimFields(rw http.ResponseWriter, r *http.Request) {
271+
org := httpmw.OrganizationParam(r)
272+
api.idpSyncClaimFields(org.ID, rw, r)
273+
}
274+
275+
// @Summary Get the available idp sync claim fields
276+
// @ID get-the-available-idp-sync-claim-fields
277+
// @Security CoderSessionToken
278+
// @Produce json
279+
// @Tags Enterprise
280+
// @Param organization path string true "Organization ID" format(uuid)
281+
// @Success 200 {array} string
282+
// @Router /settings/idpsync/available-fields [get]
283+
func (api *API) deploymentIDPSyncClaimFields(rw http.ResponseWriter, r *http.Request) {
284+
// nil uuid implies all organizations
285+
api.idpSyncClaimFields(uuid.Nil, rw, r)
286+
}
287+
288+
func (api *API) idpSyncClaimFields(orgID uuid.UUID, rw http.ResponseWriter, r *http.Request) {
289+
ctx := r.Context()
290+
291+
fields, err := api.Database.OIDCClaimFields(ctx, orgID)
292+
if httpapi.IsUnauthorizedError(err) {
293+
// Give a helpful error. The user could read the org, so this does not
294+
// leak anything.
295+
httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{
296+
Message: "You do not have permission to view the available IDP fields",
297+
Detail: fmt.Sprintf("%s.read permission is required", rbac.ResourceIdpsyncSettings.Type),
298+
})
299+
return
300+
}
301+
if err != nil {
302+
httpapi.InternalServerError(rw, err)
303+
return
304+
}
305+
306+
httpapi.Write(ctx, rw, http.StatusOK, fields)
307+
}

enterprise/coderd/userauth_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,19 @@ func TestUserOIDC(t *testing.T) {
165165
user, err := userClient.User(ctx, codersdk.Me)
166166
require.NoError(t, err)
167167

168+
// Then: the available sync fields should be "email" and "organization"
169+
fields, err := runner.AdminClient.GetAvailableIDPSyncFields(ctx)
170+
require.NoError(t, err)
171+
require.ElementsMatch(t, []string{
172+
"aud", "exp", "iss", // Always included from jwt
173+
"email", "organization",
174+
}, fields)
175+
176+
// This should be the same as above
177+
orgFields, err := runner.AdminClient.GetOrganizationAvailableIDPSyncFields(ctx, orgOne.ID.String())
178+
require.NoError(t, err)
179+
require.ElementsMatch(t, fields, orgFields)
180+
168181
// When: they are manually added to the fourth organization, a new sync
169182
// should remove them.
170183
_, err = runner.AdminClient.PostOrganizationMember(ctx, orgThree.ID, "alice")

0 commit comments

Comments
 (0)