chore: add query to fetch top level idp claim fields (#15525)

Adds an api endpoint to grab all available sync field options for IDP
sync. This is for autocomplete on idp sync forms. This is required for
organization admins to have some insight into the claim fields available
when configuring group/role sync.
This commit is contained in:
Steven Masley
2024-11-18 14:31:39 -06:00
committed by GitHub
parent 48bb452829
commit c3c23ed3d9
18 changed files with 679 additions and 10 deletions

View File

@ -291,9 +291,12 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
r.Use(
apiKeyMiddleware,
)
r.Route("/settings/idpsync/organization", func(r chi.Router) {
r.Get("/", api.organizationIDPSyncSettings)
r.Patch("/", api.patchOrganizationIDPSyncSettings)
r.Route("/settings/idpsync", func(r chi.Router) {
r.Route("/organization", func(r chi.Router) {
r.Get("/", api.organizationIDPSyncSettings)
r.Patch("/", api.patchOrganizationIDPSyncSettings)
})
r.Get("/available-fields", api.deploymentIDPSyncClaimFields)
})
})
@ -303,6 +306,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
httpmw.ExtractOrganizationParam(api.Database),
)
r.Route("/organizations/{organization}/settings", func(r chi.Router) {
r.Get("/idpsync/available-fields", api.organizationIDPSyncClaimFields)
r.Get("/idpsync/groups", api.groupIDPSyncSettings)
r.Patch("/idpsync/groups", api.patchGroupIDPSyncSettings)
r.Get("/idpsync/roles", api.roleIDPSyncSettings)

View File

@ -1,8 +1,11 @@
package coderd
import (
"fmt"
"net/http"
"github.com/google/uuid"
"github.com/coder/coder/v2/coderd/database/dbauthz"
"github.com/coder/coder/v2/coderd/httpapi"
"github.com/coder/coder/v2/coderd/httpmw"
@ -259,3 +262,50 @@ func (api *API) patchOrganizationIDPSyncSettings(rw http.ResponseWriter, r *http
AssignDefault: settings.AssignDefault,
})
}
// @Summary Get the available organization idp sync claim fields
// @ID get-the-available-organization-idp-sync-claim-fields
// @Security CoderSessionToken
// @Produce json
// @Tags Enterprise
// @Param organization path string true "Organization ID" format(uuid)
// @Success 200 {array} string
// @Router /organizations/{organization}/settings/idpsync/available-fields [get]
func (api *API) organizationIDPSyncClaimFields(rw http.ResponseWriter, r *http.Request) {
org := httpmw.OrganizationParam(r)
api.idpSyncClaimFields(org.ID, rw, r)
}
// @Summary Get the available idp sync claim fields
// @ID get-the-available-idp-sync-claim-fields
// @Security CoderSessionToken
// @Produce json
// @Tags Enterprise
// @Param organization path string true "Organization ID" format(uuid)
// @Success 200 {array} string
// @Router /settings/idpsync/available-fields [get]
func (api *API) deploymentIDPSyncClaimFields(rw http.ResponseWriter, r *http.Request) {
// nil uuid implies all organizations
api.idpSyncClaimFields(uuid.Nil, rw, r)
}
func (api *API) idpSyncClaimFields(orgID uuid.UUID, rw http.ResponseWriter, r *http.Request) {
ctx := r.Context()
fields, err := api.Database.OIDCClaimFields(ctx, orgID)
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 available IDP fields",
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)
}

View File

@ -165,6 +165,19 @@ func TestUserOIDC(t *testing.T) {
user, err := userClient.User(ctx, codersdk.Me)
require.NoError(t, err)
// Then: the available sync fields should be "email" and "organization"
fields, err := runner.AdminClient.GetAvailableIDPSyncFields(ctx)
require.NoError(t, err)
require.ElementsMatch(t, []string{
"aud", "exp", "iss", // Always included from jwt
"email", "organization",
}, fields)
// This should be the same as above
orgFields, err := runner.AdminClient.GetOrganizationAvailableIDPSyncFields(ctx, orgOne.ID.String())
require.NoError(t, err)
require.ElementsMatch(t, fields, orgFields)
// When: they are manually added to the fourth organization, a new sync
// should remove them.
_, err = runner.AdminClient.PostOrganizationMember(ctx, orgThree.ID, "alice")