From ab55ca7ebd7d30dad894c35e6facd0b1822fb899 Mon Sep 17 00:00:00 2001 From: techknowlogick Date: Fri, 6 Jul 2018 21:54:30 -0400 Subject: [PATCH] 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 d9bf210a174fd..df8203267fd35 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 0000000000000..2520f356b7fa7 --- /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 664290cc9da32..a1e66ffdfdfce 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 9fd790281a9fa..c2efd66da0bbd 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 5007a0d56d10a..689ea22ccaa86 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 fc4118649c068..216190b0f0b93 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 08e98513ee07f..d3bfce971bad4 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 +}