Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/public-api-server/pkg/apiv1/oidc_test.go
2499 views
1
// Copyright (c) 2022 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package apiv1
6
7
import (
8
"context"
9
"net/http"
10
"net/http/httptest"
11
"sort"
12
"strings"
13
"testing"
14
15
"gorm.io/gorm"
16
17
db "github.com/gitpod-io/gitpod/components/gitpod-db/go"
18
"github.com/gitpod-io/gitpod/components/gitpod-db/go/dbtest"
19
"github.com/go-chi/chi/v5"
20
"github.com/go-chi/chi/v5/middleware"
21
"github.com/google/uuid"
22
23
connect "github.com/bufbuild/connect-go"
24
"github.com/gitpod-io/gitpod/common-go/experiments"
25
"github.com/gitpod-io/gitpod/common-go/experiments/experimentstest"
26
"github.com/gitpod-io/gitpod/components/public-api/go/config"
27
v1 "github.com/gitpod-io/gitpod/components/public-api/go/experimental/v1"
28
"github.com/gitpod-io/gitpod/components/public-api/go/experimental/v1/v1connect"
29
protocol "github.com/gitpod-io/gitpod/gitpod-protocol"
30
"github.com/gitpod-io/gitpod/public-api-server/pkg/auth"
31
"github.com/gitpod-io/gitpod/public-api-server/pkg/jws"
32
"github.com/gitpod-io/gitpod/public-api-server/pkg/jws/jwstest"
33
"github.com/golang/mock/gomock"
34
"github.com/stretchr/testify/require"
35
)
36
37
var (
38
withOIDCFeatureDisabled = &experimentstest.Client{
39
BoolMatcher: func(ctx context.Context, experiment string, defaultValue bool, attributes experiments.Attributes) bool {
40
return false
41
},
42
}
43
withOIDCFeatureEnabled = &experimentstest.Client{
44
BoolMatcher: func(ctx context.Context, experiment string, defaultValue bool, attributes experiments.Attributes) bool {
45
return experiment == experiments.OIDCServiceEnabledFlag
46
},
47
}
48
49
user = newUser(&protocol.User{})
50
organizationID = uuid.New()
51
)
52
53
func TestOIDCService_CreateClientConfig_FeatureFlagDisabled(t *testing.T) {
54
55
t.Run("returns unauthorized", func(t *testing.T) {
56
_, client, _ := setupOIDCService(t, withOIDCFeatureDisabled)
57
issuer := newFakeIdP(t, true)
58
59
_, err := client.CreateClientConfig(context.Background(), connect.NewRequest(&v1.CreateClientConfigRequest{
60
Config: &v1.OIDCClientConfig{
61
OrganizationId: organizationID.String(),
62
OidcConfig: &v1.OIDCConfig{
63
Issuer: issuer,
64
},
65
},
66
}))
67
require.Error(t, err)
68
require.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err))
69
})
70
}
71
72
func TestOIDCService_CreateClientConfig_FeatureFlagEnabled(t *testing.T) {
73
t.Run("returns invalid argument when no organisation specified", func(t *testing.T) {
74
_, client, _ := setupOIDCService(t, withOIDCFeatureEnabled)
75
issuer := newFakeIdP(t, true)
76
77
config := &v1.OIDCClientConfig{
78
OidcConfig: &v1.OIDCConfig{Issuer: issuer},
79
Oauth2Config: &v1.OAuth2Config{ClientId: "test-id", ClientSecret: "test-secret"},
80
}
81
_, err := client.CreateClientConfig(context.Background(), connect.NewRequest(&v1.CreateClientConfigRequest{
82
Config: config,
83
}))
84
require.Error(t, err)
85
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
86
})
87
88
t.Run("returns invalid argument when organisation id is not a uuid", func(t *testing.T) {
89
_, client, _ := setupOIDCService(t, withOIDCFeatureEnabled)
90
issuer := newFakeIdP(t, true)
91
92
config := &v1.OIDCClientConfig{
93
OrganizationId: "some-random-id",
94
OidcConfig: &v1.OIDCConfig{Issuer: issuer},
95
Oauth2Config: &v1.OAuth2Config{ClientId: "test-id", ClientSecret: "test-secret"},
96
}
97
_, err := client.CreateClientConfig(context.Background(), connect.NewRequest(&v1.CreateClientConfigRequest{
98
Config: config,
99
}))
100
require.Error(t, err)
101
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
102
})
103
104
t.Run("returns permission denied when user is not org owner", func(t *testing.T) {
105
_, client, dbConn := setupOIDCService(t, withOIDCFeatureEnabled)
106
issuer := newFakeIdP(t, true)
107
108
anotherOrg := uuid.New()
109
dbtest.CreateTeamMembership(t, dbConn, db.OrganizationMembership{
110
OrganizationID: anotherOrg,
111
UserID: uuid.MustParse(user.ID),
112
Role: db.OrganizationMembershipRole_Member,
113
})
114
115
config := &v1.OIDCClientConfig{
116
OrganizationId: anotherOrg.String(),
117
OidcConfig: &v1.OIDCConfig{Issuer: issuer},
118
Oauth2Config: &v1.OAuth2Config{ClientId: "test-id", ClientSecret: "test-secret"},
119
}
120
_, err := client.CreateClientConfig(context.Background(), connect.NewRequest(&v1.CreateClientConfigRequest{
121
Config: config,
122
}))
123
require.Error(t, err)
124
require.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err))
125
})
126
127
t.Run("returns invalid argument when issuer is not valid URL", func(t *testing.T) {
128
_, client, _ := setupOIDCService(t, withOIDCFeatureEnabled)
129
130
config := &v1.OIDCClientConfig{
131
OrganizationId: organizationID.String(),
132
OidcConfig: &v1.OIDCConfig{Issuer: "random thing which is not url"},
133
Oauth2Config: &v1.OAuth2Config{ClientId: "test-id", ClientSecret: "test-secret"},
134
}
135
_, err := client.CreateClientConfig(context.Background(), connect.NewRequest(&v1.CreateClientConfigRequest{
136
Config: config,
137
}))
138
require.Error(t, err)
139
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
140
})
141
142
t.Run("returns invalid argument when issuer is not reachable", func(t *testing.T) {
143
_, client, _ := setupOIDCService(t, withOIDCFeatureEnabled)
144
145
config := &v1.OIDCClientConfig{
146
OrganizationId: organizationID.String(),
147
OidcConfig: &v1.OIDCConfig{Issuer: "https://this-host-is-not-reachable"},
148
Oauth2Config: &v1.OAuth2Config{ClientId: "test-id", ClientSecret: "test-secret"},
149
}
150
_, err := client.CreateClientConfig(context.Background(), connect.NewRequest(&v1.CreateClientConfigRequest{
151
Config: config,
152
}))
153
require.Error(t, err)
154
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
155
})
156
157
t.Run("returns invalid argument when issuer does not provide discovery", func(t *testing.T) {
158
_, client, _ := setupOIDCService(t, withOIDCFeatureEnabled)
159
issuer := newFakeIdP(t, false)
160
161
config := &v1.OIDCClientConfig{
162
OrganizationId: organizationID.String(),
163
OidcConfig: &v1.OIDCConfig{Issuer: issuer},
164
Oauth2Config: &v1.OAuth2Config{ClientId: "test-id", ClientSecret: "test-secret"},
165
}
166
_, err := client.CreateClientConfig(context.Background(), connect.NewRequest(&v1.CreateClientConfigRequest{
167
Config: config,
168
}))
169
require.Error(t, err)
170
require.Contains(t, err.Error(), "needs to support OIDC Discovery")
171
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
172
})
173
174
t.Run("creates oidc client config", func(t *testing.T) {
175
_, client, dbConn := setupOIDCService(t, withOIDCFeatureEnabled)
176
issuer := newFakeIdP(t, true)
177
178
// Trailing slashes should be removed from the issuer
179
issuerWithTrailingSlash := issuer + "/"
180
config := &v1.OIDCClientConfig{
181
OrganizationId: organizationID.String(),
182
OidcConfig: &v1.OIDCConfig{Issuer: issuerWithTrailingSlash},
183
Active: true,
184
Oauth2Config: &v1.OAuth2Config{
185
ClientId: "test-id",
186
ClientSecret: "test-secret",
187
Scopes: []string{"my-scope"},
188
},
189
}
190
response, err := client.CreateClientConfig(context.Background(), connect.NewRequest(&v1.CreateClientConfigRequest{
191
Config: config,
192
}))
193
require.NoError(t, err)
194
195
requireEqualProto(t, &v1.CreateClientConfigResponse{
196
Config: &v1.OIDCClientConfig{
197
Id: response.Msg.Config.Id,
198
Active: true,
199
OrganizationId: response.Msg.Config.OrganizationId,
200
Oauth2Config: &v1.OAuth2Config{
201
ClientId: config.Oauth2Config.ClientId,
202
ClientSecret: "REDACTED",
203
Scopes: []string{"openid", "profile", "email", "my-scope"},
204
},
205
OidcConfig: &v1.OIDCConfig{
206
Issuer: issuer,
207
},
208
},
209
}, response.Msg)
210
211
t.Cleanup(func() {
212
dbtest.HardDeleteOIDCClientConfigs(t, response.Msg.Config.GetId())
213
})
214
215
retrieved, err := db.GetOIDCClientConfig(context.Background(), dbConn, uuid.MustParse(response.Msg.Config.Id))
216
require.NoError(t, err)
217
require.Equal(t, issuer, retrieved.Issuer, "issuer must not contain trailing slash")
218
219
decrypted, err := retrieved.Data.Decrypt(dbtest.CipherSet(t))
220
require.NoError(t, err)
221
require.Equal(t, toDbOIDCSpec(config.Oauth2Config), decrypted)
222
})
223
}
224
225
func TestOIDCService_GetClientConfig_WithFeatureFlagDisabled(t *testing.T) {
226
_, client, _ := setupOIDCService(t, withOIDCFeatureDisabled)
227
228
_, err := client.GetClientConfig(context.Background(), connect.NewRequest(&v1.GetClientConfigRequest{
229
Id: uuid.NewString(),
230
OrganizationId: uuid.NewString(),
231
}))
232
require.Error(t, err)
233
require.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err))
234
}
235
236
func TestOIDCService_GetClientConfig_WithFeatureFlagEnabled(t *testing.T) {
237
238
t.Run("invalid argument when config id missing", func(t *testing.T) {
239
_, client, _ := setupOIDCService(t, withOIDCFeatureEnabled)
240
241
_, err := client.GetClientConfig(context.Background(), connect.NewRequest(&v1.GetClientConfigRequest{}))
242
require.Error(t, err)
243
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
244
})
245
246
t.Run("invalid argument when organization id missing", func(t *testing.T) {
247
_, client, _ := setupOIDCService(t, withOIDCFeatureEnabled)
248
249
_, err := client.GetClientConfig(context.Background(), connect.NewRequest(&v1.GetClientConfigRequest{
250
Id: uuid.NewString(),
251
}))
252
require.Error(t, err)
253
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
254
})
255
256
t.Run("not found when record does not exist", func(t *testing.T) {
257
_, client, _ := setupOIDCService(t, withOIDCFeatureEnabled)
258
259
_, err := client.GetClientConfig(context.Background(), connect.NewRequest(&v1.GetClientConfigRequest{
260
Id: uuid.NewString(),
261
OrganizationId: uuid.NewString(),
262
}))
263
require.Error(t, err)
264
require.Equal(t, connect.CodeNotFound, connect.CodeOf(err))
265
})
266
267
t.Run("retrieves record when it exists", func(t *testing.T) {
268
_, client, dbConn := setupOIDCService(t, withOIDCFeatureEnabled)
269
issuer := newFakeIdP(t, true)
270
271
created := dbtest.CreateOIDCClientConfigs(t, dbConn, db.OIDCClientConfig{
272
OrganizationID: organizationID,
273
Issuer: issuer,
274
})[0]
275
276
resp, err := client.GetClientConfig(context.Background(), connect.NewRequest(&v1.GetClientConfigRequest{
277
Id: created.ID.String(),
278
OrganizationId: created.OrganizationID.String(),
279
}))
280
require.NoError(t, err)
281
282
converted, err := dbOIDCClientConfigToAPI(created, dbtest.CipherSet(t))
283
require.NoError(t, err)
284
285
requireEqualProto(t, &v1.GetClientConfigResponse{
286
Config: converted,
287
}, resp.Msg)
288
})
289
290
t.Run("returns permission denied when user is not org owner", func(t *testing.T) {
291
_, client, dbConn := setupOIDCService(t, withOIDCFeatureEnabled)
292
293
anotherOrg := uuid.New()
294
dbtest.CreateTeamMembership(t, dbConn, db.OrganizationMembership{
295
OrganizationID: anotherOrg,
296
UserID: uuid.MustParse(user.ID),
297
Role: db.OrganizationMembershipRole_Member,
298
})
299
300
_, err := client.GetClientConfig(context.Background(), connect.NewRequest(&v1.GetClientConfigRequest{
301
Id: uuid.NewString(),
302
OrganizationId: anotherOrg.String(),
303
}))
304
require.Error(t, err)
305
require.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err))
306
})
307
}
308
309
func TestOIDCService_ListClientConfigs_WithFeatureFlagDisabled(t *testing.T) {
310
_, client, _ := setupOIDCService(t, withOIDCFeatureDisabled)
311
312
_, err := client.ListClientConfigs(context.Background(), connect.NewRequest(&v1.ListClientConfigsRequest{
313
OrganizationId: organizationID.String(),
314
}))
315
require.Error(t, err)
316
require.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err))
317
}
318
319
func TestOIDCService_ListClientConfigs_WithFeatureFlagEnabled(t *testing.T) {
320
321
t.Run("invalid argument when organization id missing", func(t *testing.T) {
322
_, client, _ := setupOIDCService(t, withOIDCFeatureEnabled)
323
324
_, err := client.ListClientConfigs(context.Background(), connect.NewRequest(&v1.ListClientConfigsRequest{}))
325
require.Error(t, err)
326
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
327
})
328
329
t.Run("invalid argument when organization id is invalid", func(t *testing.T) {
330
_, client, _ := setupOIDCService(t, withOIDCFeatureEnabled)
331
332
_, err := client.ListClientConfigs(context.Background(), connect.NewRequest(&v1.ListClientConfigsRequest{
333
OrganizationId: "some-invalid-id",
334
}))
335
require.Error(t, err)
336
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
337
})
338
339
t.Run("returns permission denied when user is not org owner", func(t *testing.T) {
340
_, client, dbConn := setupOIDCService(t, withOIDCFeatureEnabled)
341
342
anotherOrg := uuid.New()
343
dbtest.CreateTeamMembership(t, dbConn, db.OrganizationMembership{
344
OrganizationID: anotherOrg,
345
UserID: uuid.MustParse(user.ID),
346
Role: db.OrganizationMembershipRole_Member,
347
})
348
349
_, err := client.ListClientConfigs(context.Background(), connect.NewRequest(&v1.ListClientConfigsRequest{
350
OrganizationId: anotherOrg.String(),
351
}))
352
require.Error(t, err)
353
require.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err))
354
})
355
356
t.Run("retrieves configs by organization id", func(t *testing.T) {
357
_, client, dbConn := setupOIDCService(t, withOIDCFeatureEnabled)
358
issuer := newFakeIdP(t, true)
359
360
anotherOrgID := uuid.New()
361
362
configs := dbtest.CreateOIDCClientConfigs(t, dbConn,
363
dbtest.NewOIDCClientConfig(t, db.OIDCClientConfig{
364
OrganizationID: organizationID,
365
Issuer: issuer,
366
}),
367
dbtest.NewOIDCClientConfig(t, db.OIDCClientConfig{
368
OrganizationID: organizationID,
369
Issuer: issuer,
370
}),
371
dbtest.NewOIDCClientConfig(t, db.OIDCClientConfig{
372
OrganizationID: anotherOrgID,
373
Issuer: issuer,
374
}),
375
)
376
377
response, err := client.ListClientConfigs(context.Background(), connect.NewRequest(&v1.ListClientConfigsRequest{
378
OrganizationId: organizationID.String(),
379
}))
380
require.NoError(t, err)
381
382
configA, err := dbOIDCClientConfigToAPI(configs[0], dbtest.CipherSet(t))
383
require.NoError(t, err)
384
configB, err := dbOIDCClientConfigToAPI(configs[1], dbtest.CipherSet(t))
385
require.NoError(t, err)
386
387
expected := []*v1.OIDCClientConfig{
388
configA,
389
configB,
390
}
391
sort.Slice(expected, func(i, j int) bool {
392
return strings.Compare(expected[i].Id, expected[j].Id) == -1
393
394
})
395
396
requireEqualProto(t, expected, response.Msg.ClientConfigs)
397
})
398
399
}
400
401
func TestOIDCService_UpdateClientConfig_WithFeatureFlagDisabled(t *testing.T) {
402
t.Run("feature flag disabled returns unauthorized", func(t *testing.T) {
403
_, client, _ := setupOIDCService(t, withOIDCFeatureDisabled)
404
405
_, err := client.UpdateClientConfig(context.Background(), connect.NewRequest(&v1.UpdateClientConfigRequest{
406
Config: &v1.OIDCClientConfig{
407
Id: uuid.NewString(),
408
OrganizationId: organizationID.String(),
409
},
410
}))
411
require.Error(t, err)
412
require.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err))
413
})
414
415
}
416
417
func TestOIDCService_UpdateClientConfig_WithFeatureFlagEnabled(t *testing.T) {
418
t.Run("non-existent config ID returns not found", func(t *testing.T) {
419
_, client, _ := setupOIDCService(t, withOIDCFeatureEnabled)
420
421
_, err := client.UpdateClientConfig(context.Background(), connect.NewRequest(&v1.UpdateClientConfigRequest{
422
Config: &v1.OIDCClientConfig{
423
Id: uuid.New().String(),
424
OrganizationId: organizationID.String(),
425
},
426
}))
427
require.Error(t, err)
428
require.Equal(t, connect.CodeNotFound, connect.CodeOf(err))
429
})
430
431
t.Run("returns permission denied when user is not org owner", func(t *testing.T) {
432
_, client, dbConn := setupOIDCService(t, withOIDCFeatureEnabled)
433
434
anotherOrg := uuid.New()
435
dbtest.CreateTeamMembership(t, dbConn, db.OrganizationMembership{
436
OrganizationID: anotherOrg,
437
UserID: uuid.MustParse(user.ID),
438
Role: db.OrganizationMembershipRole_Member,
439
})
440
441
_, err := client.UpdateClientConfig(context.Background(), connect.NewRequest(&v1.UpdateClientConfigRequest{
442
Config: &v1.OIDCClientConfig{
443
Id: uuid.New().String(),
444
OrganizationId: anotherOrg.String(),
445
},
446
}))
447
require.Error(t, err)
448
require.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err))
449
})
450
451
t.Run("partially applies updates to issuer and scopes", func(t *testing.T) {
452
_, client, _ := setupOIDCService(t, withOIDCFeatureEnabled)
453
issuer := newFakeIdP(t, true)
454
issuerNew := newFakeIdP(t, true)
455
456
config := &v1.OIDCClientConfig{
457
OrganizationId: organizationID.String(),
458
OidcConfig: &v1.OIDCConfig{
459
Issuer: issuer,
460
},
461
Active: true,
462
Oauth2Config: &v1.OAuth2Config{
463
ClientId: "test-id",
464
ClientSecret: "test-secret",
465
Scopes: []string{"my-scope"},
466
},
467
}
468
created, err := client.CreateClientConfig(context.Background(), connect.NewRequest(&v1.CreateClientConfigRequest{
469
Config: config,
470
}))
471
require.NoError(t, err)
472
473
_, err = client.UpdateClientConfig(context.Background(), connect.NewRequest(&v1.UpdateClientConfigRequest{
474
Config: &v1.OIDCClientConfig{
475
Id: created.Msg.GetConfig().Id,
476
OrganizationId: organizationID.String(),
477
OidcConfig: &v1.OIDCConfig{
478
Issuer: issuerNew + "/", // trailing slash should be removed
479
},
480
Oauth2Config: &v1.OAuth2Config{
481
Scopes: []string{"foo"},
482
},
483
},
484
}))
485
require.NoError(t, err)
486
487
retrieved, err := client.GetClientConfig(context.Background(), connect.NewRequest(&v1.GetClientConfigRequest{
488
Id: created.Msg.GetConfig().GetId(),
489
OrganizationId: created.Msg.GetConfig().OrganizationId,
490
}))
491
require.NoError(t, err)
492
493
require.Equal(t, config.Active, retrieved.Msg.GetConfig().Active, "unexpected change of `active` flag")
494
require.Equal(t, issuerNew, retrieved.Msg.GetConfig().OidcConfig.Issuer)
495
require.Equal(t, []string{"email", "foo", "openid", "profile"}, retrieved.Msg.GetConfig().GetOauth2Config().GetScopes())
496
497
})
498
}
499
500
func TestOIDCService_DeleteClientConfig_WithFeatureFlagDisabled(t *testing.T) {
501
t.Run("feature flag disabled returns unauthorized", func(t *testing.T) {
502
_, client, _ := setupOIDCService(t, withOIDCFeatureDisabled)
503
504
_, err := client.DeleteClientConfig(context.Background(), connect.NewRequest(&v1.DeleteClientConfigRequest{
505
Id: uuid.NewString(),
506
OrganizationId: uuid.NewString(),
507
}))
508
require.Error(t, err)
509
require.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err))
510
})
511
512
}
513
514
func TestOIDCService_DeleteClientConfig_WithFeatureFlagEnabled(t *testing.T) {
515
t.Run("invalid argument when ID not specified", func(t *testing.T) {
516
_, client, _ := setupOIDCService(t, withOIDCFeatureEnabled)
517
518
_, err := client.DeleteClientConfig(context.Background(), connect.NewRequest(&v1.DeleteClientConfigRequest{}))
519
require.Error(t, err)
520
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
521
})
522
523
t.Run("invalid argument when Organization ID not specified", func(t *testing.T) {
524
_, client, _ := setupOIDCService(t, withOIDCFeatureEnabled)
525
526
_, err := client.DeleteClientConfig(context.Background(), connect.NewRequest(&v1.DeleteClientConfigRequest{
527
Id: uuid.NewString(),
528
}))
529
require.Error(t, err)
530
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
531
})
532
533
t.Run("not found when record does not exist", func(t *testing.T) {
534
_, client, _ := setupOIDCService(t, withOIDCFeatureEnabled)
535
536
_, err := client.DeleteClientConfig(context.Background(), connect.NewRequest(&v1.DeleteClientConfigRequest{
537
Id: uuid.NewString(),
538
OrganizationId: organizationID.String(),
539
}))
540
require.Error(t, err)
541
require.Equal(t, connect.CodeNotFound, connect.CodeOf(err))
542
})
543
544
t.Run("returns permission denied when user is not org owner", func(t *testing.T) {
545
_, client, dbConn := setupOIDCService(t, withOIDCFeatureEnabled)
546
547
anotherOrg := uuid.New()
548
dbtest.CreateTeamMembership(t, dbConn, db.OrganizationMembership{
549
OrganizationID: anotherOrg,
550
UserID: uuid.MustParse(user.ID),
551
Role: db.OrganizationMembershipRole_Member,
552
})
553
554
_, err := client.DeleteClientConfig(context.Background(), connect.NewRequest(&v1.DeleteClientConfigRequest{
555
Id: uuid.NewString(),
556
OrganizationId: anotherOrg.String(),
557
}))
558
require.Error(t, err)
559
require.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err))
560
})
561
562
t.Run("deletes record", func(t *testing.T) {
563
_, client, dbConn := setupOIDCService(t, withOIDCFeatureEnabled)
564
issuer := newFakeIdP(t, true)
565
566
created := dbtest.CreateOIDCClientConfigs(t, dbConn, db.OIDCClientConfig{
567
OrganizationID: organizationID,
568
Issuer: issuer,
569
})[0]
570
571
resp, err := client.DeleteClientConfig(context.Background(), connect.NewRequest(&v1.DeleteClientConfigRequest{
572
Id: created.ID.String(),
573
OrganizationId: created.OrganizationID.String(),
574
}))
575
require.NoError(t, err)
576
requireEqualProto(t, &v1.DeleteClientConfigResponse{}, resp.Msg)
577
})
578
}
579
580
func TestOIDCService_SetClientConfigActivation_WithFeatureFlagDisabled(t *testing.T) {
581
t.Run("feature flag disabled returns unauthorized", func(t *testing.T) {
582
_, client, _ := setupOIDCService(t, withOIDCFeatureDisabled)
583
584
_, err := client.SetClientConfigActivation(context.Background(), connect.NewRequest(&v1.SetClientConfigActivationRequest{
585
Id: uuid.NewString(),
586
OrganizationId: uuid.NewString(),
587
Activate: true,
588
}))
589
require.Error(t, err)
590
require.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err))
591
})
592
593
}
594
595
func TestOIDCService_SetClientConfigActivation_WithFeatureFlagEnabled(t *testing.T) {
596
t.Run("invalid argument when ID not specified", func(t *testing.T) {
597
_, client, _ := setupOIDCService(t, withOIDCFeatureEnabled)
598
599
_, err := client.SetClientConfigActivation(context.Background(), connect.NewRequest(&v1.SetClientConfigActivationRequest{}))
600
require.Error(t, err)
601
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
602
})
603
604
t.Run("invalid argument when Organization ID not specified", func(t *testing.T) {
605
_, client, _ := setupOIDCService(t, withOIDCFeatureEnabled)
606
607
_, err := client.SetClientConfigActivation(context.Background(), connect.NewRequest(&v1.SetClientConfigActivationRequest{
608
Id: uuid.NewString(),
609
}))
610
require.Error(t, err)
611
require.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))
612
})
613
614
t.Run("returns permission denied when user is not org owner", func(t *testing.T) {
615
_, client, dbConn := setupOIDCService(t, withOIDCFeatureEnabled)
616
617
anotherOrg := uuid.New()
618
dbtest.CreateTeamMembership(t, dbConn, db.OrganizationMembership{
619
OrganizationID: anotherOrg,
620
UserID: uuid.MustParse(user.ID),
621
Role: db.OrganizationMembershipRole_Member,
622
})
623
624
_, err := client.SetClientConfigActivation(context.Background(), connect.NewRequest(&v1.SetClientConfigActivationRequest{
625
Id: uuid.NewString(),
626
OrganizationId: anotherOrg.String(),
627
Activate: true,
628
}))
629
require.Error(t, err)
630
require.Equal(t, connect.CodePermissionDenied, connect.CodeOf(err))
631
})
632
633
t.Run("activates record", func(t *testing.T) {
634
_, client, dbConn := setupOIDCService(t, withOIDCFeatureEnabled)
635
issuer := newFakeIdP(t, true)
636
637
created := dbtest.CreateOIDCClientConfigs(t, dbConn, db.OIDCClientConfig{
638
OrganizationID: organizationID,
639
Issuer: issuer,
640
Active: false,
641
Verified: db.BoolPointer(true),
642
})[0]
643
644
resp, err := client.SetClientConfigActivation(context.Background(), connect.NewRequest(&v1.SetClientConfigActivationRequest{
645
Id: created.ID.String(),
646
OrganizationId: created.OrganizationID.String(),
647
Activate: true,
648
}))
649
require.NoError(t, err)
650
requireEqualProto(t, &v1.SetClientConfigActivationResponse{}, resp.Msg)
651
})
652
653
t.Run("fails to activate unverified record", func(t *testing.T) {
654
_, client, dbConn := setupOIDCService(t, withOIDCFeatureEnabled)
655
issuer := newFakeIdP(t, true)
656
657
created := dbtest.CreateOIDCClientConfigs(t, dbConn, db.OIDCClientConfig{
658
OrganizationID: organizationID,
659
Issuer: issuer,
660
Active: false,
661
Verified: db.BoolPointer(false),
662
})[0]
663
664
_, err := client.SetClientConfigActivation(context.Background(), connect.NewRequest(&v1.SetClientConfigActivationRequest{
665
Id: created.ID.String(),
666
OrganizationId: created.OrganizationID.String(),
667
Activate: true,
668
}))
669
require.Error(t, err)
670
require.Equal(t, connect.CodeFailedPrecondition, connect.CodeOf(err))
671
})
672
673
t.Run("activation of record should deactivate others", func(t *testing.T) {
674
_, client, dbConn := setupOIDCService(t, withOIDCFeatureEnabled)
675
issuer := newFakeIdP(t, true)
676
677
configs := dbtest.CreateOIDCClientConfigs(t, dbConn, db.OIDCClientConfig{
678
OrganizationID: organizationID,
679
Issuer: issuer,
680
Active: true,
681
Verified: db.BoolPointer(true),
682
}, db.OIDCClientConfig{
683
OrganizationID: organizationID,
684
Issuer: issuer,
685
Active: false,
686
Verified: db.BoolPointer(true),
687
})
688
689
first := configs[0]
690
second := configs[1]
691
692
_, err := client.SetClientConfigActivation(context.Background(), connect.NewRequest(&v1.SetClientConfigActivationRequest{
693
Id: second.ID.String(),
694
OrganizationId: organizationID.String(),
695
Activate: true,
696
}))
697
require.NoError(t, err)
698
699
getFirstConfigResponse, err := client.GetClientConfig(context.Background(), connect.NewRequest(&v1.GetClientConfigRequest{
700
Id: first.ID.String(),
701
OrganizationId: organizationID.String(),
702
}))
703
require.NoError(t, err)
704
require.Equal(t, false, getFirstConfigResponse.Msg.GetConfig().Active)
705
})
706
707
t.Run("deactivates record", func(t *testing.T) {
708
_, client, dbConn := setupOIDCService(t, withOIDCFeatureEnabled)
709
issuer := newFakeIdP(t, true)
710
711
created := dbtest.CreateOIDCClientConfigs(t, dbConn, db.OIDCClientConfig{
712
OrganizationID: organizationID,
713
Issuer: issuer,
714
Active: true,
715
Verified: db.BoolPointer(true),
716
})[0]
717
718
_, err := client.SetClientConfigActivation(context.Background(), connect.NewRequest(&v1.SetClientConfigActivationRequest{
719
Id: created.ID.String(),
720
OrganizationId: created.OrganizationID.String(),
721
Activate: false,
722
}))
723
require.NoError(t, err)
724
725
getResponse, err := client.GetClientConfig(context.Background(), connect.NewRequest(&v1.GetClientConfigRequest{
726
Id: created.ID.String(),
727
OrganizationId: created.OrganizationID.String(),
728
}))
729
require.NoError(t, err)
730
require.Equal(t, false, getResponse.Msg.Config.Active)
731
})
732
733
t.Run("record not found", func(t *testing.T) {
734
_, client, _ := setupOIDCService(t, withOIDCFeatureEnabled)
735
736
_, err := client.SetClientConfigActivation(context.Background(), connect.NewRequest(&v1.SetClientConfigActivationRequest{
737
Id: uuid.NewString(),
738
OrganizationId: organizationID.String(),
739
Activate: false,
740
}))
741
require.Error(t, err)
742
require.Equal(t, connect.CodeNotFound, connect.CodeOf(err))
743
})
744
}
745
746
func setupOIDCService(t *testing.T, expClient experiments.Client) (*protocol.MockAPIInterface, v1connect.OIDCServiceClient, *gorm.DB) {
747
t.Helper()
748
749
dbConn := dbtest.ConnectForTests(t)
750
751
ctrl := gomock.NewController(t)
752
t.Cleanup(ctrl.Finish)
753
754
serverMock := protocol.NewMockAPIInterface(ctrl)
755
756
svc := NewOIDCService(&FakeServerConnPool{api: serverMock}, expClient, dbConn, dbtest.CipherSet(t))
757
758
keyset := jwstest.GenerateKeySet(t)
759
rsa256, err := jws.NewRSA256(keyset)
760
require.NoError(t, err)
761
762
_, handler := v1connect.NewOIDCServiceHandler(svc, connect.WithInterceptors(auth.NewServerInterceptor(config.SessionConfig{
763
Issuer: "unitetest.com",
764
Cookie: config.CookieConfig{
765
Name: "cookie_jwt",
766
},
767
}, rsa256)))
768
769
router := chi.NewRouter()
770
router.Use(middleware.Logger)
771
router.Mount("/", handler)
772
ts := httptest.NewServer(router)
773
t.Cleanup(ts.Close)
774
775
client := v1connect.NewOIDCServiceClient(http.DefaultClient, ts.URL, connect.WithInterceptors(
776
auth.NewClientInterceptor("auth-token"),
777
))
778
779
// setup our default user
780
serverMock.EXPECT().GetLoggedInUser(gomock.Any()).Return(user, nil).AnyTimes()
781
serverMock.EXPECT().GetTeams(gomock.Any()).Return(teams, nil).AnyTimes()
782
// ensure our user is owner of our default org
783
dbtest.CreateTeamMembership(t, dbConn, db.OrganizationMembership{
784
UserID: uuid.MustParse(user.ID),
785
OrganizationID: organizationID,
786
Role: db.OrganizationMembershipRole_Owner,
787
})
788
789
return serverMock, client, dbConn
790
}
791
792
func newFakeIdP(t *testing.T, discoveryEnabled bool) string {
793
t.Helper()
794
795
router := chi.NewRouter()
796
ts := httptest.NewServer(router)
797
t.Cleanup(ts.Close)
798
url := ts.URL
799
800
router.Use(middleware.Logger)
801
if discoveryEnabled {
802
router.Get("/.well-known/openid-configuration", func(w http.ResponseWriter, r *http.Request) {
803
w.Header().Add("Content-Type", "application/json;application/foo")
804
_, err := w.Write([]byte(`{}`))
805
require.NoError(t, err)
806
})
807
}
808
return url
809
}
810
811