Skip to content

Commit e03ef62

Browse files
authored
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.
1 parent 27f5ff2 commit e03ef62

File tree

7 files changed

+167
-1
lines changed

7 files changed

+167
-1
lines changed

coderd/apidoc/docs.go

Lines changed: 17 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 13 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/api/enterprise.md

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

enterprise/coderd/coderd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,7 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
457457
r.Use(
458458
api.RequireFeatureMW(codersdk.FeatureSCIM),
459459
)
460+
r.Get("/ServiceProviderConfig", api.scimServiceProviderConfig)
460461
r.Post("/Users", api.scimPostUser)
461462
r.Route("/Users", func(r chi.Router) {
462463
r.Get("/", api.scimGetUsers)

enterprise/coderd/scim.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"database/sql"
66
"encoding/json"
77
"net/http"
8+
"time"
89

910
"github.com/go-chi/chi/v5"
1011
"github.com/google/uuid"
@@ -21,6 +22,7 @@ import (
2122
"github.com/coder/coder/v2/coderd/database/dbtime"
2223
"github.com/coder/coder/v2/coderd/httpapi"
2324
"github.com/coder/coder/v2/codersdk"
25+
"github.com/coder/coder/v2/enterprise/coderd/scim"
2426
)
2527

2628
func (api *API) scimVerifyAuthHeader(r *http.Request) bool {
@@ -34,6 +36,69 @@ func (api *API) scimVerifyAuthHeader(r *http.Request) bool {
3436
return len(api.SCIMAPIKey) != 0 && subtle.ConstantTimeCompare(hdr, api.SCIMAPIKey) == 1
3537
}
3638

39+
// scimServiceProviderConfig returns a static SCIM service provider configuration.
40+
//
41+
// @Summary SCIM 2.0: Service Provider Config
42+
// @ID scim-get-service-provider-config
43+
// @Produce application/scim+json
44+
// @Tags Enterprise
45+
// @Success 200
46+
// @Router /scim/v2/ServiceProviderConfig [get]
47+
func (api *API) scimServiceProviderConfig(rw http.ResponseWriter, _ *http.Request) {
48+
// No auth needed to query this endpoint.
49+
50+
rw.Header().Set("Content-Type", spec.ApplicationScimJson)
51+
rw.WriteHeader(http.StatusOK)
52+
53+
// providerUpdated is the last time the static provider config was updated.
54+
// Increment this time if you make any changes to the provider config.
55+
providerUpdated := time.Date(2024, 10, 25, 17, 0, 0, 0, time.UTC)
56+
var location string
57+
locURL, err := api.AccessURL.Parse("/scim/v2/ServiceProviderConfig")
58+
if err == nil {
59+
location = locURL.String()
60+
}
61+
62+
enc := json.NewEncoder(rw)
63+
enc.SetEscapeHTML(true)
64+
_ = enc.Encode(scim.ServiceProviderConfig{
65+
Schemas: []string{"urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"},
66+
DocURI: "https://coder.com/docs/admin/users/oidc-auth#scim-enterprise-premium",
67+
Patch: scim.Supported{
68+
Supported: true,
69+
},
70+
Bulk: scim.BulkSupported{
71+
Supported: false,
72+
},
73+
Filter: scim.FilterSupported{
74+
Supported: false,
75+
},
76+
ChangePassword: scim.Supported{
77+
Supported: false,
78+
},
79+
Sort: scim.Supported{
80+
Supported: false,
81+
},
82+
ETag: scim.Supported{
83+
Supported: false,
84+
},
85+
AuthSchemes: []scim.AuthenticationScheme{
86+
{
87+
Type: "oauthbearertoken",
88+
Name: "HTTP Header Authentication",
89+
Description: "Authentication scheme using the Authorization header with the shared token",
90+
DocURI: "https://coder.com/docs/admin/users/oidc-auth#scim-enterprise-premium",
91+
},
92+
},
93+
Meta: scim.ServiceProviderMeta{
94+
Created: providerUpdated,
95+
LastModified: providerUpdated,
96+
Location: location,
97+
ResourceType: "ServiceProviderConfig",
98+
},
99+
})
100+
}
101+
37102
// scimGetUsers intentionally always returns no users. This is done to always force
38103
// Okta to try and create each user individually, this way we don't need to
39104
// implement fetching users twice.

enterprise/coderd/scim/scimtypes.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package scim
2+
3+
import "time"
4+
5+
type ServiceProviderConfig struct {
6+
Schemas []string `json:"schemas"`
7+
DocURI string `json:"documentationUri"`
8+
Patch Supported `json:"patch"`
9+
Bulk BulkSupported `json:"bulk"`
10+
Filter FilterSupported `json:"filter"`
11+
ChangePassword Supported `json:"changePassword"`
12+
Sort Supported `json:"sort"`
13+
ETag Supported `json:"etag"`
14+
AuthSchemes []AuthenticationScheme `json:"authenticationSchemes"`
15+
Meta ServiceProviderMeta `json:"meta"`
16+
}
17+
18+
type ServiceProviderMeta struct {
19+
Created time.Time `json:"created"`
20+
LastModified time.Time `json:"lastModified"`
21+
Location string `json:"location"`
22+
ResourceType string `json:"resourceType"`
23+
}
24+
25+
type Supported struct {
26+
Supported bool `json:"supported"`
27+
}
28+
29+
type BulkSupported struct {
30+
Supported bool `json:"supported"`
31+
MaxOp int `json:"maxOperations"`
32+
MaxPayload int `json:"maxPayloadSize"`
33+
}
34+
35+
type FilterSupported struct {
36+
Supported bool `json:"supported"`
37+
MaxResults int `json:"maxResults"`
38+
}
39+
40+
type AuthenticationScheme struct {
41+
Type string `json:"type"`
42+
Name string `json:"name"`
43+
Description string `json:"description"`
44+
SpecURI string `json:"specUri"`
45+
DocURI string `json:"documentationUri"`
46+
}

enterprise/coderd/scim_test.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,15 @@ func TestScim(t *testing.T) {
140140
})
141141
mockAudit.ResetLogs()
142142

143+
// verify scim is enabled
144+
res, err := client.Request(ctx, http.MethodGet, "/scim/v2/ServiceProviderConfig", nil)
145+
require.NoError(t, err)
146+
defer res.Body.Close()
147+
require.Equal(t, http.StatusOK, res.StatusCode)
148+
143149
// when
144150
sUser := makeScimUser(t)
145-
res, err := client.Request(ctx, "POST", "/scim/v2/Users", sUser, setScimAuth(scimAPIKey))
151+
res, err = client.Request(ctx, http.MethodPost, "/scim/v2/Users", sUser, setScimAuth(scimAPIKey))
146152
require.NoError(t, err)
147153
defer res.Body.Close()
148154
require.Equal(t, http.StatusOK, res.StatusCode)

0 commit comments

Comments
 (0)
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