Skip to content

Commit 1189fe7

Browse files
committed
add api endpoints
1 parent c9df165 commit 1189fe7

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

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)