Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

confidential clientのサポート(client typeを選択可能に) #2426

Merged
merged 5 commits into from
Jul 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/v3-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5848,12 +5848,16 @@ components:
description: 要求スコープの配列
items:
$ref: '#/components/schemas/OAuth2Scope'
confidential:
type: boolean
description: confidential client なら true, public client なら false
required:
- id
- name
- description
- developerId
- scopes
- confidential
PatchClientRequest:
title: PatchClientRequest
type: object
Expand All @@ -5876,6 +5880,9 @@ components:
type: string
description: クライアント開発者UUID
format: uuid
confidential:
type: boolean
description: confidential client なら true, public client なら false
OAuth2ClientDetail:
title: OAuth2ClientDetail
description: OAuth2クライアント詳細情報
Expand Down Expand Up @@ -5909,6 +5916,9 @@ components:
secret:
type: string
description: クライアントシークレット
confidential:
type: boolean
description: confidential client なら true, public client なら false
required:
- id
- developerId
Expand All @@ -5917,6 +5927,7 @@ components:
- scopes
- callbackUrl
- secret
- confidential
PostClientRequest:
title: PostClientRequest
type: object
Expand All @@ -5940,6 +5951,10 @@ components:
type: string
description: 説明
maxLength: 1000
confidential:
type: boolean
description: confidential client なら true, public cleint なら false
default: false
required:
- name
- callbackUrl
Expand Down
29 changes: 16 additions & 13 deletions router/v3/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ func (h *Handlers) GetClients(c echo.Context) error {

// PostClientsRequest POST /clients リクエストボディ
type PostClientsRequest struct {
Name string `json:"name"`
Description string `json:"description"`
CallbackURL string `json:"callbackUrl"`
Scopes model.AccessScopes `json:"scopes"`
Name string `json:"name"`
Description string `json:"description"`
CallbackURL string `json:"callbackUrl"`
Scopes model.AccessScopes `json:"scopes"`
Confidential bool `json:"confidential"` // default false (public client)
}

func (r PostClientsRequest) Validate() error {
Expand All @@ -62,7 +63,7 @@ func (h *Handlers) CreateClient(c echo.Context) error {
ID: random.SecureAlphaNumeric(36),
Name: req.Name,
Description: req.Description,
Confidential: false,
Confidential: req.Confidential,
CreatorID: userID,
RedirectURI: req.CallbackURL,
Secret: random.SecureAlphaNumeric(36),
Expand Down Expand Up @@ -92,10 +93,11 @@ func (h *Handlers) GetClient(c echo.Context) error {

// PatchClientRequest PATCH /clients/:clientID リクエストボディ
type PatchClientRequest struct {
Name optional.Of[string] `json:"name"`
Description optional.Of[string] `json:"description"`
CallbackURL optional.Of[string] `json:"callbackUrl"`
DeveloperID optional.Of[uuid.UUID] `json:"developerId"`
Name optional.Of[string] `json:"name"`
Description optional.Of[string] `json:"description"`
CallbackURL optional.Of[string] `json:"callbackUrl"`
DeveloperID optional.Of[uuid.UUID] `json:"developerId"`
Confidential optional.Of[bool] `json:"confidential"`
}

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

args := repository.UpdateClientArgs{
Name: req.Name,
Description: req.Description,
DeveloperID: req.DeveloperID,
CallbackURL: req.CallbackURL,
Name: req.Name,
Description: req.Description,
DeveloperID: req.DeveloperID,
CallbackURL: req.CallbackURL,
Confidential: req.Confidential,
}
if err := h.Repo.UpdateClient(oc.ID, args); err != nil {
return herror.InternalServerError(err)
Expand Down
116 changes: 103 additions & 13 deletions router/v3/clients_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ func oAuth2ClientEquals(t *testing.T, expect *model.OAuth2Client, actual *httpex
scopes = append(scopes, scope)
}
actual.Value("scopes").Array().ContainsOnly(scopes...)
actual.Value("confidential").Boolean().IsEqual(expect.Confidential)
}

func TestHandlers_GetClients(t *testing.T) {
Expand All @@ -38,7 +39,7 @@ func TestHandlers_GetClients(t *testing.T) {
user := env.CreateUser(t, rand)
user2 := env.CreateUser(t, rand)
c1 := env.CreateOAuth2Client(t, rand, user.GetID())
c2 := env.CreateOAuth2Client(t, rand, user2.GetID())
c2 := env.CreateOAuth2Client(t, rand, user2.GetID(), WithConfidential(true))
commonSession := env.S(t, user.GetID())

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

type fields struct {
Name string
Description string
CallbackURL string
Scopes model.AccessScopes
Name string
Description string
CallbackURL string
Scopes model.AccessScopes
Confidential bool
}
tests := []struct {
name string
Expand Down Expand Up @@ -163,6 +165,17 @@ func TestPostClientsRequest_Validate(t *testing.T) {
},
false,
},
{
"success (confidential client)",
fields{
Name: "test",
Description: "desc",
CallbackURL: "https://example.com",
Scopes: map[model.AccessScope]struct{}{"read": {}},
Confidential: true,
},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -194,6 +207,15 @@ func TestHandlers_CreateClient(t *testing.T) {
Scopes: map[model.AccessScope]struct{}{"read": {}},
}

// confidential client
req2 := &PostClientsRequest{
Name: "test",
Description: "desc",
CallbackURL: "https://example.com",
Scopes: map[model.AccessScope]struct{}{"read": {}},
Confidential: true,
}

t.Run("not logged in", func(t *testing.T) {
t.Parallel()
e := env.R(t)
Expand Down Expand Up @@ -233,6 +255,34 @@ func TestHandlers_CreateClient(t *testing.T) {
scopes.Value(0).String().IsEqual("read")
obj.Value("callbackUrl").String().IsEqual("https://example.com")
obj.Value("secret").String().NotEmpty()
obj.Value("confidential").Boolean().IsFalse()

c, err := env.Repository.GetClient(obj.Value("id").String().Raw())
assert.NoError(t, err)
oAuth2ClientEquals(t, c, obj)
})

t.Run("success (confidential client)", func(t *testing.T) {
t.Parallel()
e := env.R(t)
obj := e.POST(path).
WithCookie(session.CookieName, commonSession).
WithJSON(req2).
Expect().
Status(http.StatusCreated).
JSON().
Object()

obj.Value("id").String().NotEmpty()
obj.Value("developerId").String().IsEqual(user.GetID().String())
obj.Value("description").String().IsEqual("desc")
obj.Value("name").String().IsEqual("test")
scopes := obj.Value("scopes").Array()
scopes.Length().IsEqual(1)
scopes.Value(0).String().IsEqual("read")
obj.Value("callbackUrl").String().IsEqual("https://example.com")
obj.Value("secret").String().NotEmpty()
obj.Value("confidential").Boolean().IsTrue()

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

Expand Down Expand Up @@ -337,16 +388,34 @@ func TestHandlers_GetClient(t *testing.T) {
obj.Value("callbackUrl").String().NotEmpty()
obj.Value("secret").String().NotEmpty()
})

t.Run("success (c3, detail=true)", func(t *testing.T) {
t.Parallel()
e := env.R(t)
obj := e.GET(path, c3.ID).
WithCookie(session.CookieName, user1Session).
WithQuery("detail", true).
Expect().
Status(http.StatusOK).
JSON().
Object()

oAuth2ClientEquals(t, c3, obj)
obj.Value("callbackUrl").String().NotEmpty()
obj.Value("secret").String().NotEmpty()
obj.Value("confidential").Boolean().IsTrue()
})
}

func TestPatchClientRequest_Validate(t *testing.T) {
t.Parallel()

type fields struct {
Name optional.Of[string]
Description optional.Of[string]
CallbackURL optional.Of[string]
DeveloperID optional.Of[uuid.UUID]
Name optional.Of[string]
Description optional.Of[string]
CallbackURL optional.Of[string]
DeveloperID optional.Of[uuid.UUID]
Confidential optional.Of[bool]
}
tests := []struct {
name string
Expand Down Expand Up @@ -393,14 +462,20 @@ func TestPatchClientRequest_Validate(t *testing.T) {
fields{Name: optional.From("po")},
false,
},
{
"success (confidential client)",
fields{Confidential: optional.From(true)},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r := PatchClientRequest{
Name: tt.fields.Name,
Description: tt.fields.Description,
CallbackURL: tt.fields.CallbackURL,
DeveloperID: tt.fields.DeveloperID,
Name: tt.fields.Name,
Description: tt.fields.Description,
CallbackURL: tt.fields.CallbackURL,
DeveloperID: tt.fields.DeveloperID,
Confidential: tt.fields.Confidential,
}
if err := r.Validate(); (err != nil) != tt.wantErr {
t.Errorf("Validate() error = %v, wantErr %v", err, tt.wantErr)
Expand All @@ -419,6 +494,7 @@ func TestHandlers_EditClient(t *testing.T) {
admin := env.CreateAdmin(t, rand)
c1 := env.CreateOAuth2Client(t, rand, user1.GetID())
c2 := env.CreateOAuth2Client(t, rand, user2.GetID())
c3 := env.CreateOAuth2Client(t, rand, user1.GetID(), WithConfidential(true))
user1Session := env.S(t, user1.GetID())
adminSession := env.S(t, admin.GetID())

Expand Down Expand Up @@ -488,6 +564,20 @@ func TestHandlers_EditClient(t *testing.T) {
require.NoError(t, err)
assert.EqualValues(t, c.Name, "po2")
})

t.Run("success (user1, c3, confidential)", func(t *testing.T) {
t.Parallel()
e := env.R(t)
e.PATCH(path, c3.ID).
WithCookie(session.CookieName, user1Session).
WithJSON(&PatchClientRequest{Confidential: optional.From(true)}).
Expect().
Status(http.StatusNoContent)

c, err := env.Repository.GetClient(c3.ID)
require.NoError(t, err)
assert.True(t, c.Confidential)
})
}

func TestHandlers_DeleteClient(t *testing.T) {
Expand Down
52 changes: 28 additions & 24 deletions router/v3/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -497,20 +497,22 @@ func formatFileInfos(metas []model.File) []*FileInfo {
}

type OAuth2Client struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
DeveloperID uuid.UUID `json:"developerId"`
Scopes model.AccessScopes `json:"scopes"`
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
DeveloperID uuid.UUID `json:"developerId"`
Scopes model.AccessScopes `json:"scopes"`
Confidential bool `json:"confidential"`
}

func formatOAuth2Client(oc *model.OAuth2Client) *OAuth2Client {
return &OAuth2Client{
ID: oc.ID,
Name: oc.Name,
Description: oc.Description,
DeveloperID: oc.CreatorID,
Scopes: oc.Scopes,
ID: oc.ID,
Name: oc.Name,
Description: oc.Description,
DeveloperID: oc.CreatorID,
Scopes: oc.Scopes,
Confidential: oc.Confidential,
}
}

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

type OAuth2ClientDetail struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
DeveloperID uuid.UUID `json:"developerId"`
Scopes model.AccessScopes `json:"scopes"`
CallbackURL string `json:"callbackUrl"`
Secret string `json:"secret"`
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
DeveloperID uuid.UUID `json:"developerId"`
Scopes model.AccessScopes `json:"scopes"`
CallbackURL string `json:"callbackUrl"`
Secret string `json:"secret"`
Confidential bool `json:"confidential"`
}

func formatOAuth2ClientDetail(oc *model.OAuth2Client) *OAuth2ClientDetail {
return &OAuth2ClientDetail{
ID: oc.ID,
Name: oc.Name,
Description: oc.Description,
DeveloperID: oc.CreatorID,
Scopes: oc.Scopes,
CallbackURL: oc.RedirectURI,
Secret: oc.Secret,
ID: oc.ID,
Name: oc.Name,
Description: oc.Description,
DeveloperID: oc.CreatorID,
Scopes: oc.Scopes,
CallbackURL: oc.RedirectURI,
Secret: oc.Secret,
Confidential: oc.Confidential,
}
}

Expand Down
Loading
Loading