mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
chore: add scim service provider config endpoint (#15235)
Adds a static `/scim/v2/ServiceProviderConfig` endpoint. Our scim support is static, so the response config is also defined statically.
This commit is contained in:
@@ -457,6 +457,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
|
||||
r.Use(
|
||||
api.RequireFeatureMW(codersdk.FeatureSCIM),
|
||||
)
|
||||
r.Get("/ServiceProviderConfig", api.scimServiceProviderConfig)
|
||||
r.Post("/Users", api.scimPostUser)
|
||||
r.Route("/Users", func(r chi.Router) {
|
||||
r.Get("/", api.scimGetUsers)
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/google/uuid"
|
||||
@@ -21,6 +22,7 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/database/dbtime"
|
||||
"github.com/coder/coder/v2/coderd/httpapi"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/enterprise/coderd/scim"
|
||||
)
|
||||
|
||||
func (api *API) scimVerifyAuthHeader(r *http.Request) bool {
|
||||
@@ -34,6 +36,69 @@ func (api *API) scimVerifyAuthHeader(r *http.Request) bool {
|
||||
return len(api.SCIMAPIKey) != 0 && subtle.ConstantTimeCompare(hdr, api.SCIMAPIKey) == 1
|
||||
}
|
||||
|
||||
// scimServiceProviderConfig returns a static SCIM service provider configuration.
|
||||
//
|
||||
// @Summary SCIM 2.0: Service Provider Config
|
||||
// @ID scim-get-service-provider-config
|
||||
// @Produce application/scim+json
|
||||
// @Tags Enterprise
|
||||
// @Success 200
|
||||
// @Router /scim/v2/ServiceProviderConfig [get]
|
||||
func (api *API) scimServiceProviderConfig(rw http.ResponseWriter, _ *http.Request) {
|
||||
// No auth needed to query this endpoint.
|
||||
|
||||
rw.Header().Set("Content-Type", spec.ApplicationScimJson)
|
||||
rw.WriteHeader(http.StatusOK)
|
||||
|
||||
// providerUpdated is the last time the static provider config was updated.
|
||||
// Increment this time if you make any changes to the provider config.
|
||||
providerUpdated := time.Date(2024, 10, 25, 17, 0, 0, 0, time.UTC)
|
||||
var location string
|
||||
locURL, err := api.AccessURL.Parse("/scim/v2/ServiceProviderConfig")
|
||||
if err == nil {
|
||||
location = locURL.String()
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(rw)
|
||||
enc.SetEscapeHTML(true)
|
||||
_ = enc.Encode(scim.ServiceProviderConfig{
|
||||
Schemas: []string{"urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"},
|
||||
DocURI: "https://coder.com/docs/admin/users/oidc-auth#scim-enterprise-premium",
|
||||
Patch: scim.Supported{
|
||||
Supported: true,
|
||||
},
|
||||
Bulk: scim.BulkSupported{
|
||||
Supported: false,
|
||||
},
|
||||
Filter: scim.FilterSupported{
|
||||
Supported: false,
|
||||
},
|
||||
ChangePassword: scim.Supported{
|
||||
Supported: false,
|
||||
},
|
||||
Sort: scim.Supported{
|
||||
Supported: false,
|
||||
},
|
||||
ETag: scim.Supported{
|
||||
Supported: false,
|
||||
},
|
||||
AuthSchemes: []scim.AuthenticationScheme{
|
||||
{
|
||||
Type: "oauthbearertoken",
|
||||
Name: "HTTP Header Authentication",
|
||||
Description: "Authentication scheme using the Authorization header with the shared token",
|
||||
DocURI: "https://coder.com/docs/admin/users/oidc-auth#scim-enterprise-premium",
|
||||
},
|
||||
},
|
||||
Meta: scim.ServiceProviderMeta{
|
||||
Created: providerUpdated,
|
||||
LastModified: providerUpdated,
|
||||
Location: location,
|
||||
ResourceType: "ServiceProviderConfig",
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// scimGetUsers intentionally always returns no users. This is done to always force
|
||||
// Okta to try and create each user individually, this way we don't need to
|
||||
// implement fetching users twice.
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package scim
|
||||
|
||||
import "time"
|
||||
|
||||
type ServiceProviderConfig struct {
|
||||
Schemas []string `json:"schemas"`
|
||||
DocURI string `json:"documentationUri"`
|
||||
Patch Supported `json:"patch"`
|
||||
Bulk BulkSupported `json:"bulk"`
|
||||
Filter FilterSupported `json:"filter"`
|
||||
ChangePassword Supported `json:"changePassword"`
|
||||
Sort Supported `json:"sort"`
|
||||
ETag Supported `json:"etag"`
|
||||
AuthSchemes []AuthenticationScheme `json:"authenticationSchemes"`
|
||||
Meta ServiceProviderMeta `json:"meta"`
|
||||
}
|
||||
|
||||
type ServiceProviderMeta struct {
|
||||
Created time.Time `json:"created"`
|
||||
LastModified time.Time `json:"lastModified"`
|
||||
Location string `json:"location"`
|
||||
ResourceType string `json:"resourceType"`
|
||||
}
|
||||
|
||||
type Supported struct {
|
||||
Supported bool `json:"supported"`
|
||||
}
|
||||
|
||||
type BulkSupported struct {
|
||||
Supported bool `json:"supported"`
|
||||
MaxOp int `json:"maxOperations"`
|
||||
MaxPayload int `json:"maxPayloadSize"`
|
||||
}
|
||||
|
||||
type FilterSupported struct {
|
||||
Supported bool `json:"supported"`
|
||||
MaxResults int `json:"maxResults"`
|
||||
}
|
||||
|
||||
type AuthenticationScheme struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
SpecURI string `json:"specUri"`
|
||||
DocURI string `json:"documentationUri"`
|
||||
}
|
||||
@@ -140,9 +140,15 @@ func TestScim(t *testing.T) {
|
||||
})
|
||||
mockAudit.ResetLogs()
|
||||
|
||||
// verify scim is enabled
|
||||
res, err := client.Request(ctx, http.MethodGet, "/scim/v2/ServiceProviderConfig", nil)
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
// when
|
||||
sUser := makeScimUser(t)
|
||||
res, err := client.Request(ctx, "POST", "/scim/v2/Users", sUser, setScimAuth(scimAPIKey))
|
||||
res, err = client.Request(ctx, http.MethodPost, "/scim/v2/Users", sUser, setScimAuth(scimAPIKey))
|
||||
require.NoError(t, err)
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
|
||||
Reference in New Issue
Block a user