code.gitea.io/gitea@v1.21.7/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/context"
    19  	"code.gitea.io/gitea/modules/setting"
    20  	api "code.gitea.io/gitea/modules/structs"
    21  	"code.gitea.io/gitea/modules/web"
    22  	"code.gitea.io/gitea/routers/api/v1/utils"
    23  	asymkey_service "code.gitea.io/gitea/services/asymkey"
    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, err := asymkey_model.ListDeployKeys(ctx, opts)
    94  	if err != nil {
    95  		ctx.InternalServerError(err)
    96  		return
    97  	}
    98  
    99  	count, err := asymkey_model.CountDeployKeys(opts)
   100  	if err != nil {
   101  		ctx.InternalServerError(err)
   102  		return
   103  	}
   104  
   105  	apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
   106  	apiKeys := make([]*api.DeployKey, len(keys))
   107  	for i := range keys {
   108  		if err := keys[i].GetContent(); err != nil {
   109  			ctx.Error(http.StatusInternalServerError, "GetContent", err)
   110  			return
   111  		}
   112  		apiKeys[i] = convert.ToDeployKey(apiLink, keys[i])
   113  		if ctx.Doer.IsAdmin || ((ctx.Repo.Repository.ID == keys[i].RepoID) && (ctx.Doer.ID == ctx.Repo.Owner.ID)) {
   114  			apiKeys[i], _ = appendPrivateInformation(ctx, apiKeys[i], keys[i], ctx.Repo.Repository)
   115  		}
   116  	}
   117  
   118  	ctx.SetTotalCountHeader(count)
   119  	ctx.JSON(http.StatusOK, &apiKeys)
   120  }
   121  
   122  // GetDeployKey get a deploy key by id
   123  func GetDeployKey(ctx *context.APIContext) {
   124  	// swagger:operation GET /repos/{owner}/{repo}/keys/{id} repository repoGetKey
   125  	// ---
   126  	// summary: Get a repository's key by id
   127  	// produces:
   128  	// - application/json
   129  	// parameters:
   130  	// - name: owner
   131  	//   in: path
   132  	//   description: owner of the repo
   133  	//   type: string
   134  	//   required: true
   135  	// - name: repo
   136  	//   in: path
   137  	//   description: name of the repo
   138  	//   type: string
   139  	//   required: true
   140  	// - name: id
   141  	//   in: path
   142  	//   description: id of the key to get
   143  	//   type: integer
   144  	//   format: int64
   145  	//   required: true
   146  	// responses:
   147  	//   "200":
   148  	//     "$ref": "#/responses/DeployKey"
   149  	//   "404":
   150  	//     "$ref": "#/responses/notFound"
   151  
   152  	key, err := asymkey_model.GetDeployKeyByID(ctx, ctx.ParamsInt64(":id"))
   153  	if err != nil {
   154  		if asymkey_model.IsErrDeployKeyNotExist(err) {
   155  			ctx.NotFound()
   156  		} else {
   157  			ctx.Error(http.StatusInternalServerError, "GetDeployKeyByID", err)
   158  		}
   159  		return
   160  	}
   161  
   162  	// this check make it more consistent
   163  	if key.RepoID != ctx.Repo.Repository.ID {
   164  		ctx.NotFound()
   165  		return
   166  	}
   167  
   168  	if err = key.GetContent(); err != nil {
   169  		ctx.Error(http.StatusInternalServerError, "GetContent", err)
   170  		return
   171  	}
   172  
   173  	apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
   174  	apiKey := convert.ToDeployKey(apiLink, key)
   175  	if ctx.Doer.IsAdmin || ((ctx.Repo.Repository.ID == key.RepoID) && (ctx.Doer.ID == ctx.Repo.Owner.ID)) {
   176  		apiKey, _ = appendPrivateInformation(ctx, apiKey, key, ctx.Repo.Repository)
   177  	}
   178  	ctx.JSON(http.StatusOK, apiKey)
   179  }
   180  
   181  // HandleCheckKeyStringError handle check key error
   182  func HandleCheckKeyStringError(ctx *context.APIContext, err error) {
   183  	if db.IsErrSSHDisabled(err) {
   184  		ctx.Error(http.StatusUnprocessableEntity, "", "SSH is disabled")
   185  	} else if asymkey_model.IsErrKeyUnableVerify(err) {
   186  		ctx.Error(http.StatusUnprocessableEntity, "", "Unable to verify key content")
   187  	} else {
   188  		ctx.Error(http.StatusUnprocessableEntity, "", fmt.Errorf("Invalid key content: %w", err))
   189  	}
   190  }
   191  
   192  // HandleAddKeyError handle add key error
   193  func HandleAddKeyError(ctx *context.APIContext, err error) {
   194  	switch {
   195  	case asymkey_model.IsErrDeployKeyAlreadyExist(err):
   196  		ctx.Error(http.StatusUnprocessableEntity, "", "This key has already been added to this repository")
   197  	case asymkey_model.IsErrKeyAlreadyExist(err):
   198  		ctx.Error(http.StatusUnprocessableEntity, "", "Key content has been used as non-deploy key")
   199  	case asymkey_model.IsErrKeyNameAlreadyUsed(err):
   200  		ctx.Error(http.StatusUnprocessableEntity, "", "Key title has been used")
   201  	case asymkey_model.IsErrDeployKeyNameAlreadyUsed(err):
   202  		ctx.Error(http.StatusUnprocessableEntity, "", "A key with the same name already exists")
   203  	default:
   204  		ctx.Error(http.StatusInternalServerError, "AddKey", err)
   205  	}
   206  }
   207  
   208  // CreateDeployKey create deploy key for a repository
   209  func CreateDeployKey(ctx *context.APIContext) {
   210  	// swagger:operation POST /repos/{owner}/{repo}/keys repository repoCreateKey
   211  	// ---
   212  	// summary: Add a key to a repository
   213  	// consumes:
   214  	// - application/json
   215  	// produces:
   216  	// - application/json
   217  	// parameters:
   218  	// - name: owner
   219  	//   in: path
   220  	//   description: owner of the repo
   221  	//   type: string
   222  	//   required: true
   223  	// - name: repo
   224  	//   in: path
   225  	//   description: name of the repo
   226  	//   type: string
   227  	//   required: true
   228  	// - name: body
   229  	//   in: body
   230  	//   schema:
   231  	//     "$ref": "#/definitions/CreateKeyOption"
   232  	// responses:
   233  	//   "201":
   234  	//     "$ref": "#/responses/DeployKey"
   235  	//   "404":
   236  	//     "$ref": "#/responses/notFound"
   237  	//   "422":
   238  	//     "$ref": "#/responses/validationError"
   239  
   240  	form := web.GetForm(ctx).(*api.CreateKeyOption)
   241  	content, err := asymkey_model.CheckPublicKeyString(form.Key)
   242  	if err != nil {
   243  		HandleCheckKeyStringError(ctx, err)
   244  		return
   245  	}
   246  
   247  	key, err := asymkey_model.AddDeployKey(ctx.Repo.Repository.ID, form.Title, content, form.ReadOnly)
   248  	if err != nil {
   249  		HandleAddKeyError(ctx, err)
   250  		return
   251  	}
   252  
   253  	key.Content = content
   254  	apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
   255  	ctx.JSON(http.StatusCreated, convert.ToDeployKey(apiLink, key))
   256  }
   257  
   258  // DeleteDeploykey delete deploy key for a repository
   259  func DeleteDeploykey(ctx *context.APIContext) {
   260  	// swagger:operation DELETE /repos/{owner}/{repo}/keys/{id} repository repoDeleteKey
   261  	// ---
   262  	// summary: Delete a key from a repository
   263  	// parameters:
   264  	// - name: owner
   265  	//   in: path
   266  	//   description: owner of the repo
   267  	//   type: string
   268  	//   required: true
   269  	// - name: repo
   270  	//   in: path
   271  	//   description: name of the repo
   272  	//   type: string
   273  	//   required: true
   274  	// - name: id
   275  	//   in: path
   276  	//   description: id of the key to delete
   277  	//   type: integer
   278  	//   format: int64
   279  	//   required: true
   280  	// responses:
   281  	//   "204":
   282  	//     "$ref": "#/responses/empty"
   283  	//   "403":
   284  	//     "$ref": "#/responses/forbidden"
   285  	//   "404":
   286  	//     "$ref": "#/responses/notFound"
   287  
   288  	if err := asymkey_service.DeleteDeployKey(ctx, ctx.Doer, ctx.ParamsInt64(":id")); err != nil {
   289  		if asymkey_model.IsErrKeyAccessDenied(err) {
   290  			ctx.Error(http.StatusForbidden, "", "You do not have access to this key")
   291  		} else {
   292  			ctx.Error(http.StatusInternalServerError, "DeleteDeployKey", err)
   293  		}
   294  		return
   295  	}
   296  
   297  	ctx.Status(http.StatusNoContent)
   298  }