Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit5e2efb6

Browse files
coadlerbpmct
andauthored
feat: add SCIM provisioning via Okta (#4132)
Co-authored-by: Ben Potter <ben@coder.com>
1 parent50321ba commit5e2efb6

File tree

16 files changed

+467
-13
lines changed

16 files changed

+467
-13
lines changed

‎cli/server_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import (
2424
"testing"
2525
"time"
2626

27-
"github.com/go-chi/chi"
27+
"github.com/go-chi/chi/v5"
2828
"github.com/stretchr/testify/assert"
2929
"github.com/stretchr/testify/require"
3030
"go.uber.org/goleak"

‎coderd/coderd.go

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -222,11 +222,7 @@ func New(options *Options) *API {
222222
r.Route("/api/v2",func(r chi.Router) {
223223
api.APIHandler=r
224224

225-
r.NotFound(func(rw http.ResponseWriter,r*http.Request) {
226-
httpapi.Write(rw,http.StatusNotFound, codersdk.Response{
227-
Message:"Route not found.",
228-
})
229-
})
225+
r.NotFound(func(rw http.ResponseWriter,r*http.Request) {httpapi.RouteNotFound(rw) })
230226
r.Use(
231227
tracing.Middleware(api.TracerProvider),
232228
// Specific routes can specify smaller limits.

‎coderd/httpapi/httpapi.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ func InternalServerError(rw http.ResponseWriter, err error) {
7575
})
7676
}
7777

78+
funcRouteNotFound(rw http.ResponseWriter) {
79+
Write(rw,http.StatusNotFound, codersdk.Response{
80+
Message:"Route not found.",
81+
})
82+
}
83+
7884
// Write outputs a standardized format to an HTTP response body.
7985
funcWrite(rw http.ResponseWriter,statusint,responseinterface{}) {
8086
buf:=&bytes.Buffer{}

‎coderd/userauth.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ func (api *API) oauthLogin(r *http.Request, params oauthLoginParams) (*http.Cook
378378
organizationID=organizations[0].ID
379379
}
380380

381-
user,_,err=api.createUser(ctx,tx,createUserRequest{
381+
user,_,err=api.CreateUser(ctx,tx,CreateUserRequest{
382382
CreateUserRequest: codersdk.CreateUserRequest{
383383
Email:params.Email,
384384
Username:params.Username,

‎coderd/users.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func (api *API) postFirstUser(rw http.ResponseWriter, r *http.Request) {
8383
return
8484
}
8585

86-
user,organizationID,err:=api.createUser(r.Context(),api.Database,createUserRequest{
86+
user,organizationID,err:=api.CreateUser(r.Context(),api.Database,CreateUserRequest{
8787
CreateUserRequest: codersdk.CreateUserRequest{
8888
Email:createUser.Email,
8989
Username:createUser.Username,
@@ -317,7 +317,7 @@ func (api *API) postUser(rw http.ResponseWriter, r *http.Request) {
317317
return
318318
}
319319

320-
user,_,err:=api.createUser(r.Context(),api.Database,createUserRequest{
320+
user,_,err:=api.CreateUser(r.Context(),api.Database,CreateUserRequest{
321321
CreateUserRequest:req,
322322
LoginType:database.LoginTypePassword,
323323
})
@@ -1101,12 +1101,12 @@ func (api *API) createAPIKey(r *http.Request, params createAPIKeyParams) (*http.
11011101
},nil
11021102
}
11031103

1104-
typecreateUserRequeststruct {
1104+
typeCreateUserRequeststruct {
11051105
codersdk.CreateUserRequest
11061106
LoginType database.LoginType
11071107
}
11081108

1109-
func (api*API)createUser(ctx context.Context,store database.Store,reqcreateUserRequest) (database.User, uuid.UUID,error) {
1109+
func (api*API)CreateUser(ctx context.Context,store database.Store,reqCreateUserRequest) (database.User, uuid.UUID,error) {
11101110
varuser database.User
11111111
returnuser,req.OrganizationID,store.InTx(func(tx database.Store)error {
11121112
orgRoles:=make([]string,0)

‎codersdk/features.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ const (
1717
const (
1818
FeatureUserLimit="user_limit"
1919
FeatureAuditLog="audit_log"
20+
FeatureSCIM="scim"
2021
)
2122

22-
varFeatureNames= []string{FeatureUserLimit,FeatureAuditLog}
23+
varFeatureNames= []string{FeatureUserLimit,FeatureAuditLog,FeatureSCIM}
2324

2425
typeFeaturestruct {
2526
EntitlementEntitlement`json:"entitlement"`

‎docs/admin/auth.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,14 @@ CODER_OIDC_CLIENT_SECRET="G0CSP...7qSM"
7474
Once complete, run`sudo service coder restart` to reboot Coder.
7575

7676
>When a new user is created, the`preferred_username` claim becomes the username. If this claim is empty, the email address will be stripped of the domain, and become the username (e.g.`example@coder.com` becomes`example`).
77+
78+
##SCIM
79+
80+
Coder supports user provisioning and deprovisioning via SCIM 2.0 with header
81+
authentication. Upon deactivation, users are[suspended](userd.md#suspend-a-user)
82+
and are not deleted.[Configure](./configure.md) your SCIM application with an
83+
auth key and supply it the Coder server.
84+
85+
```console
86+
CODER_SCIM_API_KEY="your-api-key"
87+
```

‎enterprise/cli/server.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ import (
1414

1515
funcserver()*cobra.Command {
1616
var (
17-
auditLoggingbool
17+
auditLoggingbool
18+
scimAuthHeaderstring
1819
)
1920
cmd:=agpl.Server(func(ctx context.Context,options*agplcoderd.Options) (*agplcoderd.API,error) {
2021
api,err:=coderd.New(ctx,&coderd.Options{
2122
AuditLogging:auditLogging,
23+
SCIMAPIKey: []byte(scimAuthHeader),
2224
Options:options,
2325
})
2426
iferr!=nil {
@@ -28,6 +30,7 @@ func server() *cobra.Command {
2830
})
2931
cliflag.BoolVarP(cmd.Flags(),&auditLogging,"audit-logging","","CODER_AUDIT_LOGGING",true,
3032
"Specifies whether audit logging is enabled.")
33+
cliflag.StringVarP(cmd.Flags(),&scimAuthHeader,"scim-auth-header","","CODER_SCIM_API_KEY","","Enables SCIM and sets the authentication header for the built-in SCIM server. New users are automatically created with OIDC authentication.")
3134

3235
returncmd
3336
}

‎enterprise/coderd/coderd.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,19 @@ func New(ctx context.Context, options *Options) (*API, error) {
6363
})
6464
})
6565

66+
iflen(options.SCIMAPIKey)!=0 {
67+
api.AGPL.RootHandler.Route("/scim/v2",func(r chi.Router) {
68+
r.Use(api.scimEnabledMW)
69+
r.Post("/Users",api.scimPostUser)
70+
r.Route("/Users",func(r chi.Router) {
71+
r.Get("/",api.scimGetUsers)
72+
r.Post("/",api.scimPostUser)
73+
r.Get("/{id}",api.scimGetUser)
74+
r.Patch("/{id}",api.scimPatchUser)
75+
})
76+
})
77+
}
78+
6679
err:=api.updateEntitlements(ctx)
6780
iferr!=nil {
6881
returnnil,xerrors.Errorf("update entitlements: %w",err)
@@ -76,6 +89,7 @@ type Options struct {
7689
*coderd.Options
7790

7891
AuditLoggingbool
92+
SCIMAPIKey []byte
7993
EntitlementsUpdateInterval time.Duration
8094
Keysmap[string]ed25519.PublicKey
8195
}
@@ -93,6 +107,7 @@ type entitlements struct {
93107
hasLicensebool
94108
activeUsers codersdk.Feature
95109
auditLogs codersdk.Entitlement
110+
scim codersdk.Entitlement
96111
}
97112

98113
func (api*API)Close()error {
@@ -117,6 +132,7 @@ func (api *API) updateEntitlements(ctx context.Context) error {
117132
Entitlement:codersdk.EntitlementNotEntitled,
118133
},
119134
auditLogs:codersdk.EntitlementNotEntitled,
135+
scim:codersdk.EntitlementNotEntitled,
120136
}
121137

122138
// Here we loop through licenses to detect enabled features.
@@ -149,6 +165,9 @@ func (api *API) updateEntitlements(ctx context.Context) error {
149165
ifclaims.Features.AuditLog>0 {
150166
entitlements.auditLogs=entitlement
151167
}
168+
ifclaims.Features.SCIM>0 {
169+
entitlements.scim=entitlement
170+
}
152171
}
153172

154173
ifentitlements.auditLogs!=api.entitlements.auditLogs {

‎enterprise/coderd/coderdenttest/coderdenttest.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ func init() {
3737
typeOptionsstruct {
3838
*coderdtest.Options
3939
EntitlementsUpdateInterval time.Duration
40+
SCIMAPIKey []byte
4041
}
4142

4243
// New constructs a codersdk client connected to an in-memory Enterprise API instance.
@@ -55,6 +56,7 @@ func NewWithAPI(t *testing.T, options *Options) (*codersdk.Client, io.Closer, *c
5556
srv,cancelFunc,oop:=coderdtest.NewOptions(t,options.Options)
5657
coderAPI,err:=coderd.New(context.Background(),&coderd.Options{
5758
AuditLogging:true,
59+
SCIMAPIKey:options.SCIMAPIKey,
5860
Options:oop,
5961
EntitlementsUpdateInterval:options.EntitlementsUpdateInterval,
6062
Keys:map[string]ed25519.PublicKey{
@@ -82,6 +84,7 @@ type LicenseOptions struct {
8284
ExpiresAt time.Time
8385
UserLimitint64
8486
AuditLogbool
87+
SCIMbool
8588
}
8689

8790
// AddLicense generates a new license with the options provided and inserts it.
@@ -105,6 +108,11 @@ func GenerateLicense(t *testing.T, options LicenseOptions) string {
105108
ifoptions.AuditLog {
106109
auditLog=1
107110
}
111+
scim:=int64(0)
112+
ifoptions.SCIM {
113+
scim=1
114+
}
115+
108116
c:=&coderd.Claims{
109117
RegisteredClaims: jwt.RegisteredClaims{
110118
Issuer:"test@testing.test",
@@ -119,6 +127,7 @@ func GenerateLicense(t *testing.T, options LicenseOptions) string {
119127
Features: coderd.Features{
120128
UserLimit:options.UserLimit,
121129
AuditLog:auditLog,
130+
SCIM:scim,
122131
},
123132
}
124133
tok:=jwt.NewWithClaims(jwt.SigningMethodEdDSA,c)

‎enterprise/coderd/licenses.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ var Keys = map[string]ed25519.PublicKey{"2022-08-12": ed25519.PublicKey(key20220
4747
typeFeaturesstruct {
4848
UserLimitint64`json:"user_limit"`
4949
AuditLogint64`json:"audit_log"`
50+
SCIMint64`json:"scim"`
5051
}
5152

5253
typeClaimsstruct {

‎enterprise/coderd/licenses_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,13 @@ func TestGetLicense(t *testing.T) {
8080
coderdenttest.AddLicense(t,client, coderdenttest.LicenseOptions{
8181
AccountID:"testing",
8282
AuditLog:true,
83+
SCIM:true,
8384
})
8485

8586
coderdenttest.AddLicense(t,client, coderdenttest.LicenseOptions{
8687
AccountID:"testing2",
8788
AuditLog:true,
89+
SCIM:true,
8890
UserLimit:200,
8991
})
9092

@@ -96,12 +98,14 @@ func TestGetLicense(t *testing.T) {
9698
assert.Equal(t,map[string]interface{}{
9799
codersdk.FeatureUserLimit:json.Number("0"),
98100
codersdk.FeatureAuditLog:json.Number("1"),
101+
codersdk.FeatureSCIM:json.Number("1"),
99102
},licenses[0].Claims["features"])
100103
assert.Equal(t,int32(2),licenses[1].ID)
101104
assert.Equal(t,"testing2",licenses[1].Claims["account_id"])
102105
assert.Equal(t,map[string]interface{}{
103106
codersdk.FeatureUserLimit:json.Number("200"),
104107
codersdk.FeatureAuditLog:json.Number("1"),
108+
codersdk.FeatureSCIM:json.Number("1"),
105109
},licenses[1].Claims["features"])
106110
})
107111
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp