* Delete Token accept names too * better description Co-authored-by: zeripath <art27@cantab.net> Co-authored-by: Lauris BH <lauris@nix.lv>tags/v1.13.0-rc1
@@ -37,6 +37,19 @@ func TestAPICreateAndDeleteToken(t *testing.T) { | |||
MakeRequest(t, req, http.StatusNoContent) | |||
models.AssertNotExistsBean(t, &models.AccessToken{ID: newAccessToken.ID}) | |||
req = NewRequestWithJSON(t, "POST", "/api/v1/users/user1/tokens", map[string]string{ | |||
"name": "test-key-2", | |||
}) | |||
req = AddBasicAuthHeader(req, user.Name) | |||
resp = MakeRequest(t, req, http.StatusCreated) | |||
DecodeJSON(t, resp, &newAccessToken) | |||
req = NewRequestf(t, "DELETE", "/api/v1/users/user1/tokens/%s", newAccessToken.Name) | |||
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 |
@@ -82,16 +82,27 @@ func AccessTokenByNameExists(token *AccessToken) (bool, error) { | |||
return x.Table("access_token").Where("name = ?", token.Name).And("uid = ?", token.UID).Exist() | |||
} | |||
// ListAccessTokensOptions contain filter options | |||
type ListAccessTokensOptions struct { | |||
ListOptions | |||
Name string | |||
UserID int64 | |||
} | |||
// ListAccessTokens returns a list of access tokens belongs to given user. | |||
func ListAccessTokens(uid int64, listOptions ListOptions) ([]*AccessToken, error) { | |||
sess := x. | |||
Where("uid=?", uid). | |||
Desc("id") | |||
func ListAccessTokens(opts ListAccessTokensOptions) ([]*AccessToken, error) { | |||
sess := x.Where("uid=?", opts.UserID) | |||
if len(opts.Name) != 0 { | |||
sess = sess.Where("name=?", opts.Name) | |||
} | |||
sess = sess.Desc("id") | |||
if listOptions.Page != 0 { | |||
sess = listOptions.setSessionPagination(sess) | |||
if opts.Page != 0 { | |||
sess = opts.setSessionPagination(sess) | |||
tokens := make([]*AccessToken, 0, listOptions.PageSize) | |||
tokens := make([]*AccessToken, 0, opts.PageSize) | |||
return tokens, sess.Find(&tokens) | |||
} | |||
@@ -83,7 +83,7 @@ func TestGetAccessTokenBySHA(t *testing.T) { | |||
func TestListAccessTokens(t *testing.T) { | |||
assert.NoError(t, PrepareTestDatabase()) | |||
tokens, err := ListAccessTokens(1, ListOptions{}) | |||
tokens, err := ListAccessTokens(ListAccessTokensOptions{UserID: 1}) | |||
assert.NoError(t, err) | |||
if assert.Len(t, tokens, 2) { | |||
assert.Equal(t, int64(1), tokens[0].UID) | |||
@@ -92,14 +92,14 @@ func TestListAccessTokens(t *testing.T) { | |||
assert.Contains(t, []string{tokens[0].Name, tokens[1].Name}, "Token B") | |||
} | |||
tokens, err = ListAccessTokens(2, ListOptions{}) | |||
tokens, err = ListAccessTokens(ListAccessTokensOptions{UserID: 2}) | |||
assert.NoError(t, err) | |||
if assert.Len(t, tokens, 1) { | |||
assert.Equal(t, int64(2), tokens[0].UID) | |||
assert.Equal(t, "Token A", tokens[0].Name) | |||
} | |||
tokens, err = ListAccessTokens(100, ListOptions{}) | |||
tokens, err = ListAccessTokens(ListAccessTokensOptions{UserID: 100}) | |||
assert.NoError(t, err) | |||
assert.Empty(t, tokens) | |||
} |
@@ -7,7 +7,9 @@ package user | |||
import ( | |||
"errors" | |||
"fmt" | |||
"net/http" | |||
"strconv" | |||
"code.gitea.io/gitea/models" | |||
"code.gitea.io/gitea/modules/context" | |||
@@ -41,7 +43,7 @@ func ListAccessTokens(ctx *context.APIContext) { | |||
// "200": | |||
// "$ref": "#/responses/AccessTokenList" | |||
tokens, err := models.ListAccessTokens(ctx.User.ID, utils.GetListOptions(ctx)) | |||
tokens, err := models.ListAccessTokens(models.ListAccessTokensOptions{UserID: ctx.User.ID, ListOptions: utils.GetListOptions(ctx)}) | |||
if err != nil { | |||
ctx.Error(http.StatusInternalServerError, "ListAccessTokens", err) | |||
return | |||
@@ -128,15 +130,44 @@ func DeleteAccessToken(ctx *context.APIContext) { | |||
// required: true | |||
// - name: token | |||
// in: path | |||
// description: token to be deleted | |||
// type: integer | |||
// format: int64 | |||
// description: token to be deleted, identified by ID and if not available by name | |||
// type: string | |||
// required: true | |||
// responses: | |||
// "204": | |||
// "$ref": "#/responses/empty" | |||
// "422": | |||
// "$ref": "#/responses/error" | |||
token := ctx.Params(":id") | |||
tokenID, _ := strconv.ParseInt(token, 0, 64) | |||
if tokenID == 0 { | |||
tokens, err := models.ListAccessTokens(models.ListAccessTokensOptions{ | |||
Name: token, | |||
UserID: ctx.User.ID, | |||
}) | |||
if err != nil { | |||
ctx.Error(http.StatusInternalServerError, "ListAccessTokens", err) | |||
return | |||
} | |||
switch len(tokens) { | |||
case 0: | |||
ctx.NotFound() | |||
return | |||
case 1: | |||
tokenID = tokens[0].ID | |||
default: | |||
ctx.Error(http.StatusUnprocessableEntity, "DeleteAccessTokenByID", fmt.Errorf("multible matches for token name '%s'", token)) | |||
return | |||
} | |||
} | |||
if tokenID == 0 { | |||
ctx.Error(http.StatusInternalServerError, "Invalid TokenID", nil) | |||
return | |||
} | |||
tokenID := ctx.ParamsInt64(":id") | |||
if err := models.DeleteAccessTokenByID(tokenID, ctx.User.ID); err != nil { | |||
if models.IsErrAccessTokenNotExist(err) { | |||
ctx.NotFound() |
@@ -80,7 +80,7 @@ func DeleteApplication(ctx *context.Context) { | |||
} | |||
func loadApplicationsData(ctx *context.Context) { | |||
tokens, err := models.ListAccessTokens(ctx.User.ID, models.ListOptions{}) | |||
tokens, err := models.ListAccessTokens(models.ListAccessTokensOptions{UserID: ctx.User.ID}) | |||
if err != nil { | |||
ctx.ServerError("ListAccessTokens", err) | |||
return |
@@ -71,7 +71,7 @@ func loadSecurityData(ctx *context.Context) { | |||
ctx.Data["RequireU2F"] = true | |||
} | |||
tokens, err := models.ListAccessTokens(ctx.User.ID, models.ListOptions{}) | |||
tokens, err := models.ListAccessTokens(models.ListAccessTokensOptions{UserID: ctx.User.ID}) | |||
if err != nil { | |||
ctx.ServerError("ListAccessTokens", err) | |||
return |
@@ -10635,9 +10635,8 @@ | |||
"required": true | |||
}, | |||
{ | |||
"type": "integer", | |||
"format": "int64", | |||
"description": "token to be deleted", | |||
"type": "string", | |||
"description": "token to be deleted, identified by ID and if not available by name", | |||
"name": "token", | |||
"in": "path", | |||
"required": true | |||
@@ -10646,6 +10645,9 @@ | |||
"responses": { | |||
"204": { | |||
"$ref": "#/responses/empty" | |||
}, | |||
"422": { | |||
"$ref": "#/responses/error" | |||
} | |||
} | |||
} |