From 43279be9d1e7bb340be43019e08979fb3dcf8046 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Tue, 7 Jan 2025 21:42:38 +0000 Subject: [PATCH 1/4] chore: add api endpoints to get idp field values --- coderd/apidoc/docs.go | 92 ++++++++++++++++++++++++++++++ coderd/apidoc/swagger.json | 84 +++++++++++++++++++++++++++ codersdk/idpsync.go | 28 +++++++++ docs/reference/api/enterprise.md | 80 ++++++++++++++++++++++++++ enterprise/coderd/coderd.go | 8 +++ enterprise/coderd/idpsync.go | 55 ++++++++++++++++++ enterprise/coderd/userauth_test.go | 8 +++ 7 files changed, 355 insertions(+) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 0b4778a20cbce..a23488052007a 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -3170,6 +3170,52 @@ const docTemplate = `{ } } }, + "/organizations/{organization}/settings/idpsync/field-values": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Enterprise" + ], + "summary": "Get the organization idp sync claim field values", + "operationId": "get-the-organization-idp-sync-claim-field-values", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Organization ID", + "name": "organization", + "in": "path", + "required": true + }, + { + "type": "string", + "format": "string", + "description": "Claim Field", + "name": "claimField", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, "/organizations/{organization}/settings/idpsync/groups": { "get": { "security": [ @@ -3952,6 +3998,52 @@ const docTemplate = `{ } } }, + "/settings/idpsync/field-values": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": [ + "application/json" + ], + "tags": [ + "Enterprise" + ], + "summary": "Get the idp sync claim field values", + "operationId": "get-the-idp-sync-claim-field-values", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Organization ID", + "name": "organization", + "in": "path", + "required": true + }, + { + "type": "string", + "format": "string", + "description": "Claim Field", + "name": "claimField", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, "/settings/idpsync/organization": { "get": { "security": [ diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index e9fbb8a5a9083..67d394ae67c25 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -2788,6 +2788,48 @@ } } }, + "/organizations/{organization}/settings/idpsync/field-values": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": ["application/json"], + "tags": ["Enterprise"], + "summary": "Get the organization idp sync claim field values", + "operationId": "get-the-organization-idp-sync-claim-field-values", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Organization ID", + "name": "organization", + "in": "path", + "required": true + }, + { + "type": "string", + "format": "string", + "description": "Claim Field", + "name": "claimField", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, "/organizations/{organization}/settings/idpsync/groups": { "get": { "security": [ @@ -3478,6 +3520,48 @@ } } }, + "/settings/idpsync/field-values": { + "get": { + "security": [ + { + "CoderSessionToken": [] + } + ], + "produces": ["application/json"], + "tags": ["Enterprise"], + "summary": "Get the idp sync claim field values", + "operationId": "get-the-idp-sync-claim-field-values", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "Organization ID", + "name": "organization", + "in": "path", + "required": true + }, + { + "type": "string", + "format": "string", + "description": "Claim Field", + "name": "claimField", + "in": "query", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + } + } + }, "/settings/idpsync/organization": { "get": { "security": [ diff --git a/codersdk/idpsync.go b/codersdk/idpsync.go index 3a2e707ccb623..bc54587b3795b 100644 --- a/codersdk/idpsync.go +++ b/codersdk/idpsync.go @@ -163,3 +163,31 @@ func (c *Client) GetOrganizationAvailableIDPSyncFields(ctx context.Context, orgI var resp []string return resp, json.NewDecoder(res.Body).Decode(&resp) } + +func (c *Client) GetIDPSyncFieldValues(ctx context.Context, claimField string) ([]string, error) { + res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/settings/idpsync/field-values?claimField=%s", claimField), nil) + if err != nil { + return nil, xerrors.Errorf("make request: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return nil, ReadBodyAsError(res) + } + var resp []string + return resp, json.NewDecoder(res.Body).Decode(&resp) +} + +func (c *Client) GetOrganizationIDPSyncFieldValues(ctx context.Context, orgID string, claimField string) ([]string, error) { + res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/settings/idpsync/field-values?claimField=%s", orgID, claimField), nil) + if err != nil { + return nil, xerrors.Errorf("make request: %w", err) + } + defer res.Body.Close() + + if res.StatusCode != http.StatusOK { + return nil, ReadBodyAsError(res) + } + var resp []string + return resp, json.NewDecoder(res.Body).Decode(&resp) +} diff --git a/docs/reference/api/enterprise.md b/docs/reference/api/enterprise.md index 21a46e92cba08..80515f3ee7bef 100644 --- a/docs/reference/api/enterprise.md +++ b/docs/reference/api/enterprise.md @@ -1830,6 +1830,46 @@ curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/setting To perform this operation, you must be authenticated. [Learn more](authentication.md). +## Get the organization idp sync claim field values + +### Code samples + +```shell +# Example request using curl +curl -X GET http://coder-server:8080/api/v2/organizations/{organization}/settings/idpsync/field-values?claimField=string \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`GET /organizations/{organization}/settings/idpsync/field-values` + +### Parameters + +| Name | In | Type | Required | Description | +|----------------|-------|----------------|----------|-----------------| +| `organization` | path | string(uuid) | true | Organization ID | +| `claimField` | query | string(string) | true | Claim Field | + +### Example responses + +> 200 Response + +```json +[ + "string" +] +``` + +### Responses + +| Status | Meaning | Description | Schema | +|--------|---------------------------------------------------------|-------------|-----------------| +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | array of string | + +

Response Schema

+ +To perform this operation, you must be authenticated. [Learn more](authentication.md). + ## Get group IdP Sync settings by organization ### Code samples @@ -2536,6 +2576,46 @@ curl -X GET http://coder-server:8080/api/v2/settings/idpsync/available-fields \ To perform this operation, you must be authenticated. [Learn more](authentication.md). +## Get the idp sync claim field values + +### Code samples + +```shell +# Example request using curl +curl -X GET http://coder-server:8080/api/v2/settings/idpsync/field-values?claimField=string \ + -H 'Accept: application/json' \ + -H 'Coder-Session-Token: API_KEY' +``` + +`GET /settings/idpsync/field-values` + +### Parameters + +| Name | In | Type | Required | Description | +|----------------|-------|----------------|----------|-----------------| +| `organization` | path | string(uuid) | true | Organization ID | +| `claimField` | query | string(string) | true | Claim Field | + +### Example responses + +> 200 Response + +```json +[ + "string" +] +``` + +### Responses + +| Status | Meaning | Description | Schema | +|--------|---------------------------------------------------------|-------------|-----------------| +| 200 | [OK](https://tools.ietf.org/html/rfc7231#section-6.3.1) | OK | array of string | + +

Response Schema

+ +To perform this operation, you must be authenticated. [Learn more](authentication.md). + ## Get organization IdP Sync settings ### Code samples diff --git a/enterprise/coderd/coderd.go b/enterprise/coderd/coderd.go index 71273ff97fd75..b3974077ab0b5 100644 --- a/enterprise/coderd/coderd.go +++ b/enterprise/coderd/coderd.go @@ -297,6 +297,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) { r.Patch("/", api.patchOrganizationIDPSyncSettings) }) r.Get("/available-fields", api.deploymentIDPSyncClaimFields) + r.Get("/field-values", api.deploymentIDPSyncClaimFieldValues) }) }) @@ -311,6 +312,13 @@ func New(ctx context.Context, options *Options) (_ *API, err error) { r.Patch("/idpsync/groups", api.patchGroupIDPSyncSettings) r.Get("/idpsync/roles", api.roleIDPSyncSettings) r.Patch("/idpsync/roles", api.patchRoleIDPSyncSettings) + r.Get("/idpsync/field-values", api.organizationIDPSyncClaimFieldValues) + // r.Route("/idpsync/field-values/{claimField}", func(r chi.Router) { + // r.Use( + // httpmw.ExtractClaimFieldParam(api.Database), + // ) + // r.Get("/", api.organizationIDPSyncClaimFieldValues) + // }) }) }) diff --git a/enterprise/coderd/idpsync.go b/enterprise/coderd/idpsync.go index e7346f8406844..73146181b966f 100644 --- a/enterprise/coderd/idpsync.go +++ b/enterprise/coderd/idpsync.go @@ -363,3 +363,58 @@ func (api *API) idpSyncClaimFields(orgID uuid.UUID, rw http.ResponseWriter, r *h httpapi.Write(ctx, rw, http.StatusOK, fields) } + +// @Summary Get the organization idp sync claim field values +// @ID get-the-organization-idp-sync-claim-field-values +// @Security CoderSessionToken +// @Produce json +// @Tags Enterprise +// @Param organization path string true "Organization ID" format(uuid) +// @Param claimField query string true "Claim Field" format(string) +// @Success 200 {array} string +// @Router /organizations/{organization}/settings/idpsync/field-values [get] +func (api *API) organizationIDPSyncClaimFieldValues(rw http.ResponseWriter, r *http.Request) { + org := httpmw.OrganizationParam(r) + claimField := r.URL.Query().Get("claimField") + api.idpSyncClaimFieldValues(org.ID, claimField, rw, r) +} + +// @Summary Get the idp sync claim field values +// @ID get-the-idp-sync-claim-field-values +// @Security CoderSessionToken +// @Produce json +// @Tags Enterprise +// @Param organization path string true "Organization ID" format(uuid) +// @Param claimField query string true "Claim Field" format(string) +// @Success 200 {array} string +// @Router /settings/idpsync/field-values [get] +func (api *API) deploymentIDPSyncClaimFieldValues(rw http.ResponseWriter, r *http.Request) { + claimField := r.URL.Query().Get("claimField") + // nil uuid implies all organizations + api.idpSyncClaimFieldValues(uuid.Nil, claimField, rw, r) +} + +func (api *API) idpSyncClaimFieldValues(orgID uuid.UUID, claimField string, rw http.ResponseWriter, r *http.Request) { + ctx := r.Context() + + fields, err := api.Database.OIDCClaimFieldValues(ctx, database.OIDCClaimFieldValuesParams{ + OrganizationID: orgID, + ClaimField: claimField, + }) + + if httpapi.IsUnauthorizedError(err) { + // Give a helpful error. The user could read the org, so this does not + // leak anything. + httpapi.Write(ctx, rw, http.StatusForbidden, codersdk.Response{ + Message: "You do not have permission to view the IDP claim field values", + Detail: fmt.Sprintf("%s.read permission is required", rbac.ResourceIdpsyncSettings.Type), + }) + return + } + if err != nil { + httpapi.InternalServerError(rw, err) + return + } + + httpapi.Write(ctx, rw, http.StatusOK, fields) +} diff --git a/enterprise/coderd/userauth_test.go b/enterprise/coderd/userauth_test.go index 28257078ebb36..d3e997608f316 100644 --- a/enterprise/coderd/userauth_test.go +++ b/enterprise/coderd/userauth_test.go @@ -178,6 +178,14 @@ func TestUserOIDC(t *testing.T) { require.NoError(t, err) require.ElementsMatch(t, fields, orgFields) + fieldValues, err := runner.AdminClient.GetIDPSyncFieldValues(ctx, "organization") + require.NoError(t, err) + require.ElementsMatch(t, []string{"first", "second"}, fieldValues) + + orgFieldValues, err := runner.AdminClient.GetOrganizationIDPSyncFieldValues(ctx, orgOne.ID.String(), "organization") + require.NoError(t, err) + require.ElementsMatch(t, []string{"first", "second"}, orgFieldValues) + // When: they are manually added to the fourth organization, a new sync // should remove them. _, err = runner.AdminClient.PostOrganizationMember(ctx, orgThree.ID, "alice") From faf5150a5fd61ef2f8334d1e8cb21b54deb80df6 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Tue, 7 Jan 2025 22:36:17 +0000 Subject: [PATCH 2/4] chore: removed unused code --- enterprise/coderd/coderd.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/enterprise/coderd/coderd.go b/enterprise/coderd/coderd.go index b3974077ab0b5..90042bf0997fc 100644 --- a/enterprise/coderd/coderd.go +++ b/enterprise/coderd/coderd.go @@ -313,12 +313,6 @@ func New(ctx context.Context, options *Options) (_ *API, err error) { r.Get("/idpsync/roles", api.roleIDPSyncSettings) r.Patch("/idpsync/roles", api.patchRoleIDPSyncSettings) r.Get("/idpsync/field-values", api.organizationIDPSyncClaimFieldValues) - // r.Route("/idpsync/field-values/{claimField}", func(r chi.Router) { - // r.Use( - // httpmw.ExtractClaimFieldParam(api.Database), - // ) - // r.Get("/", api.organizationIDPSyncClaimFieldValues) - // }) }) }) From a4558032c3e0ebc0096fb71b7beadbed3564dd48 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Wed, 8 Jan 2025 18:41:32 +0000 Subject: [PATCH 3/4] fix: url encode query param --- codersdk/idpsync.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/codersdk/idpsync.go b/codersdk/idpsync.go index bc54587b3795b..2cc1f51ee3011 100644 --- a/codersdk/idpsync.go +++ b/codersdk/idpsync.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "regexp" "github.com/google/uuid" @@ -165,7 +166,9 @@ func (c *Client) GetOrganizationAvailableIDPSyncFields(ctx context.Context, orgI } func (c *Client) GetIDPSyncFieldValues(ctx context.Context, claimField string) ([]string, error) { - res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/settings/idpsync/field-values?claimField=%s", claimField), nil) + qv := url.Values{} + qv.Add("claimField", claimField) + res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/settings/idpsync/field-values?%s", qv.Encode()), nil) if err != nil { return nil, xerrors.Errorf("make request: %w", err) } @@ -179,7 +182,9 @@ func (c *Client) GetIDPSyncFieldValues(ctx context.Context, claimField string) ( } func (c *Client) GetOrganizationIDPSyncFieldValues(ctx context.Context, orgID string, claimField string) ([]string, error) { - res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/settings/idpsync/field-values?claimField=%s", orgID, claimField), nil) + qv := url.Values{} + qv.Add("claimField", claimField) + res, err := c.Request(ctx, http.MethodGet, fmt.Sprintf("/api/v2/organizations/%s/settings/idpsync/field-values?%s", orgID, qv.Encode()), nil) if err != nil { return nil, xerrors.Errorf("make request: %w", err) } From 4ff34ccd70bd8c1e099e4c815250533c79930ab9 Mon Sep 17 00:00:00 2001 From: Jaayden Halko Date: Wed, 8 Jan 2025 18:42:58 +0000 Subject: [PATCH 4/4] fix: check for empty string in claimField --- enterprise/coderd/idpsync.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/enterprise/coderd/idpsync.go b/enterprise/coderd/idpsync.go index 73146181b966f..192d61ea996c6 100644 --- a/enterprise/coderd/idpsync.go +++ b/enterprise/coderd/idpsync.go @@ -375,8 +375,7 @@ func (api *API) idpSyncClaimFields(orgID uuid.UUID, rw http.ResponseWriter, r *h // @Router /organizations/{organization}/settings/idpsync/field-values [get] func (api *API) organizationIDPSyncClaimFieldValues(rw http.ResponseWriter, r *http.Request) { org := httpmw.OrganizationParam(r) - claimField := r.URL.Query().Get("claimField") - api.idpSyncClaimFieldValues(org.ID, claimField, rw, r) + api.idpSyncClaimFieldValues(org.ID, rw, r) } // @Summary Get the idp sync claim field values @@ -389,15 +388,21 @@ func (api *API) organizationIDPSyncClaimFieldValues(rw http.ResponseWriter, r *h // @Success 200 {array} string // @Router /settings/idpsync/field-values [get] func (api *API) deploymentIDPSyncClaimFieldValues(rw http.ResponseWriter, r *http.Request) { - claimField := r.URL.Query().Get("claimField") // nil uuid implies all organizations - api.idpSyncClaimFieldValues(uuid.Nil, claimField, rw, r) + api.idpSyncClaimFieldValues(uuid.Nil, rw, r) } -func (api *API) idpSyncClaimFieldValues(orgID uuid.UUID, claimField string, rw http.ResponseWriter, r *http.Request) { +func (api *API) idpSyncClaimFieldValues(orgID uuid.UUID, rw http.ResponseWriter, r *http.Request) { ctx := r.Context() - fields, err := api.Database.OIDCClaimFieldValues(ctx, database.OIDCClaimFieldValuesParams{ + claimField := r.URL.Query().Get("claimField") + if claimField == "" { + httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ + Message: "claimField query parameter is required", + }) + return + } + fieldValues, err := api.Database.OIDCClaimFieldValues(ctx, database.OIDCClaimFieldValuesParams{ OrganizationID: orgID, ClaimField: claimField, }) @@ -416,5 +421,5 @@ func (api *API) idpSyncClaimFieldValues(orgID uuid.UUID, claimField string, rw h return } - httpapi.Write(ctx, rw, http.StatusOK, fields) + httpapi.Write(ctx, rw, http.StatusOK, fieldValues) } pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy