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 }