Skip to content

Commit 4884284

Browse files
authored
Merge pull request #2426 from traPtitech/issue2402
confidential clientのサポート(client typeを選択可能に)
2 parents 1f050d5 + 2e40b6b commit 4884284

File tree

5 files changed

+176
-51
lines changed

5 files changed

+176
-51
lines changed

docs/v3-api.yaml

+15
Original file line numberDiff line numberDiff line change
@@ -5868,12 +5868,16 @@ components:
58685868
description: 要求スコープの配列
58695869
items:
58705870
$ref: '#/components/schemas/OAuth2Scope'
5871+
confidential:
5872+
type: boolean
5873+
description: confidential client なら true, public client なら false
58715874
required:
58725875
- id
58735876
- name
58745877
- description
58755878
- developerId
58765879
- scopes
5880+
- confidential
58775881
PatchClientRequest:
58785882
title: PatchClientRequest
58795883
type: object
@@ -5896,6 +5900,9 @@ components:
58965900
type: string
58975901
description: クライアント開発者UUID
58985902
format: uuid
5903+
confidential:
5904+
type: boolean
5905+
description: confidential client なら true, public client なら false
58995906
OAuth2ClientDetail:
59005907
title: OAuth2ClientDetail
59015908
description: OAuth2クライアント詳細情報
@@ -5929,6 +5936,9 @@ components:
59295936
secret:
59305937
type: string
59315938
description: クライアントシークレット
5939+
confidential:
5940+
type: boolean
5941+
description: confidential client なら true, public client なら false
59325942
required:
59335943
- id
59345944
- developerId
@@ -5937,6 +5947,7 @@ components:
59375947
- scopes
59385948
- callbackUrl
59395949
- secret
5950+
- confidential
59405951
PostClientRequest:
59415952
title: PostClientRequest
59425953
type: object
@@ -5960,6 +5971,10 @@ components:
59605971
type: string
59615972
description: 説明
59625973
maxLength: 1000
5974+
confidential:
5975+
type: boolean
5976+
description: confidential client なら true, public cleint なら false
5977+
default: false
59635978
required:
59645979
- name
59655980
- callbackUrl

router/v3/clients.go

+16-13
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ func (h *Handlers) GetClients(c echo.Context) error {
3434

3535
// PostClientsRequest POST /clients リクエストボディ
3636
type PostClientsRequest struct {
37-
Name string `json:"name"`
38-
Description string `json:"description"`
39-
CallbackURL string `json:"callbackUrl"`
40-
Scopes model.AccessScopes `json:"scopes"`
37+
Name string `json:"name"`
38+
Description string `json:"description"`
39+
CallbackURL string `json:"callbackUrl"`
40+
Scopes model.AccessScopes `json:"scopes"`
41+
Confidential bool `json:"confidential"` // default false (public client)
4142
}
4243

4344
func (r PostClientsRequest) Validate() error {
@@ -62,7 +63,7 @@ func (h *Handlers) CreateClient(c echo.Context) error {
6263
ID: random.SecureAlphaNumeric(36),
6364
Name: req.Name,
6465
Description: req.Description,
65-
Confidential: false,
66+
Confidential: req.Confidential,
6667
CreatorID: userID,
6768
RedirectURI: req.CallbackURL,
6869
Secret: random.SecureAlphaNumeric(36),
@@ -92,10 +93,11 @@ func (h *Handlers) GetClient(c echo.Context) error {
9293

9394
// PatchClientRequest PATCH /clients/:clientID リクエストボディ
9495
type PatchClientRequest struct {
95-
Name optional.Of[string] `json:"name"`
96-
Description optional.Of[string] `json:"description"`
97-
CallbackURL optional.Of[string] `json:"callbackUrl"`
98-
DeveloperID optional.Of[uuid.UUID] `json:"developerId"`
96+
Name optional.Of[string] `json:"name"`
97+
Description optional.Of[string] `json:"description"`
98+
CallbackURL optional.Of[string] `json:"callbackUrl"`
99+
DeveloperID optional.Of[uuid.UUID] `json:"developerId"`
100+
Confidential optional.Of[bool] `json:"confidential"`
99101
}
100102

101103
func (r PatchClientRequest) Validate() error {
@@ -117,10 +119,11 @@ func (h *Handlers) EditClient(c echo.Context) error {
117119
}
118120

119121
args := repository.UpdateClientArgs{
120-
Name: req.Name,
121-
Description: req.Description,
122-
DeveloperID: req.DeveloperID,
123-
CallbackURL: req.CallbackURL,
122+
Name: req.Name,
123+
Description: req.Description,
124+
DeveloperID: req.DeveloperID,
125+
CallbackURL: req.CallbackURL,
126+
Confidential: req.Confidential,
124127
}
125128
if err := h.Repo.UpdateClient(oc.ID, args); err != nil {
126129
return herror.InternalServerError(err)

router/v3/clients_test.go

+103-13
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ func oAuth2ClientEquals(t *testing.T, expect *model.OAuth2Client, actual *httpex
2828
scopes = append(scopes, scope)
2929
}
3030
actual.Value("scopes").Array().ContainsOnly(scopes...)
31+
actual.Value("confidential").Boolean().IsEqual(expect.Confidential)
3132
}
3233

3334
func TestHandlers_GetClients(t *testing.T) {
@@ -38,7 +39,7 @@ func TestHandlers_GetClients(t *testing.T) {
3839
user := env.CreateUser(t, rand)
3940
user2 := env.CreateUser(t, rand)
4041
c1 := env.CreateOAuth2Client(t, rand, user.GetID())
41-
c2 := env.CreateOAuth2Client(t, rand, user2.GetID())
42+
c2 := env.CreateOAuth2Client(t, rand, user2.GetID(), WithConfidential(true))
4243
commonSession := env.S(t, user.GetID())
4344

4445
t.Run("not logged in", func(t *testing.T) {
@@ -95,10 +96,11 @@ func TestPostClientsRequest_Validate(t *testing.T) {
9596
t.Parallel()
9697

9798
type fields struct {
98-
Name string
99-
Description string
100-
CallbackURL string
101-
Scopes model.AccessScopes
99+
Name string
100+
Description string
101+
CallbackURL string
102+
Scopes model.AccessScopes
103+
Confidential bool
102104
}
103105
tests := []struct {
104106
name string
@@ -163,6 +165,17 @@ func TestPostClientsRequest_Validate(t *testing.T) {
163165
},
164166
false,
165167
},
168+
{
169+
"success (confidential client)",
170+
fields{
171+
Name: "test",
172+
Description: "desc",
173+
CallbackURL: "https://example.com",
174+
Scopes: map[model.AccessScope]struct{}{"read": {}},
175+
Confidential: true,
176+
},
177+
false,
178+
},
166179
}
167180
for _, tt := range tests {
168181
t.Run(tt.name, func(t *testing.T) {
@@ -194,6 +207,15 @@ func TestHandlers_CreateClient(t *testing.T) {
194207
Scopes: map[model.AccessScope]struct{}{"read": {}},
195208
}
196209

210+
// confidential client
211+
req2 := &PostClientsRequest{
212+
Name: "test",
213+
Description: "desc",
214+
CallbackURL: "https://example.com",
215+
Scopes: map[model.AccessScope]struct{}{"read": {}},
216+
Confidential: true,
217+
}
218+
197219
t.Run("not logged in", func(t *testing.T) {
198220
t.Parallel()
199221
e := env.R(t)
@@ -233,6 +255,34 @@ func TestHandlers_CreateClient(t *testing.T) {
233255
scopes.Value(0).String().IsEqual("read")
234256
obj.Value("callbackUrl").String().IsEqual("https://example.com")
235257
obj.Value("secret").String().NotEmpty()
258+
obj.Value("confidential").Boolean().IsFalse()
259+
260+
c, err := env.Repository.GetClient(obj.Value("id").String().Raw())
261+
assert.NoError(t, err)
262+
oAuth2ClientEquals(t, c, obj)
263+
})
264+
265+
t.Run("success (confidential client)", func(t *testing.T) {
266+
t.Parallel()
267+
e := env.R(t)
268+
obj := e.POST(path).
269+
WithCookie(session.CookieName, commonSession).
270+
WithJSON(req2).
271+
Expect().
272+
Status(http.StatusCreated).
273+
JSON().
274+
Object()
275+
276+
obj.Value("id").String().NotEmpty()
277+
obj.Value("developerId").String().IsEqual(user.GetID().String())
278+
obj.Value("description").String().IsEqual("desc")
279+
obj.Value("name").String().IsEqual("test")
280+
scopes := obj.Value("scopes").Array()
281+
scopes.Length().IsEqual(1)
282+
scopes.Value(0).String().IsEqual("read")
283+
obj.Value("callbackUrl").String().IsEqual("https://example.com")
284+
obj.Value("secret").String().NotEmpty()
285+
obj.Value("confidential").Boolean().IsTrue()
236286

237287
c, err := env.Repository.GetClient(obj.Value("id").String().Raw())
238288
assert.NoError(t, err)
@@ -250,6 +300,7 @@ func TestHandlers_GetClient(t *testing.T) {
250300
admin := env.CreateAdmin(t, rand)
251301
c1 := env.CreateOAuth2Client(t, rand, user1.GetID())
252302
c2 := env.CreateOAuth2Client(t, rand, user2.GetID())
303+
c3 := env.CreateOAuth2Client(t, rand, user1.GetID(), WithConfidential(true))
253304
user1Session := env.S(t, user1.GetID())
254305
adminSession := env.S(t, admin.GetID())
255306

@@ -337,16 +388,34 @@ func TestHandlers_GetClient(t *testing.T) {
337388
obj.Value("callbackUrl").String().NotEmpty()
338389
obj.Value("secret").String().NotEmpty()
339390
})
391+
392+
t.Run("success (c3, detail=true)", func(t *testing.T) {
393+
t.Parallel()
394+
e := env.R(t)
395+
obj := e.GET(path, c3.ID).
396+
WithCookie(session.CookieName, user1Session).
397+
WithQuery("detail", true).
398+
Expect().
399+
Status(http.StatusOK).
400+
JSON().
401+
Object()
402+
403+
oAuth2ClientEquals(t, c3, obj)
404+
obj.Value("callbackUrl").String().NotEmpty()
405+
obj.Value("secret").String().NotEmpty()
406+
obj.Value("confidential").Boolean().IsTrue()
407+
})
340408
}
341409

342410
func TestPatchClientRequest_Validate(t *testing.T) {
343411
t.Parallel()
344412

345413
type fields struct {
346-
Name optional.Of[string]
347-
Description optional.Of[string]
348-
CallbackURL optional.Of[string]
349-
DeveloperID optional.Of[uuid.UUID]
414+
Name optional.Of[string]
415+
Description optional.Of[string]
416+
CallbackURL optional.Of[string]
417+
DeveloperID optional.Of[uuid.UUID]
418+
Confidential optional.Of[bool]
350419
}
351420
tests := []struct {
352421
name string
@@ -393,14 +462,20 @@ func TestPatchClientRequest_Validate(t *testing.T) {
393462
fields{Name: optional.From("po")},
394463
false,
395464
},
465+
{
466+
"success (confidential client)",
467+
fields{Confidential: optional.From(true)},
468+
false,
469+
},
396470
}
397471
for _, tt := range tests {
398472
t.Run(tt.name, func(t *testing.T) {
399473
r := PatchClientRequest{
400-
Name: tt.fields.Name,
401-
Description: tt.fields.Description,
402-
CallbackURL: tt.fields.CallbackURL,
403-
DeveloperID: tt.fields.DeveloperID,
474+
Name: tt.fields.Name,
475+
Description: tt.fields.Description,
476+
CallbackURL: tt.fields.CallbackURL,
477+
DeveloperID: tt.fields.DeveloperID,
478+
Confidential: tt.fields.Confidential,
404479
}
405480
if err := r.Validate(); (err != nil) != tt.wantErr {
406481
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
@@ -419,6 +494,7 @@ func TestHandlers_EditClient(t *testing.T) {
419494
admin := env.CreateAdmin(t, rand)
420495
c1 := env.CreateOAuth2Client(t, rand, user1.GetID())
421496
c2 := env.CreateOAuth2Client(t, rand, user2.GetID())
497+
c3 := env.CreateOAuth2Client(t, rand, user1.GetID(), WithConfidential(true))
422498
user1Session := env.S(t, user1.GetID())
423499
adminSession := env.S(t, admin.GetID())
424500

@@ -488,6 +564,20 @@ func TestHandlers_EditClient(t *testing.T) {
488564
require.NoError(t, err)
489565
assert.EqualValues(t, c.Name, "po2")
490566
})
567+
568+
t.Run("success (user1, c3, confidential)", func(t *testing.T) {
569+
t.Parallel()
570+
e := env.R(t)
571+
e.PATCH(path, c3.ID).
572+
WithCookie(session.CookieName, user1Session).
573+
WithJSON(&PatchClientRequest{Confidential: optional.From(true)}).
574+
Expect().
575+
Status(http.StatusNoContent)
576+
577+
c, err := env.Repository.GetClient(c3.ID)
578+
require.NoError(t, err)
579+
assert.True(t, c.Confidential)
580+
})
491581
}
492582

493583
func TestHandlers_DeleteClient(t *testing.T) {

router/v3/responses.go

+28-24
Original file line numberDiff line numberDiff line change
@@ -497,20 +497,22 @@ func formatFileInfos(metas []model.File) []*FileInfo {
497497
}
498498

499499
type OAuth2Client struct {
500-
ID string `json:"id"`
501-
Name string `json:"name"`
502-
Description string `json:"description"`
503-
DeveloperID uuid.UUID `json:"developerId"`
504-
Scopes model.AccessScopes `json:"scopes"`
500+
ID string `json:"id"`
501+
Name string `json:"name"`
502+
Description string `json:"description"`
503+
DeveloperID uuid.UUID `json:"developerId"`
504+
Scopes model.AccessScopes `json:"scopes"`
505+
Confidential bool `json:"confidential"`
505506
}
506507

507508
func formatOAuth2Client(oc *model.OAuth2Client) *OAuth2Client {
508509
return &OAuth2Client{
509-
ID: oc.ID,
510-
Name: oc.Name,
511-
Description: oc.Description,
512-
DeveloperID: oc.CreatorID,
513-
Scopes: oc.Scopes,
510+
ID: oc.ID,
511+
Name: oc.Name,
512+
Description: oc.Description,
513+
DeveloperID: oc.CreatorID,
514+
Scopes: oc.Scopes,
515+
Confidential: oc.Confidential,
514516
}
515517
}
516518

@@ -523,24 +525,26 @@ func formatOAuth2Clients(ocs []*model.OAuth2Client) []*OAuth2Client {
523525
}
524526

525527
type OAuth2ClientDetail struct {
526-
ID string `json:"id"`
527-
Name string `json:"name"`
528-
Description string `json:"description"`
529-
DeveloperID uuid.UUID `json:"developerId"`
530-
Scopes model.AccessScopes `json:"scopes"`
531-
CallbackURL string `json:"callbackUrl"`
532-
Secret string `json:"secret"`
528+
ID string `json:"id"`
529+
Name string `json:"name"`
530+
Description string `json:"description"`
531+
DeveloperID uuid.UUID `json:"developerId"`
532+
Scopes model.AccessScopes `json:"scopes"`
533+
CallbackURL string `json:"callbackUrl"`
534+
Secret string `json:"secret"`
535+
Confidential bool `json:"confidential"`
533536
}
534537

535538
func formatOAuth2ClientDetail(oc *model.OAuth2Client) *OAuth2ClientDetail {
536539
return &OAuth2ClientDetail{
537-
ID: oc.ID,
538-
Name: oc.Name,
539-
Description: oc.Description,
540-
DeveloperID: oc.CreatorID,
541-
Scopes: oc.Scopes,
542-
CallbackURL: oc.RedirectURI,
543-
Secret: oc.Secret,
540+
ID: oc.ID,
541+
Name: oc.Name,
542+
Description: oc.Description,
543+
DeveloperID: oc.CreatorID,
544+
Scopes: oc.Scopes,
545+
CallbackURL: oc.RedirectURI,
546+
Secret: oc.Secret,
547+
Confidential: oc.Confidential,
544548
}
545549
}
546550

0 commit comments

Comments
 (0)