Skip to content

Commit

Permalink
Merge pull request #2477 from YuHima03/issue-1092
Browse files Browse the repository at this point in the history
  • Loading branch information
ikura-hamu authored Jul 12, 2024
2 parents d5fc0d6 + b274ec4 commit 29a5796
Show file tree
Hide file tree
Showing 6 changed files with 154 additions and 0 deletions.
19 changes: 19 additions & 0 deletions docs/v3-api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2896,6 +2896,25 @@ paths:
指定したOAuth2クライアントの情報を変更します。
対象のクライアントの管理権限が必要です。
クライアント開発者UUIDを変更した場合は、変更先ユーザーにクライアント管理権限が移譲され、自分自身は権限を失います。
'/clients/{clientId}/tokens':
delete:
summary: OAuthクライアントのトークンを削除
parameters:
- $ref: '#/components/parameters/clientIdInPath'
responses:
'204':
description: |-
No Content
削除できました。
'404':
description: |-
Not Found
OAuth2クライアントが見つかりません。
tags:
- oauth2
operationId: revokeClientTokens
description: |-
自分が許可している指定したOAuthクライアントのアクセストークンを全てRevokeします。
/clients:
get:
summary: OAuth2クライアントのリストを取得
Expand Down
7 changes: 7 additions & 0 deletions repository/gorm/oauth2.go
Original file line number Diff line number Diff line change
Expand Up @@ -240,3 +240,10 @@ func (repo *Repository) DeleteTokenByClient(clientID string) error {
}
return repo.db.Delete(&model.OAuth2Token{}, &model.OAuth2Token{ClientID: clientID}).Error
}

func (repo *Repository) DeleteUserTokensByClient(userID uuid.UUID, clientID string) error {
if userID == uuid.Nil || len(clientID) == 0 {
return nil
}
return repo.db.Delete(&model.OAuth2Token{}, &model.OAuth2Token{UserID: userID, ClientID: clientID}).Error
}
5 changes: 5 additions & 0 deletions repository/oauth2.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,9 @@ type OAuth2Repository interface {
// 成功した場合、nilを返します。
// DBによるエラーを返すことがあります。
DeleteTokenByClient(clientID string) error
// DeleteUserTokensByClient 指定したユーザーの指定したクライアントのトークンをすべて削除します
//
// 成功した場合、nilを返します。
// DBによるエラーを返すことがあります。
DeleteUserTokensByClient(userID uuid.UUID, clientID string) error
}
13 changes: 13 additions & 0 deletions router/v3/clients.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,16 @@ func (h *Handlers) DeleteClient(c echo.Context) error {

return c.NoContent(http.StatusNoContent)
}

// RevokeClientTokens /clients/:clientID/tokens
func (h *Handlers) RevokeClientTokens(c echo.Context) error {
oc := getParamClient(c)
userID := getRequestUserID(c)

err := h.Repo.DeleteUserTokensByClient(userID, oc.ID)
if err != nil {
return herror.InternalServerError(err)
}

return c.NoContent(http.StatusNoContent)
}
109 changes: 109 additions & 0 deletions router/v3/clients_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -555,3 +555,112 @@ func TestHandlers_DeleteClient(t *testing.T) {
assert.ErrorIs(t, err, repository.ErrNotFound)
})
}

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

path := "/api/v3/clients/{clientId}/tokens"
env := Setup(t, common1)

user1 := env.CreateUser(t, rand)
user2 := env.CreateUser(t, rand)
client1 := env.CreateOAuth2Client(t, rand, user1.GetID())
client2 := env.CreateOAuth2Client(t, rand, user1.GetID())

tests := map[string]struct {
user model.UserInfo
userTokenCounts map[uuid.UUID][]*model.OAuth2Client
clientID string
statusCode int
}{
"not logged in": {
nil,
nil,
client1.ID,
http.StatusUnauthorized,
},
"success": {
user1,
map[uuid.UUID][]*model.OAuth2Client{
user1.GetID(): {client1},
},
client1.ID,
http.StatusNoContent,
},
"success (同じクライアント, 複数トークン)": {
user1,
map[uuid.UUID][]*model.OAuth2Client{
user1.GetID(): {client1, client1},
},
client1.ID,
http.StatusNoContent,
},
"success (複数クライアント, 複数トークン)": {
user1,
map[uuid.UUID][]*model.OAuth2Client{
user1.GetID(): {client1, client2},
},
client1.ID,
http.StatusNoContent,
},
"success (複数ユーザー, 複数クライアント, 複数トークン)": {
user1,
map[uuid.UUID][]*model.OAuth2Client{
user1.GetID(): {client1},
user2.GetID(): {client1},
},
client1.ID,
http.StatusNoContent,
},
"success (トークン無し)": {
user1,
nil,
client1.ID,
http.StatusNoContent,
},
"クライアントが存在しない": {
user1,
map[uuid.UUID][]*model.OAuth2Client{
user1.GetID(): {client1},
},
uuid.Must(uuid.NewV7()).String(),
http.StatusNotFound,
},
}

for name, tt := range tests {
t.Run(name, func(t *testing.T) {
tokens := make(map[uuid.UUID][]*model.OAuth2Token, len(tt.userTokenCounts))
for userID, clients := range tt.userTokenCounts {
for _, c := range clients {
tokens[userID] = append(tokens[userID], env.IssueToken(t, c, userID))
}
}

e := env.R(t)

if tt.user != nil {
sess := env.S(t, tt.user.GetID())
e.DELETE(path, tt.clientID).
WithCookie(session.CookieName, sess).
Expect().
Status(tt.statusCode)
} else {
e.DELETE(path, tt.clientID).
Expect().
Status(tt.statusCode)
}

for userID, userTokens := range tokens {
for _, userToken := range userTokens {
_, err := env.Repository.GetTokenByID(userToken.ID)
if userToken.ClientID == tt.clientID && userID == tt.user.GetID() && tt.statusCode == http.StatusNoContent {
assert.ErrorIs(t, err, repository.ErrNotFound)
} else {
assert.NoError(t, err)
}
}
}
})
}
}
1 change: 1 addition & 0 deletions router/v3/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ func (h *Handlers) Setup(e *echo.Group) {
apiClientsCID.GET("", h.GetClient, requires(permission.GetClients))
apiClientsCID.PATCH("", h.EditClient, requiresClientAccessPerm, requires(permission.EditMyClient))
apiClientsCID.DELETE("", h.DeleteClient, requiresClientAccessPerm, requires(permission.DeleteMyClient))
apiClientsCID.DELETE("/tokens", h.RevokeClientTokens, requires(permission.RevokeMyToken))
}
}
apiBots := api.Group("/bots")
Expand Down

0 comments on commit 29a5796

Please sign in to comment.