From ab55ca7ebd7d30dad894c35e6facd0b1822fb899 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Fri, 6 Jul 2018 21:54:30 -0400 Subject: [PATCH 1/2] Add ability to delete a token (#4235) Fix #4234 --- Gopkg.lock | 2 +- integrations/api_token_test.go | 50 ++++++++++++++++++++++ integrations/integration_test.go | 5 +++ public/swagger.v1.json | 37 ++++++++++++++++ routers/api/v1/api.go | 1 + routers/api/v1/user/app.go | 37 ++++++++++++++++ vendor/code.gitea.io/sdk/gitea/user_app.go | 7 +++ 7 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 integrations/api_token_test.go diff --git a/Gopkg.lock b/Gopkg.lock index d9bf210a1..df8203267 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -11,7 +11,7 @@ branch = "master" name = "code.gitea.io/sdk" packages = ["gitea"] - revision = "b2308e3f700875a3642a78bd3f6e5db8ef6f974d" + revision = "ec80752c9512cf07fc62ddc42565118183743942" [[projects]] name = "github.com/PuerkitoBio/goquery" diff --git a/integrations/api_token_test.go b/integrations/api_token_test.go new file mode 100644 index 000000000..2520f356b --- /dev/null +++ b/integrations/api_token_test.go @@ -0,0 +1,50 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package integrations + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/models" + api "code.gitea.io/sdk/gitea" +) + +// TestAPICreateAndDeleteToken tests that token that was just created can be deleted +func TestAPICreateAndDeleteToken(t *testing.T) { + prepareTestEnv(t) + user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) + + req := NewRequestWithJSON(t, "POST", "/api/v1/users/user1/tokens", map[string]string{ + "name": "test-key-1", + }) + req = AddBasicAuthHeader(req, user.Name) + resp := MakeRequest(t, req, http.StatusCreated) + + var newAccessToken api.AccessToken + DecodeJSON(t, resp, &newAccessToken) + models.AssertExistsAndLoadBean(t, &models.AccessToken{ + ID: newAccessToken.ID, + Name: newAccessToken.Name, + Sha1: newAccessToken.Sha1, + UID: user.ID, + }) + + req = NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", newAccessToken.ID) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNoContent) + + models.AssertNotExistsBean(t, &models.AccessToken{ID: newAccessToken.ID}) +} + +// TestAPIDeleteMissingToken ensures that error is thrown when token not found +func TestAPIDeleteMissingToken(t *testing.T) { + prepareTestEnv(t) + user := models.AssertExistsAndLoadBean(t, &models.User{ID: 1}).(*models.User) + + req := NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%d", models.NonexistentID) + req = AddBasicAuthHeader(req, user.Name) + MakeRequest(t, req, http.StatusNotFound) +} diff --git a/integrations/integration_test.go b/integrations/integration_test.go index 664290cc9..a1e66ffdf 100644 --- a/integrations/integration_test.go +++ b/integrations/integration_test.go @@ -256,6 +256,11 @@ func NewRequestWithBody(t testing.TB, method, urlStr string, body io.Reader) *ht return request } +func AddBasicAuthHeader(request *http.Request, username string) *http.Request { + request.SetBasicAuth(username, userPassword) + return request +} + const NoExpectedStatus = -1 func MakeRequest(t testing.TB, req *http.Request, expectedStatus int) *httptest.ResponseRecorder { diff --git a/public/swagger.v1.json b/public/swagger.v1.json index 9fd790281..c2efd66da 100644 --- a/public/swagger.v1.json +++ b/public/swagger.v1.json @@ -5441,6 +5441,39 @@ } } }, + "/users/{username}/tokens/{token}": { + "delete": { + "produces": [ + "application/json" + ], + "tags": [ + "user" + ], + "summary": "delete an access token", + "operationId": "userDeleteAccessToken", + "parameters": [ + { + "type": "string", + "description": "username of user", + "name": "username", + "in": "path", + "required": true + }, + { + "type": "integer", + "description": "token to be deleted", + "name": "token", + "in": "path", + "required": true + } + ], + "responses": { + "204": { + "$ref": "#/responses/empty" + } + } + } + }, "/version": { "get": { "produces": [ @@ -7479,6 +7512,10 @@ "AccessToken": { "description": "AccessToken represents a API access token.", "headers": { + "id": { + "type": "integer", + "format": "int64" + }, "name": { "type": "string" }, diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go index 5007a0d56..689ea22cc 100644 --- a/routers/api/v1/api.go +++ b/routers/api/v1/api.go @@ -302,6 +302,7 @@ func RegisterRoutes(m *macaron.Macaron) { m.Group("/tokens", func() { m.Combo("").Get(user.ListAccessTokens). Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken) + m.Combo("/:id").Delete(user.DeleteAccessToken) }, reqBasicAuth()) }) }) diff --git a/routers/api/v1/user/app.go b/routers/api/v1/user/app.go index fc4118649..216190b0f 100644 --- a/routers/api/v1/user/app.go +++ b/routers/api/v1/user/app.go @@ -1,4 +1,5 @@ // Copyright 2014 The Gogs Authors. All rights reserved. +// Copyright 2018 The Gitea Authors. All rights reserved. // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. @@ -36,6 +37,7 @@ func ListAccessTokens(ctx *context.APIContext) { apiTokens := make([]*api.AccessToken, len(tokens)) for i := range tokens { apiTokens[i] = &api.AccessToken{ + ID: tokens[i].ID, Name: tokens[i].Name, Sha1: tokens[i].Sha1, } @@ -72,5 +74,40 @@ func CreateAccessToken(ctx *context.APIContext, form api.CreateAccessTokenOption ctx.JSON(201, &api.AccessToken{ Name: t.Name, Sha1: t.Sha1, + ID: t.ID, }) } + +// DeleteAccessToken delete access tokens +func DeleteAccessToken(ctx *context.APIContext) { + // swagger:operation DELETE /users/{username}/tokens/{token} user userDeleteAccessToken + // --- + // summary: delete an access token + // produces: + // - application/json + // parameters: + // - name: username + // in: path + // description: username of user + // type: string + // required: true + // - name: token + // in: path + // description: token to be deleted + // type: integer + // required: true + // responses: + // "204": + // "$ref": "#/responses/empty" + tokenID := ctx.ParamsInt64(":id") + if err := models.DeleteAccessTokenByID(tokenID, ctx.User.ID); err != nil { + if models.IsErrAccessTokenNotExist(err) { + ctx.Status(404) + } else { + ctx.Error(500, "DeleteAccessTokenByID", err) + } + return + } + + ctx.Status(204) +} diff --git a/vendor/code.gitea.io/sdk/gitea/user_app.go b/vendor/code.gitea.io/sdk/gitea/user_app.go index 08e98513e..d3bfce971 100644 --- a/vendor/code.gitea.io/sdk/gitea/user_app.go +++ b/vendor/code.gitea.io/sdk/gitea/user_app.go @@ -20,6 +20,7 @@ func BasicAuthEncode(user, pass string) string { // AccessToken represents a API access token. // swagger:response AccessToken type AccessToken struct { + ID int64 `json:"id"` Name string `json:"name"` Sha1 string `json:"sha1"` } @@ -54,3 +55,9 @@ func (c *Client) CreateAccessToken(user, pass string, opt CreateAccessTokenOptio "Authorization": []string{"Basic " + BasicAuthEncode(user, pass)}}, bytes.NewReader(body), t) } + +// DeleteAccessToken delete token with key id +func (c *Client) DeleteAccessToken(user string, keyID int64) error { + _, err := c.getResponse("DELETE", fmt.Sprintf("/user/%s/tokens/%d", user, keyID), nil, nil) + return err +} From efd202f40f921421062dd2453fa9c9e1d1f6a174 Mon Sep 17 00:00:00 2001 From: GiteaBot Date: Sat, 7 Jul 2018 01:55:43 +0000 Subject: [PATCH 2/2] [skip ci] Updated translations via Crowdin --- options/locale/locale_de-DE.ini | 2 +- options/locale/locale_zh-CN.ini | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/options/locale/locale_de-DE.ini b/options/locale/locale_de-DE.ini index e22f586f6..6206db445 100644 --- a/options/locale/locale_de-DE.ini +++ b/options/locale/locale_de-DE.ini @@ -669,7 +669,7 @@ issues.change_milestone_at=`hat den Meilenstein %[3]s von %[1]s zu %[2 issues.remove_milestone_at=`hat diesen Issue %[2]s vom %[1]s Meilenstein entfernt` issues.deleted_milestone=`(gelöscht)` issues.self_assign_at=`hat sich das Issue %s selbst zugewiesen` -issues.add_assignee_at=`wurde %[1]s zugewiesen von %[2]s` +issues.add_assignee_at=`wurde von %s %s zugewiesen` issues.remove_assignee_at=`hat seine Zuweisung %s entfernt` issues.change_title_at=`Titel von %s nach %s %s geändert` issues.delete_branch_at=`löschte die Branch %s %s` diff --git a/options/locale/locale_zh-CN.ini b/options/locale/locale_zh-CN.ini index ad89a0e98..53a4a0426 100644 --- a/options/locale/locale_zh-CN.ini +++ b/options/locale/locale_zh-CN.ini @@ -75,6 +75,7 @@ cancel=取消 [install] install=安装页面 title=初始配置 +docker_helper=如果您正在使用 Docker 容器运行 Gitea,请务必先仔细阅读 官方文档 后再对本页面进行填写。 requite_db_desc=Gitea 要求安装 MySQL、PostgreSQL、SQLite3 或 TiDB。 db_title=数据库设置 db_type=数据库类型 @@ -491,8 +492,13 @@ owner=拥有者 repo_name=仓库名称 repo_name_helper=好的存储库名称使用简短、深刻和独特的关键字。 visibility=可见性 +visibility_helper=将仓库设为私有 +visibility_helper_forced=站点管理员强制要求新仓库为私有。 +visibility_fork_helper=(修改该值将会影响到所有派生仓库) +clone_helper=不知道如何克隆?查看帮助 。 fork_repo=派生仓库 fork_from=派生自 +fork_visibility_helper=无法更改派生仓库的可见性。 repo_desc=仓库描述 repo_lang=仓库语言 repo_gitignore_helper=选择 .gitignore 模板。 @@ -607,6 +613,7 @@ editor.directory_is_a_file='%s' 已经作为文件名在此仓库中存在。 editor.file_is_a_symlink='%s' 是一个符号链接,无法在线编辑。 editor.filename_is_a_directory='%s' 已经作为目录名在此仓库中存在。 editor.file_editing_no_longer_exists=正在编辑的文件 '%s' 已不存在。 +editor.file_changed_while_editing=文件内容在您进行编辑时已经发生变动。单击此处 查看变动的具体内容,或者 再次提交 覆盖已发生的变动。 editor.file_already_exists=此仓库已经存在名为 '%s' 的文件。 editor.no_changes_to_show=没有可以显示的变更。 editor.fail_to_update_file=更新/创建文件 '%s' 时发生错误:%v @@ -987,6 +994,7 @@ settings.search_user_placeholder=搜索用户... settings.org_not_allowed_to_be_collaborator=组织不允许被添加为仓库协作者! settings.user_is_org_member=被操作的用户是组织,因此无法添加为协作者! settings.add_webhook=添加 Web 钩子 +settings.hooks_desc=当Gitea事件发生时,Web钩子自动发出HTTP POST请求。在 指南 中阅读更多内容。 settings.webhook_deletion=删除 Web 钩子 settings.webhook_deletion_desc=删除 web钩子 将删除其设置和历史记录。继续? settings.webhook_deletion_success=Web 钩子删除成功! @@ -1003,6 +1011,7 @@ settings.githook_edit_desc=如果钩子未启动,则会显示样例文件中 settings.githook_name=钩子名称 settings.githook_content=钩子文本 settings.update_githook=更新钩子设置 +settings.add_webhook_desc=Gitea 将向目标 URL 发送具有指定内容类型的 POST 请求。在 webhooks 指南 中阅读更多内容。 settings.payload_url=目标 URL settings.content_type=POST Content Type settings.secret=密钥文本 @@ -1264,6 +1273,8 @@ dashboard.operation_switch=开关 dashboard.operation_run=执行 dashboard.clean_unbind_oauth=清理未绑定的 OAuth 连接 dashboard.clean_unbind_oauth_success=所有未绑定的 OAuth 连接已被删除。 +dashboard.delete_inactivate_accounts=删除所有未激活的帐户 +dashboard.delete_inactivate_accounts_success=所有未激活的帐户都已删除。 dashboard.delete_repo_archives=删除所有仓库存档 dashboard.delete_repo_archives_success=所有仓库存档清除成功! dashboard.delete_missing_repos=删除所有丢失 Git 文件的仓库