code.gitea.io/gitea@v1.22.3/routers/api/v1/user/gpg_key.go (about)

     1  // Copyright 2017 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package user
     5  
     6  import (
     7  	"fmt"
     8  	"net/http"
     9  	"strings"
    10  
    11  	asymkey_model "code.gitea.io/gitea/models/asymkey"
    12  	"code.gitea.io/gitea/models/db"
    13  	user_model "code.gitea.io/gitea/models/user"
    14  	"code.gitea.io/gitea/modules/setting"
    15  	api "code.gitea.io/gitea/modules/structs"
    16  	"code.gitea.io/gitea/modules/web"
    17  	"code.gitea.io/gitea/routers/api/v1/utils"
    18  	"code.gitea.io/gitea/services/context"
    19  	"code.gitea.io/gitea/services/convert"
    20  )
    21  
    22  func listGPGKeys(ctx *context.APIContext, uid int64, listOptions db.ListOptions) {
    23  	keys, total, err := db.FindAndCount[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
    24  		ListOptions: listOptions,
    25  		OwnerID:     uid,
    26  	})
    27  	if err != nil {
    28  		ctx.Error(http.StatusInternalServerError, "ListGPGKeys", err)
    29  		return
    30  	}
    31  
    32  	if err := asymkey_model.GPGKeyList(keys).LoadSubKeys(ctx); err != nil {
    33  		ctx.Error(http.StatusInternalServerError, "ListGPGKeys", err)
    34  		return
    35  	}
    36  
    37  	apiKeys := make([]*api.GPGKey, len(keys))
    38  	for i := range keys {
    39  		apiKeys[i] = convert.ToGPGKey(keys[i])
    40  	}
    41  
    42  	ctx.SetTotalCountHeader(total)
    43  	ctx.JSON(http.StatusOK, &apiKeys)
    44  }
    45  
    46  // ListGPGKeys get the GPG key list of a user
    47  func ListGPGKeys(ctx *context.APIContext) {
    48  	// swagger:operation GET /users/{username}/gpg_keys user userListGPGKeys
    49  	// ---
    50  	// summary: List the given user's GPG keys
    51  	// produces:
    52  	// - application/json
    53  	// parameters:
    54  	// - name: username
    55  	//   in: path
    56  	//   description: username of user
    57  	//   type: string
    58  	//   required: true
    59  	// - name: page
    60  	//   in: query
    61  	//   description: page number of results to return (1-based)
    62  	//   type: integer
    63  	// - name: limit
    64  	//   in: query
    65  	//   description: page size of results
    66  	//   type: integer
    67  	// responses:
    68  	//   "200":
    69  	//     "$ref": "#/responses/GPGKeyList"
    70  	//   "404":
    71  	//     "$ref": "#/responses/notFound"
    72  
    73  	listGPGKeys(ctx, ctx.ContextUser.ID, utils.GetListOptions(ctx))
    74  }
    75  
    76  // ListMyGPGKeys get the GPG key list of the authenticated user
    77  func ListMyGPGKeys(ctx *context.APIContext) {
    78  	// swagger:operation GET /user/gpg_keys user userCurrentListGPGKeys
    79  	// ---
    80  	// summary: List the authenticated user's GPG keys
    81  	// parameters:
    82  	// - name: page
    83  	//   in: query
    84  	//   description: page number of results to return (1-based)
    85  	//   type: integer
    86  	// - name: limit
    87  	//   in: query
    88  	//   description: page size of results
    89  	//   type: integer
    90  	// produces:
    91  	// - application/json
    92  	// responses:
    93  	//   "200":
    94  	//     "$ref": "#/responses/GPGKeyList"
    95  
    96  	listGPGKeys(ctx, ctx.Doer.ID, utils.GetListOptions(ctx))
    97  }
    98  
    99  // GetGPGKey get the GPG key based on a id
   100  func GetGPGKey(ctx *context.APIContext) {
   101  	// swagger:operation GET /user/gpg_keys/{id} user userCurrentGetGPGKey
   102  	// ---
   103  	// summary: Get a GPG key
   104  	// produces:
   105  	// - application/json
   106  	// parameters:
   107  	// - name: id
   108  	//   in: path
   109  	//   description: id of key to get
   110  	//   type: integer
   111  	//   format: int64
   112  	//   required: true
   113  	// responses:
   114  	//   "200":
   115  	//     "$ref": "#/responses/GPGKey"
   116  	//   "404":
   117  	//     "$ref": "#/responses/notFound"
   118  
   119  	key, err := asymkey_model.GetGPGKeyForUserByID(ctx, ctx.Doer.ID, ctx.ParamsInt64(":id"))
   120  	if err != nil {
   121  		if asymkey_model.IsErrGPGKeyNotExist(err) {
   122  			ctx.NotFound()
   123  		} else {
   124  			ctx.Error(http.StatusInternalServerError, "GetGPGKeyByID", err)
   125  		}
   126  		return
   127  	}
   128  	if err := key.LoadSubKeys(ctx); err != nil {
   129  		ctx.Error(http.StatusInternalServerError, "LoadSubKeys", err)
   130  		return
   131  	}
   132  	ctx.JSON(http.StatusOK, convert.ToGPGKey(key))
   133  }
   134  
   135  // CreateUserGPGKey creates new GPG key to given user by ID.
   136  func CreateUserGPGKey(ctx *context.APIContext, form api.CreateGPGKeyOption, uid int64) {
   137  	if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
   138  		ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
   139  		return
   140  	}
   141  
   142  	token := asymkey_model.VerificationToken(ctx.Doer, 1)
   143  	lastToken := asymkey_model.VerificationToken(ctx.Doer, 0)
   144  
   145  	keys, err := asymkey_model.AddGPGKey(ctx, uid, form.ArmoredKey, token, form.Signature)
   146  	if err != nil && asymkey_model.IsErrGPGInvalidTokenSignature(err) {
   147  		keys, err = asymkey_model.AddGPGKey(ctx, uid, form.ArmoredKey, lastToken, form.Signature)
   148  	}
   149  	if err != nil {
   150  		HandleAddGPGKeyError(ctx, err, token)
   151  		return
   152  	}
   153  	ctx.JSON(http.StatusCreated, convert.ToGPGKey(keys[0]))
   154  }
   155  
   156  // GetVerificationToken returns the current token to be signed for this user
   157  func GetVerificationToken(ctx *context.APIContext) {
   158  	// swagger:operation GET /user/gpg_key_token user getVerificationToken
   159  	// ---
   160  	// summary: Get a Token to verify
   161  	// produces:
   162  	// - text/plain
   163  	// parameters:
   164  	// responses:
   165  	//   "200":
   166  	//     "$ref": "#/responses/string"
   167  	//   "404":
   168  	//     "$ref": "#/responses/notFound"
   169  
   170  	token := asymkey_model.VerificationToken(ctx.Doer, 1)
   171  	ctx.PlainText(http.StatusOK, token)
   172  }
   173  
   174  // VerifyUserGPGKey creates new GPG key to given user by ID.
   175  func VerifyUserGPGKey(ctx *context.APIContext) {
   176  	// swagger:operation POST /user/gpg_key_verify user userVerifyGPGKey
   177  	// ---
   178  	// summary: Verify a GPG key
   179  	// consumes:
   180  	// - application/json
   181  	// produces:
   182  	// - application/json
   183  	// responses:
   184  	//   "201":
   185  	//     "$ref": "#/responses/GPGKey"
   186  	//   "404":
   187  	//     "$ref": "#/responses/notFound"
   188  	//   "422":
   189  	//     "$ref": "#/responses/validationError"
   190  
   191  	form := web.GetForm(ctx).(*api.VerifyGPGKeyOption)
   192  	token := asymkey_model.VerificationToken(ctx.Doer, 1)
   193  	lastToken := asymkey_model.VerificationToken(ctx.Doer, 0)
   194  
   195  	form.KeyID = strings.TrimLeft(form.KeyID, "0")
   196  	if form.KeyID == "" {
   197  		ctx.NotFound()
   198  		return
   199  	}
   200  
   201  	_, err := asymkey_model.VerifyGPGKey(ctx, ctx.Doer.ID, form.KeyID, token, form.Signature)
   202  	if err != nil && asymkey_model.IsErrGPGInvalidTokenSignature(err) {
   203  		_, err = asymkey_model.VerifyGPGKey(ctx, ctx.Doer.ID, form.KeyID, lastToken, form.Signature)
   204  	}
   205  
   206  	if err != nil {
   207  		if asymkey_model.IsErrGPGInvalidTokenSignature(err) {
   208  			ctx.Error(http.StatusUnprocessableEntity, "GPGInvalidSignature", fmt.Sprintf("The provided GPG key, signature and token do not match or token is out of date. Provide a valid signature for the token: %s", token))
   209  			return
   210  		}
   211  		ctx.Error(http.StatusInternalServerError, "VerifyUserGPGKey", err)
   212  	}
   213  
   214  	keys, err := db.Find[asymkey_model.GPGKey](ctx, asymkey_model.FindGPGKeyOptions{
   215  		KeyID:          form.KeyID,
   216  		IncludeSubKeys: true,
   217  	})
   218  	if err != nil {
   219  		if asymkey_model.IsErrGPGKeyNotExist(err) {
   220  			ctx.NotFound()
   221  		} else {
   222  			ctx.Error(http.StatusInternalServerError, "GetGPGKeysByKeyID", err)
   223  		}
   224  		return
   225  	}
   226  	ctx.JSON(http.StatusOK, convert.ToGPGKey(keys[0]))
   227  }
   228  
   229  // swagger:parameters userCurrentPostGPGKey
   230  type swaggerUserCurrentPostGPGKey struct {
   231  	// in:body
   232  	Form api.CreateGPGKeyOption
   233  }
   234  
   235  // CreateGPGKey create a GPG key belonging to the authenticated user
   236  func CreateGPGKey(ctx *context.APIContext) {
   237  	// swagger:operation POST /user/gpg_keys user userCurrentPostGPGKey
   238  	// ---
   239  	// summary: Create a GPG key
   240  	// consumes:
   241  	// - application/json
   242  	// produces:
   243  	// - application/json
   244  	// responses:
   245  	//   "201":
   246  	//     "$ref": "#/responses/GPGKey"
   247  	//   "404":
   248  	//     "$ref": "#/responses/notFound"
   249  	//   "422":
   250  	//     "$ref": "#/responses/validationError"
   251  
   252  	form := web.GetForm(ctx).(*api.CreateGPGKeyOption)
   253  	CreateUserGPGKey(ctx, *form, ctx.Doer.ID)
   254  }
   255  
   256  // DeleteGPGKey remove a GPG key belonging to the authenticated user
   257  func DeleteGPGKey(ctx *context.APIContext) {
   258  	// swagger:operation DELETE /user/gpg_keys/{id} user userCurrentDeleteGPGKey
   259  	// ---
   260  	// summary: Remove a GPG key
   261  	// produces:
   262  	// - application/json
   263  	// parameters:
   264  	// - name: id
   265  	//   in: path
   266  	//   description: id of key to delete
   267  	//   type: integer
   268  	//   format: int64
   269  	//   required: true
   270  	// responses:
   271  	//   "204":
   272  	//     "$ref": "#/responses/empty"
   273  	//   "403":
   274  	//     "$ref": "#/responses/forbidden"
   275  	//   "404":
   276  	//     "$ref": "#/responses/notFound"
   277  
   278  	if user_model.IsFeatureDisabledWithLoginType(ctx.Doer, setting.UserFeatureManageGPGKeys) {
   279  		ctx.NotFound("Not Found", fmt.Errorf("gpg keys setting is not allowed to be visited"))
   280  		return
   281  	}
   282  
   283  	if err := asymkey_model.DeleteGPGKey(ctx, ctx.Doer, ctx.ParamsInt64(":id")); err != nil {
   284  		if asymkey_model.IsErrGPGKeyAccessDenied(err) {
   285  			ctx.Error(http.StatusForbidden, "", "You do not have access to this key")
   286  		} else {
   287  			ctx.Error(http.StatusInternalServerError, "DeleteGPGKey", err)
   288  		}
   289  		return
   290  	}
   291  
   292  	ctx.Status(http.StatusNoContent)
   293  }
   294  
   295  // HandleAddGPGKeyError handle add GPGKey error
   296  func HandleAddGPGKeyError(ctx *context.APIContext, err error, token string) {
   297  	switch {
   298  	case asymkey_model.IsErrGPGKeyAccessDenied(err):
   299  		ctx.Error(http.StatusUnprocessableEntity, "GPGKeyAccessDenied", "You do not have access to this GPG key")
   300  	case asymkey_model.IsErrGPGKeyIDAlreadyUsed(err):
   301  		ctx.Error(http.StatusUnprocessableEntity, "GPGKeyIDAlreadyUsed", "A key with the same id already exists")
   302  	case asymkey_model.IsErrGPGKeyParsing(err):
   303  		ctx.Error(http.StatusUnprocessableEntity, "GPGKeyParsing", err)
   304  	case asymkey_model.IsErrGPGNoEmailFound(err):
   305  		ctx.Error(http.StatusNotFound, "GPGNoEmailFound", fmt.Sprintf("None of the emails attached to the GPG key could be found. It may still be added if you provide a valid signature for the token: %s", token))
   306  	case asymkey_model.IsErrGPGInvalidTokenSignature(err):
   307  		ctx.Error(http.StatusUnprocessableEntity, "GPGInvalidSignature", fmt.Sprintf("The provided GPG key, signature and token do not match or token is out of date. Provide a valid signature for the token: %s", token))
   308  	default:
   309  		ctx.Error(http.StatusInternalServerError, "AddGPGKey", err)
   310  	}
   311  }