@@ -11,7 +11,7 @@ | |||||
branch = "master" | branch = "master" | ||||
name = "code.gitea.io/sdk" | name = "code.gitea.io/sdk" | ||||
packages = ["gitea"] | packages = ["gitea"] | ||||
revision = "b2308e3f700875a3642a78bd3f6e5db8ef6f974d" | |||||
revision = "ec80752c9512cf07fc62ddc42565118183743942" | |||||
[[projects]] | [[projects]] | ||||
name = "github.com/PuerkitoBio/goquery" | name = "github.com/PuerkitoBio/goquery" | ||||
@@ -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) | |||||
} |
@@ -256,6 +256,11 @@ func NewRequestWithBody(t testing.TB, method, urlStr string, body io.Reader) *ht | |||||
return request | return request | ||||
} | } | ||||
func AddBasicAuthHeader(request *http.Request, username string) *http.Request { | |||||
request.SetBasicAuth(username, userPassword) | |||||
return request | |||||
} | |||||
const NoExpectedStatus = -1 | const NoExpectedStatus = -1 | ||||
func MakeRequest(t testing.TB, req *http.Request, expectedStatus int) *httptest.ResponseRecorder { | func MakeRequest(t testing.TB, req *http.Request, expectedStatus int) *httptest.ResponseRecorder { | ||||
@@ -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": { | "/version": { | ||||
"get": { | "get": { | ||||
"produces": [ | "produces": [ | ||||
@@ -7479,6 +7512,10 @@ | |||||
"AccessToken": { | "AccessToken": { | ||||
"description": "AccessToken represents a API access token.", | "description": "AccessToken represents a API access token.", | ||||
"headers": { | "headers": { | ||||
"id": { | |||||
"type": "integer", | |||||
"format": "int64" | |||||
}, | |||||
"name": { | "name": { | ||||
"type": "string" | "type": "string" | ||||
}, | }, | ||||
@@ -302,6 +302,7 @@ func RegisterRoutes(m *macaron.Macaron) { | |||||
m.Group("/tokens", func() { | m.Group("/tokens", func() { | ||||
m.Combo("").Get(user.ListAccessTokens). | m.Combo("").Get(user.ListAccessTokens). | ||||
Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken) | Post(bind(api.CreateAccessTokenOption{}), user.CreateAccessToken) | ||||
m.Combo("/:id").Delete(user.DeleteAccessToken) | |||||
}, reqBasicAuth()) | }, reqBasicAuth()) | ||||
}) | }) | ||||
}) | }) | ||||
@@ -1,4 +1,5 @@ | |||||
// Copyright 2014 The Gogs Authors. All rights reserved. | // 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 | // Use of this source code is governed by a MIT-style | ||||
// license that can be found in the LICENSE file. | // license that can be found in the LICENSE file. | ||||
@@ -36,6 +37,7 @@ func ListAccessTokens(ctx *context.APIContext) { | |||||
apiTokens := make([]*api.AccessToken, len(tokens)) | apiTokens := make([]*api.AccessToken, len(tokens)) | ||||
for i := range tokens { | for i := range tokens { | ||||
apiTokens[i] = &api.AccessToken{ | apiTokens[i] = &api.AccessToken{ | ||||
ID: tokens[i].ID, | |||||
Name: tokens[i].Name, | Name: tokens[i].Name, | ||||
Sha1: tokens[i].Sha1, | Sha1: tokens[i].Sha1, | ||||
} | } | ||||
@@ -72,5 +74,40 @@ func CreateAccessToken(ctx *context.APIContext, form api.CreateAccessTokenOption | |||||
ctx.JSON(201, &api.AccessToken{ | ctx.JSON(201, &api.AccessToken{ | ||||
Name: t.Name, | Name: t.Name, | ||||
Sha1: t.Sha1, | 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) | |||||
} |
@@ -20,6 +20,7 @@ func BasicAuthEncode(user, pass string) string { | |||||
// AccessToken represents a API access token. | // AccessToken represents a API access token. | ||||
// swagger:response AccessToken | // swagger:response AccessToken | ||||
type AccessToken struct { | type AccessToken struct { | ||||
ID int64 `json:"id"` | |||||
Name string `json:"name"` | Name string `json:"name"` | ||||
Sha1 string `json:"sha1"` | Sha1 string `json:"sha1"` | ||||
} | } | ||||
@@ -54,3 +55,9 @@ func (c *Client) CreateAccessToken(user, pass string, opt CreateAccessTokenOptio | |||||
"Authorization": []string{"Basic " + BasicAuthEncode(user, pass)}}, | "Authorization": []string{"Basic " + BasicAuthEncode(user, pass)}}, | ||||
bytes.NewReader(body), t) | 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 | |||||
} |