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 }