code.gitea.io/gitea@v1.22.3/routers/api/v1/repo/key.go (about)

     1  // Copyright 2015 The Gogs Authors. All rights reserved.
     2  // Copyright 2020 The Gitea Authors.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package repo
     6  
     7  import (
     8  	stdCtx "context"
     9  	"fmt"
    10  	"net/http"
    11  	"net/url"
    12  
    13  	asymkey_model "code.gitea.io/gitea/models/asymkey"
    14  	"code.gitea.io/gitea/models/db"
    15  	"code.gitea.io/gitea/models/perm"
    16  	access_model "code.gitea.io/gitea/models/perm/access"
    17  	repo_model "code.gitea.io/gitea/models/repo"
    18  	"code.gitea.io/gitea/modules/setting"
    19  	api "code.gitea.io/gitea/modules/structs"
    20  	"code.gitea.io/gitea/modules/web"
    21  	"code.gitea.io/gitea/routers/api/v1/utils"
    22  	asymkey_service "code.gitea.io/gitea/services/asymkey"
    23  	"code.gitea.io/gitea/services/context"
    24  	"code.gitea.io/gitea/services/convert"
    25  )
    26  
    27  // appendPrivateInformation appends the owner and key type information to api.PublicKey
    28  func appendPrivateInformation(ctx stdCtx.Context, apiKey *api.DeployKey, key *asymkey_model.DeployKey, repository *repo_model.Repository) (*api.DeployKey, error) {
    29  	apiKey.ReadOnly = key.Mode == perm.AccessModeRead
    30  	if repository.ID == key.RepoID {
    31  		apiKey.Repository = convert.ToRepo(ctx, repository, access_model.Permission{AccessMode: key.Mode})
    32  	} else {
    33  		repo, err := repo_model.GetRepositoryByID(ctx, key.RepoID)
    34  		if err != nil {
    35  			return apiKey, err
    36  		}
    37  		apiKey.Repository = convert.ToRepo(ctx, repo, access_model.Permission{AccessMode: key.Mode})
    38  	}
    39  	return apiKey, nil
    40  }
    41  
    42  func composeDeployKeysAPILink(owner, name string) string {
    43  	return setting.AppURL + "api/v1/repos/" + url.PathEscape(owner) + "/" + url.PathEscape(name) + "/keys/"
    44  }
    45  
    46  // ListDeployKeys list all the deploy keys of a repository
    47  func ListDeployKeys(ctx *context.APIContext) {
    48  	// swagger:operation GET /repos/{owner}/{repo}/keys repository repoListKeys
    49  	// ---
    50  	// summary: List a repository's keys
    51  	// produces:
    52  	// - application/json
    53  	// parameters:
    54  	// - name: owner
    55  	//   in: path
    56  	//   description: owner of the repo
    57  	//   type: string
    58  	//   required: true
    59  	// - name: repo
    60  	//   in: path
    61  	//   description: name of the repo
    62  	//   type: string
    63  	//   required: true
    64  	// - name: key_id
    65  	//   in: query
    66  	//   description: the key_id to search for
    67  	//   type: integer
    68  	// - name: fingerprint
    69  	//   in: query
    70  	//   description: fingerprint of the key
    71  	//   type: string
    72  	// - name: page
    73  	//   in: query
    74  	//   description: page number of results to return (1-based)
    75  	//   type: integer
    76  	// - name: limit
    77  	//   in: query
    78  	//   description: page size of results
    79  	//   type: integer
    80  	// responses:
    81  	//   "200":
    82  	//     "$ref": "#/responses/DeployKeyList"
    83  	//   "404":
    84  	//     "$ref": "#/responses/notFound"
    85  
    86  	opts := asymkey_model.ListDeployKeysOptions{
    87  		ListOptions: utils.GetListOptions(ctx),
    88  		RepoID:      ctx.Repo.Repository.ID,
    89  		KeyID:       ctx.FormInt64("key_id"),
    90  		Fingerprint: ctx.FormString("fingerprint"),
    91  	}
    92  
    93  	keys, count, err := db.FindAndCount[asymkey_model.DeployKey](ctx, opts)
    94  	if err != nil {
    95  		ctx.InternalServerError(err)
    96  		return
    97  	}
    98  
    99  	apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
   100  	apiKeys := make([]*api.DeployKey, len(keys))
   101  	for i := range keys {
   102  		if err := keys[i].GetContent(ctx); err != nil {
   103  			ctx.Error(http.StatusInternalServerError, "GetContent", err)
   104  			return
   105  		}
   106  		apiKeys[i] = convert.ToDeployKey(apiLink, keys[i])
   107  		if ctx.Doer.IsAdmin || ((ctx.Repo.Repository.ID == keys[i].RepoID) && (ctx.Doer.ID == ctx.Repo.Owner.ID)) {
   108  			apiKeys[i], _ = appendPrivateInformation(ctx, apiKeys[i], keys[i], ctx.Repo.Repository)
   109  		}
   110  	}
   111  
   112  	ctx.SetTotalCountHeader(count)
   113  	ctx.JSON(http.StatusOK, &apiKeys)
   114  }
   115  
   116  // GetDeployKey get a deploy key by id
   117  func GetDeployKey(ctx *context.APIContext) {
   118  	// swagger:operation GET /repos/{owner}/{repo}/keys/{id} repository repoGetKey
   119  	// ---
   120  	// summary: Get a repository's key by id
   121  	// produces:
   122  	// - application/json
   123  	// parameters:
   124  	// - name: owner
   125  	//   in: path
   126  	//   description: owner of the repo
   127  	//   type: string
   128  	//   required: true
   129  	// - name: repo
   130  	//   in: path
   131  	//   description: name of the repo
   132  	//   type: string
   133  	//   required: true
   134  	// - name: id
   135  	//   in: path
   136  	//   description: id of the key to get
   137  	//   type: integer
   138  	//   format: int64
   139  	//   required: true
   140  	// responses:
   141  	//   "200":
   142  	//     "$ref": "#/responses/DeployKey"
   143  	//   "404":
   144  	//     "$ref": "#/responses/notFound"
   145  
   146  	key, err := asymkey_model.GetDeployKeyByID(ctx, ctx.ParamsInt64(":id"))
   147  	if err != nil {
   148  		if asymkey_model.IsErrDeployKeyNotExist(err) {
   149  			ctx.NotFound()
   150  		} else {
   151  			ctx.Error(http.StatusInternalServerError, "GetDeployKeyByID", err)
   152  		}
   153  		return
   154  	}
   155  
   156  	// this check make it more consistent
   157  	if key.RepoID != ctx.Repo.Repository.ID {
   158  		ctx.NotFound()
   159  		return
   160  	}
   161  
   162  	if err = key.GetContent(ctx); err != nil {
   163  		ctx.Error(http.StatusInternalServerError, "GetContent", err)
   164  		return
   165  	}
   166  
   167  	apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
   168  	apiKey := convert.ToDeployKey(apiLink, key)
   169  	if ctx.Doer.IsAdmin || ((ctx.Repo.Repository.ID == key.RepoID) && (ctx.Doer.ID == ctx.Repo.Owner.ID)) {
   170  		apiKey, _ = appendPrivateInformation(ctx, apiKey, key, ctx.Repo.Repository)
   171  	}
   172  	ctx.JSON(http.StatusOK, apiKey)
   173  }
   174  
   175  // HandleCheckKeyStringError handle check key error
   176  func HandleCheckKeyStringError(ctx *context.APIContext, err error) {
   177  	if db.IsErrSSHDisabled(err) {
   178  		ctx.Error(http.StatusUnprocessableEntity, "", "SSH is disabled")
   179  	} else if asymkey_model.IsErrKeyUnableVerify(err) {
   180  		ctx.Error(http.StatusUnprocessableEntity, "", "Unable to verify key content")
   181  	} else {
   182  		ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid key content: %w", err))
   183  	}
   184  }
   185  
   186  // HandleAddKeyError handle add key error
   187  func HandleAddKeyError(ctx *context.APIContext, err error) {
   188  	switch {
   189  	case asymkey_model.IsErrDeployKeyAlreadyExist(err):
   190  		ctx.Error(http.StatusUnprocessableEntity, "", "This key has already been added to this repository")
   191  	case asymkey_model.IsErrKeyAlreadyExist(err):
   192  		ctx.Error(http.StatusUnprocessableEntity, "", "Key content has been used as non-deploy key")
   193  	case asymkey_model.IsErrKeyNameAlreadyUsed(err):
   194  		ctx.Error(http.StatusUnprocessableEntity, "", "Key title has been used")
   195  	case asymkey_model.IsErrDeployKeyNameAlreadyUsed(err):
   196  		ctx.Error(http.StatusUnprocessableEntity, "", "A key with the same name already exists")
   197  	default:
   198  		ctx.Error(http.StatusInternalServerError, "AddKey", err)
   199  	}
   200  }
   201  
   202  // CreateDeployKey create deploy key for a repository
   203  func CreateDeployKey(ctx *context.APIContext) {
   204  	// swagger:operation POST /repos/{owner}/{repo}/keys repository repoCreateKey
   205  	// ---
   206  	// summary: Add a key to a repository
   207  	// consumes:
   208  	// - application/json
   209  	// produces:
   210  	// - application/json
   211  	// parameters:
   212  	// - name: owner
   213  	//   in: path
   214  	//   description: owner of the repo
   215  	//   type: string
   216  	//   required: true
   217  	// - name: repo
   218  	//   in: path
   219  	//   description: name of the repo
   220  	//   type: string
   221  	//   required: true
   222  	// - name: body
   223  	//   in: body
   224  	//   schema:
   225  	//     "$ref": "#/definitions/CreateKeyOption"
   226  	// responses:
   227  	//   "201":
   228  	//     "$ref": "#/responses/DeployKey"
   229  	//   "404":
   230  	//     "$ref": "#/responses/notFound"
   231  	//   "422":
   232  	//     "$ref": "#/responses/validationError"
   233  
   234  	form := web.GetForm(ctx).(*api.CreateKeyOption)
   235  	content, err := asymkey_model.CheckPublicKeyString(form.Key)
   236  	if err != nil {
   237  		HandleCheckKeyStringError(ctx, err)
   238  		return
   239  	}
   240  
   241  	key, err := asymkey_model.AddDeployKey(ctx, ctx.Repo.Repository.ID, form.Title, content, form.ReadOnly)
   242  	if err != nil {
   243  		HandleAddKeyError(ctx, err)
   244  		return
   245  	}
   246  
   247  	key.Content = content
   248  	apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
   249  	ctx.JSON(http.StatusCreated, convert.ToDeployKey(apiLink, key))
   250  }
   251  
   252  // DeleteDeploykey delete deploy key for a repository
   253  func DeleteDeploykey(ctx *context.APIContext) {
   254  	// swagger:operation DELETE /repos/{owner}/{repo}/keys/{id} repository repoDeleteKey
   255  	// ---
   256  	// summary: Delete a key from a repository
   257  	// parameters:
   258  	// - name: owner
   259  	//   in: path
   260  	//   description: owner of the repo
   261  	//   type: string
   262  	//   required: true
   263  	// - name: repo
   264  	//   in: path
   265  	//   description: name of the repo
   266  	//   type: string
   267  	//   required: true
   268  	// - name: id
   269  	//   in: path
   270  	//   description: id of the key to delete
   271  	//   type: integer
   272  	//   format: int64
   273  	//   required: true
   274  	// responses:
   275  	//   "204":
   276  	//     "$ref": "#/responses/empty"
   277  	//   "403":
   278  	//     "$ref": "#/responses/forbidden"
   279  	//   "404":
   280  	//     "$ref": "#/responses/notFound"
   281  
   282  	if err := asymkey_service.DeleteDeployKey(ctx, ctx.Doer, ctx.ParamsInt64(":id")); err != nil {
   283  		if asymkey_model.IsErrKeyAccessDenied(err) {
   284  			ctx.Error(http.StatusForbidden, "", "You do not have access to this key")
   285  		} else {
   286  			ctx.Error(http.StatusInternalServerError, "DeleteDeployKey", err)
   287  		}
   288  		return
   289  	}
   290  
   291  	ctx.Status(http.StatusNoContent)
   292  }