mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	[API] Delete Token accept names too (#12366)
* Delete Token accept names too * better description Co-authored-by: zeripath <art27@cantab.net> Co-authored-by: Lauris BH <lauris@nix.lv>
This commit is contained in:
		@@ -37,6 +37,19 @@ func TestAPICreateAndDeleteToken(t *testing.T) {
 | 
				
			|||||||
	MakeRequest(t, req, http.StatusNoContent)
 | 
						MakeRequest(t, req, http.StatusNoContent)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	models.AssertNotExistsBean(t, &models.AccessToken{ID: newAccessToken.ID})
 | 
						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
 | 
					// 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()
 | 
						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.
 | 
					// ListAccessTokens returns a list of access tokens belongs to given user.
 | 
				
			||||||
func ListAccessTokens(uid int64, listOptions ListOptions) ([]*AccessToken, error) {
 | 
					func ListAccessTokens(opts ListAccessTokensOptions) ([]*AccessToken, error) {
 | 
				
			||||||
	sess := x.
 | 
						sess := x.Where("uid=?", opts.UserID)
 | 
				
			||||||
		Where("uid=?", uid).
 | 
					 | 
				
			||||||
		Desc("id")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if listOptions.Page != 0 {
 | 
						if len(opts.Name) != 0 {
 | 
				
			||||||
		sess = listOptions.setSessionPagination(sess)
 | 
							sess = sess.Where("name=?", opts.Name)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		tokens := make([]*AccessToken, 0, listOptions.PageSize)
 | 
						sess = sess.Desc("id")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if opts.Page != 0 {
 | 
				
			||||||
 | 
							sess = opts.setSessionPagination(sess)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							tokens := make([]*AccessToken, 0, opts.PageSize)
 | 
				
			||||||
		return tokens, sess.Find(&tokens)
 | 
							return tokens, sess.Find(&tokens)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -83,7 +83,7 @@ func TestGetAccessTokenBySHA(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func TestListAccessTokens(t *testing.T) {
 | 
					func TestListAccessTokens(t *testing.T) {
 | 
				
			||||||
	assert.NoError(t, PrepareTestDatabase())
 | 
						assert.NoError(t, PrepareTestDatabase())
 | 
				
			||||||
	tokens, err := ListAccessTokens(1, ListOptions{})
 | 
						tokens, err := ListAccessTokens(ListAccessTokensOptions{UserID: 1})
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
	if assert.Len(t, tokens, 2) {
 | 
						if assert.Len(t, tokens, 2) {
 | 
				
			||||||
		assert.Equal(t, int64(1), tokens[0].UID)
 | 
							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")
 | 
							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)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
	if assert.Len(t, tokens, 1) {
 | 
						if assert.Len(t, tokens, 1) {
 | 
				
			||||||
		assert.Equal(t, int64(2), tokens[0].UID)
 | 
							assert.Equal(t, int64(2), tokens[0].UID)
 | 
				
			||||||
		assert.Equal(t, "Token A", tokens[0].Name)
 | 
							assert.Equal(t, "Token A", tokens[0].Name)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	tokens, err = ListAccessTokens(100, ListOptions{})
 | 
						tokens, err = ListAccessTokens(ListAccessTokensOptions{UserID: 100})
 | 
				
			||||||
	assert.NoError(t, err)
 | 
						assert.NoError(t, err)
 | 
				
			||||||
	assert.Empty(t, tokens)
 | 
						assert.Empty(t, tokens)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,9 @@ package user
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/context"
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
@@ -41,7 +43,7 @@ func ListAccessTokens(ctx *context.APIContext) {
 | 
				
			|||||||
	//   "200":
 | 
						//   "200":
 | 
				
			||||||
	//     "$ref": "#/responses/AccessTokenList"
 | 
						//     "$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 {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.Error(http.StatusInternalServerError, "ListAccessTokens", err)
 | 
							ctx.Error(http.StatusInternalServerError, "ListAccessTokens", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
@@ -128,15 +130,44 @@ func DeleteAccessToken(ctx *context.APIContext) {
 | 
				
			|||||||
	//   required: true
 | 
						//   required: true
 | 
				
			||||||
	// - name: token
 | 
						// - name: token
 | 
				
			||||||
	//   in: path
 | 
						//   in: path
 | 
				
			||||||
	//   description: token to be deleted
 | 
						//   description: token to be deleted, identified by ID and if not available by name
 | 
				
			||||||
	//   type: integer
 | 
						//   type: string
 | 
				
			||||||
	//   format: int64
 | 
					 | 
				
			||||||
	//   required: true
 | 
						//   required: true
 | 
				
			||||||
	// responses:
 | 
						// responses:
 | 
				
			||||||
	//   "204":
 | 
						//   "204":
 | 
				
			||||||
	//     "$ref": "#/responses/empty"
 | 
						//     "$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 err := models.DeleteAccessTokenByID(tokenID, ctx.User.ID); err != nil {
 | 
				
			||||||
		if models.IsErrAccessTokenNotExist(err) {
 | 
							if models.IsErrAccessTokenNotExist(err) {
 | 
				
			||||||
			ctx.NotFound()
 | 
								ctx.NotFound()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -80,7 +80,7 @@ func DeleteApplication(ctx *context.Context) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func loadApplicationsData(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 {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.ServerError("ListAccessTokens", err)
 | 
							ctx.ServerError("ListAccessTokens", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -71,7 +71,7 @@ func loadSecurityData(ctx *context.Context) {
 | 
				
			|||||||
		ctx.Data["RequireU2F"] = true
 | 
							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 {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.ServerError("ListAccessTokens", err)
 | 
							ctx.ServerError("ListAccessTokens", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10635,9 +10635,8 @@
 | 
				
			|||||||
            "required": true
 | 
					            "required": true
 | 
				
			||||||
          },
 | 
					          },
 | 
				
			||||||
          {
 | 
					          {
 | 
				
			||||||
            "type": "integer",
 | 
					            "type": "string",
 | 
				
			||||||
            "format": "int64",
 | 
					            "description": "token to be deleted, identified by ID and if not available by name",
 | 
				
			||||||
            "description": "token to be deleted",
 | 
					 | 
				
			||||||
            "name": "token",
 | 
					            "name": "token",
 | 
				
			||||||
            "in": "path",
 | 
					            "in": "path",
 | 
				
			||||||
            "required": true
 | 
					            "required": true
 | 
				
			||||||
@@ -10646,6 +10645,9 @@
 | 
				
			|||||||
        "responses": {
 | 
					        "responses": {
 | 
				
			||||||
          "204": {
 | 
					          "204": {
 | 
				
			||||||
            "$ref": "#/responses/empty"
 | 
					            "$ref": "#/responses/empty"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "422": {
 | 
				
			||||||
 | 
					            "$ref": "#/responses/error"
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user