forked from NYANDEV/forgejo
bf41958c16
Backport #20878 The go crypto library does not pad keyIDs to 16 characters with preceding zeroes. This is a somewhat confusing thing for most users who expect these to have preceding zeroes. This PR prefixes any sub 16 length KeyID with preceding zeroes and removes preceding zeroes from KeyIDs inputted on the API. Fix #20876 Signed-off-by: Andrew Thornton <art27@cantab.net>
289 lines
8.6 KiB
Go
289 lines
8.6 KiB
Go
// Copyright 2017 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 user
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
asymkey_model "code.gitea.io/gitea/models/asymkey"
|
|
"code.gitea.io/gitea/models/db"
|
|
"code.gitea.io/gitea/modules/context"
|
|
"code.gitea.io/gitea/modules/convert"
|
|
api "code.gitea.io/gitea/modules/structs"
|
|
"code.gitea.io/gitea/modules/web"
|
|
"code.gitea.io/gitea/routers/api/v1/utils"
|
|
)
|
|
|
|
func listGPGKeys(ctx *context.APIContext, uid int64, listOptions db.ListOptions) {
|
|
keys, err := asymkey_model.ListGPGKeys(ctx, uid, listOptions)
|
|
if err != nil {
|
|
ctx.Error(http.StatusInternalServerError, "ListGPGKeys", err)
|
|
return
|
|
}
|
|
|
|
apiKeys := make([]*api.GPGKey, len(keys))
|
|
for i := range keys {
|
|
apiKeys[i] = convert.ToGPGKey(keys[i])
|
|
}
|
|
|
|
total, err := asymkey_model.CountUserGPGKeys(uid)
|
|
if err != nil {
|
|
ctx.InternalServerError(err)
|
|
return
|
|
}
|
|
|
|
ctx.SetTotalCountHeader(total)
|
|
ctx.JSON(http.StatusOK, &apiKeys)
|
|
}
|
|
|
|
// ListGPGKeys get the GPG key list of a user
|
|
func ListGPGKeys(ctx *context.APIContext) {
|
|
// swagger:operation GET /users/{username}/gpg_keys user userListGPGKeys
|
|
// ---
|
|
// summary: List the given user's GPG keys
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: username
|
|
// in: path
|
|
// description: username of user
|
|
// type: string
|
|
// required: true
|
|
// - name: page
|
|
// in: query
|
|
// description: page number of results to return (1-based)
|
|
// type: integer
|
|
// - name: limit
|
|
// in: query
|
|
// description: page size of results
|
|
// type: integer
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/GPGKeyList"
|
|
|
|
listGPGKeys(ctx, ctx.ContextUser.ID, utils.GetListOptions(ctx))
|
|
}
|
|
|
|
// ListMyGPGKeys get the GPG key list of the authenticated user
|
|
func ListMyGPGKeys(ctx *context.APIContext) {
|
|
// swagger:operation GET /user/gpg_keys user userCurrentListGPGKeys
|
|
// ---
|
|
// summary: List the authenticated user's GPG keys
|
|
// parameters:
|
|
// - name: page
|
|
// in: query
|
|
// description: page number of results to return (1-based)
|
|
// type: integer
|
|
// - name: limit
|
|
// in: query
|
|
// description: page size of results
|
|
// type: integer
|
|
// produces:
|
|
// - application/json
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/GPGKeyList"
|
|
|
|
listGPGKeys(ctx, ctx.Doer.ID, utils.GetListOptions(ctx))
|
|
}
|
|
|
|
// GetGPGKey get the GPG key based on a id
|
|
func GetGPGKey(ctx *context.APIContext) {
|
|
// swagger:operation GET /user/gpg_keys/{id} user userCurrentGetGPGKey
|
|
// ---
|
|
// summary: Get a GPG key
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: id
|
|
// in: path
|
|
// description: id of key to get
|
|
// type: integer
|
|
// format: int64
|
|
// required: true
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/GPGKey"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
key, err := asymkey_model.GetGPGKeyByID(ctx.ParamsInt64(":id"))
|
|
if err != nil {
|
|
if asymkey_model.IsErrGPGKeyNotExist(err) {
|
|
ctx.NotFound()
|
|
} else {
|
|
ctx.Error(http.StatusInternalServerError, "GetGPGKeyByID", err)
|
|
}
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, convert.ToGPGKey(key))
|
|
}
|
|
|
|
// CreateUserGPGKey creates new GPG key to given user by ID.
|
|
func CreateUserGPGKey(ctx *context.APIContext, form api.CreateGPGKeyOption, uid int64) {
|
|
token := asymkey_model.VerificationToken(ctx.Doer, 1)
|
|
lastToken := asymkey_model.VerificationToken(ctx.Doer, 0)
|
|
|
|
keys, err := asymkey_model.AddGPGKey(uid, form.ArmoredKey, token, form.Signature)
|
|
if err != nil && asymkey_model.IsErrGPGInvalidTokenSignature(err) {
|
|
keys, err = asymkey_model.AddGPGKey(uid, form.ArmoredKey, lastToken, form.Signature)
|
|
}
|
|
if err != nil {
|
|
HandleAddGPGKeyError(ctx, err, token)
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusCreated, convert.ToGPGKey(keys[0]))
|
|
}
|
|
|
|
// GetVerificationToken returns the current token to be signed for this user
|
|
func GetVerificationToken(ctx *context.APIContext) {
|
|
// swagger:operation GET /user/gpg_key_token user getVerificationToken
|
|
// ---
|
|
// summary: Get a Token to verify
|
|
// produces:
|
|
// - text/plain
|
|
// parameters:
|
|
// responses:
|
|
// "200":
|
|
// "$ref": "#/responses/string"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
token := asymkey_model.VerificationToken(ctx.Doer, 1)
|
|
ctx.PlainText(http.StatusOK, token)
|
|
}
|
|
|
|
// VerifyUserGPGKey creates new GPG key to given user by ID.
|
|
func VerifyUserGPGKey(ctx *context.APIContext) {
|
|
// swagger:operation POST /user/gpg_key_verify user userVerifyGPGKey
|
|
// ---
|
|
// summary: Verify a GPG key
|
|
// consumes:
|
|
// - application/json
|
|
// produces:
|
|
// - application/json
|
|
// responses:
|
|
// "201":
|
|
// "$ref": "#/responses/GPGKey"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
// "422":
|
|
// "$ref": "#/responses/validationError"
|
|
|
|
form := web.GetForm(ctx).(*api.VerifyGPGKeyOption)
|
|
token := asymkey_model.VerificationToken(ctx.Doer, 1)
|
|
lastToken := asymkey_model.VerificationToken(ctx.Doer, 0)
|
|
|
|
form.KeyID = strings.TrimLeft(form.KeyID, "0")
|
|
if form.KeyID == "" {
|
|
ctx.NotFound()
|
|
return
|
|
}
|
|
|
|
_, err := asymkey_model.VerifyGPGKey(ctx.Doer.ID, form.KeyID, token, form.Signature)
|
|
if err != nil && asymkey_model.IsErrGPGInvalidTokenSignature(err) {
|
|
_, err = asymkey_model.VerifyGPGKey(ctx.Doer.ID, form.KeyID, lastToken, form.Signature)
|
|
}
|
|
|
|
if err != nil {
|
|
if asymkey_model.IsErrGPGInvalidTokenSignature(err) {
|
|
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))
|
|
return
|
|
}
|
|
ctx.Error(http.StatusInternalServerError, "VerifyUserGPGKey", err)
|
|
}
|
|
|
|
key, err := asymkey_model.GetGPGKeysByKeyID(form.KeyID)
|
|
if err != nil {
|
|
if asymkey_model.IsErrGPGKeyNotExist(err) {
|
|
ctx.NotFound()
|
|
} else {
|
|
ctx.Error(http.StatusInternalServerError, "GetGPGKeysByKeyID", err)
|
|
}
|
|
return
|
|
}
|
|
ctx.JSON(http.StatusOK, convert.ToGPGKey(key[0]))
|
|
}
|
|
|
|
// swagger:parameters userCurrentPostGPGKey
|
|
type swaggerUserCurrentPostGPGKey struct {
|
|
// in:body
|
|
Form api.CreateGPGKeyOption
|
|
}
|
|
|
|
// CreateGPGKey create a GPG key belonging to the authenticated user
|
|
func CreateGPGKey(ctx *context.APIContext) {
|
|
// swagger:operation POST /user/gpg_keys user userCurrentPostGPGKey
|
|
// ---
|
|
// summary: Create a GPG key
|
|
// consumes:
|
|
// - application/json
|
|
// produces:
|
|
// - application/json
|
|
// responses:
|
|
// "201":
|
|
// "$ref": "#/responses/GPGKey"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
// "422":
|
|
// "$ref": "#/responses/validationError"
|
|
|
|
form := web.GetForm(ctx).(*api.CreateGPGKeyOption)
|
|
CreateUserGPGKey(ctx, *form, ctx.Doer.ID)
|
|
}
|
|
|
|
// DeleteGPGKey remove a GPG key belonging to the authenticated user
|
|
func DeleteGPGKey(ctx *context.APIContext) {
|
|
// swagger:operation DELETE /user/gpg_keys/{id} user userCurrentDeleteGPGKey
|
|
// ---
|
|
// summary: Remove a GPG key
|
|
// produces:
|
|
// - application/json
|
|
// parameters:
|
|
// - name: id
|
|
// in: path
|
|
// description: id of key to delete
|
|
// type: integer
|
|
// format: int64
|
|
// required: true
|
|
// responses:
|
|
// "204":
|
|
// "$ref": "#/responses/empty"
|
|
// "403":
|
|
// "$ref": "#/responses/forbidden"
|
|
// "404":
|
|
// "$ref": "#/responses/notFound"
|
|
|
|
if err := asymkey_model.DeleteGPGKey(ctx.Doer, ctx.ParamsInt64(":id")); err != nil {
|
|
if asymkey_model.IsErrGPGKeyAccessDenied(err) {
|
|
ctx.Error(http.StatusForbidden, "", "You do not have access to this key")
|
|
} else {
|
|
ctx.Error(http.StatusInternalServerError, "DeleteGPGKey", err)
|
|
}
|
|
return
|
|
}
|
|
|
|
ctx.Status(http.StatusNoContent)
|
|
}
|
|
|
|
// HandleAddGPGKeyError handle add GPGKey error
|
|
func HandleAddGPGKeyError(ctx *context.APIContext, err error, token string) {
|
|
switch {
|
|
case asymkey_model.IsErrGPGKeyAccessDenied(err):
|
|
ctx.Error(http.StatusUnprocessableEntity, "GPGKeyAccessDenied", "You do not have access to this GPG key")
|
|
case asymkey_model.IsErrGPGKeyIDAlreadyUsed(err):
|
|
ctx.Error(http.StatusUnprocessableEntity, "GPGKeyIDAlreadyUsed", "A key with the same id already exists")
|
|
case asymkey_model.IsErrGPGKeyParsing(err):
|
|
ctx.Error(http.StatusUnprocessableEntity, "GPGKeyParsing", err)
|
|
case asymkey_model.IsErrGPGNoEmailFound(err):
|
|
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))
|
|
case asymkey_model.IsErrGPGInvalidTokenSignature(err):
|
|
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))
|
|
default:
|
|
ctx.Error(http.StatusInternalServerError, "AddGPGKey", err)
|
|
}
|
|
}
|