Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit e03ef62

Browse files
authored
chore: add scim service provider config endpoint (coder#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

+17
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

+13
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/reference/api/enterprise.md

+18
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

enterprise/coderd/coderd.go

+1
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

+65
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

+46
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

+7-1
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)