code.gitea.io/gitea@v1.21.7/routers/private/serv.go (about) 1 // Copyright 2019 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package private 5 6 import ( 7 "fmt" 8 "net/http" 9 "strings" 10 11 asymkey_model "code.gitea.io/gitea/models/asymkey" 12 "code.gitea.io/gitea/models/perm" 13 access_model "code.gitea.io/gitea/models/perm/access" 14 repo_model "code.gitea.io/gitea/models/repo" 15 "code.gitea.io/gitea/models/unit" 16 user_model "code.gitea.io/gitea/models/user" 17 "code.gitea.io/gitea/modules/context" 18 "code.gitea.io/gitea/modules/git" 19 "code.gitea.io/gitea/modules/log" 20 "code.gitea.io/gitea/modules/private" 21 "code.gitea.io/gitea/modules/setting" 22 repo_service "code.gitea.io/gitea/services/repository" 23 wiki_service "code.gitea.io/gitea/services/wiki" 24 ) 25 26 // ServNoCommand returns information about the provided keyid 27 func ServNoCommand(ctx *context.PrivateContext) { 28 keyID := ctx.ParamsInt64(":keyid") 29 if keyID <= 0 { 30 ctx.JSON(http.StatusBadRequest, private.Response{ 31 UserMsg: fmt.Sprintf("Bad key id: %d", keyID), 32 }) 33 } 34 results := private.KeyAndOwner{} 35 36 key, err := asymkey_model.GetPublicKeyByID(keyID) 37 if err != nil { 38 if asymkey_model.IsErrKeyNotExist(err) { 39 ctx.JSON(http.StatusUnauthorized, private.Response{ 40 UserMsg: fmt.Sprintf("Cannot find key: %d", keyID), 41 }) 42 return 43 } 44 log.Error("Unable to get public key: %d Error: %v", keyID, err) 45 ctx.JSON(http.StatusInternalServerError, private.Response{ 46 Err: err.Error(), 47 }) 48 return 49 } 50 results.Key = key 51 52 if key.Type == asymkey_model.KeyTypeUser || key.Type == asymkey_model.KeyTypePrincipal { 53 user, err := user_model.GetUserByID(ctx, key.OwnerID) 54 if err != nil { 55 if user_model.IsErrUserNotExist(err) { 56 ctx.JSON(http.StatusUnauthorized, private.Response{ 57 UserMsg: fmt.Sprintf("Cannot find owner with id: %d for key: %d", key.OwnerID, keyID), 58 }) 59 return 60 } 61 log.Error("Unable to get owner with id: %d for public key: %d Error: %v", key.OwnerID, keyID, err) 62 ctx.JSON(http.StatusInternalServerError, private.Response{ 63 Err: err.Error(), 64 }) 65 return 66 } 67 if !user.IsActive || user.ProhibitLogin { 68 ctx.JSON(http.StatusForbidden, private.Response{ 69 UserMsg: "Your account is disabled.", 70 }) 71 return 72 } 73 results.Owner = user 74 } 75 ctx.JSON(http.StatusOK, &results) 76 } 77 78 // ServCommand returns information about the provided keyid 79 func ServCommand(ctx *context.PrivateContext) { 80 keyID := ctx.ParamsInt64(":keyid") 81 ownerName := ctx.Params(":owner") 82 repoName := ctx.Params(":repo") 83 mode := perm.AccessMode(ctx.FormInt("mode")) 84 85 // Set the basic parts of the results to return 86 results := private.ServCommandResults{ 87 RepoName: repoName, 88 OwnerName: ownerName, 89 KeyID: keyID, 90 } 91 92 // Now because we're not translating things properly let's just default some English strings here 93 modeString := "read" 94 if mode > perm.AccessModeRead { 95 modeString = "write to" 96 } 97 98 // The default unit we're trying to look at is code 99 unitType := unit.TypeCode 100 101 // Unless we're a wiki... 102 if strings.HasSuffix(repoName, ".wiki") { 103 // in which case we need to look at the wiki 104 unitType = unit.TypeWiki 105 // And we'd better munge the reponame and tell downstream we're looking at a wiki 106 results.IsWiki = true 107 results.RepoName = repoName[:len(repoName)-5] 108 } 109 110 owner, err := user_model.GetUserByName(ctx, results.OwnerName) 111 if err != nil { 112 if user_model.IsErrUserNotExist(err) { 113 // User is fetching/cloning a non-existent repository 114 log.Warn("Failed authentication attempt (cannot find repository: %s/%s) from %s", results.OwnerName, results.RepoName, ctx.RemoteAddr()) 115 ctx.JSON(http.StatusNotFound, private.Response{ 116 UserMsg: fmt.Sprintf("Cannot find repository: %s/%s", results.OwnerName, results.RepoName), 117 }) 118 return 119 } 120 log.Error("Unable to get repository owner: %s/%s Error: %v", results.OwnerName, results.RepoName, err) 121 ctx.JSON(http.StatusForbidden, private.Response{ 122 UserMsg: fmt.Sprintf("Unable to get repository owner: %s/%s %v", results.OwnerName, results.RepoName, err), 123 }) 124 return 125 } 126 if !owner.IsOrganization() && !owner.IsActive { 127 ctx.JSON(http.StatusForbidden, private.Response{ 128 UserMsg: "Repository cannot be accessed, you could retry it later", 129 }) 130 return 131 } 132 133 // Now get the Repository and set the results section 134 repoExist := true 135 repo, err := repo_model.GetRepositoryByName(owner.ID, results.RepoName) 136 if err != nil { 137 if repo_model.IsErrRepoNotExist(err) { 138 repoExist = false 139 for _, verb := range ctx.FormStrings("verb") { 140 if verb == "git-upload-pack" { 141 // User is fetching/cloning a non-existent repository 142 log.Warn("Failed authentication attempt (cannot find repository: %s/%s) from %s", results.OwnerName, results.RepoName, ctx.RemoteAddr()) 143 ctx.JSON(http.StatusNotFound, private.Response{ 144 UserMsg: fmt.Sprintf("Cannot find repository: %s/%s", results.OwnerName, results.RepoName), 145 }) 146 return 147 } 148 } 149 } else { 150 log.Error("Unable to get repository: %s/%s Error: %v", results.OwnerName, results.RepoName, err) 151 ctx.JSON(http.StatusInternalServerError, private.Response{ 152 Err: fmt.Sprintf("Unable to get repository: %s/%s %v", results.OwnerName, results.RepoName, err), 153 }) 154 return 155 } 156 } 157 158 if repoExist { 159 repo.Owner = owner 160 repo.OwnerName = ownerName 161 results.RepoID = repo.ID 162 163 if repo.IsBeingCreated() { 164 ctx.JSON(http.StatusInternalServerError, private.Response{ 165 Err: "Repository is being created, you could retry after it finished", 166 }) 167 return 168 } 169 170 if repo.IsBroken() { 171 ctx.JSON(http.StatusInternalServerError, private.Response{ 172 Err: "Repository is in a broken state", 173 }) 174 return 175 } 176 177 // We can shortcut at this point if the repo is a mirror 178 if mode > perm.AccessModeRead && repo.IsMirror { 179 ctx.JSON(http.StatusForbidden, private.Response{ 180 UserMsg: fmt.Sprintf("Mirror Repository %s/%s is read-only", results.OwnerName, results.RepoName), 181 }) 182 return 183 } 184 } 185 186 // Get the Public Key represented by the keyID 187 key, err := asymkey_model.GetPublicKeyByID(keyID) 188 if err != nil { 189 if asymkey_model.IsErrKeyNotExist(err) { 190 ctx.JSON(http.StatusNotFound, private.Response{ 191 UserMsg: fmt.Sprintf("Cannot find key: %d", keyID), 192 }) 193 return 194 } 195 log.Error("Unable to get public key: %d Error: %v", keyID, err) 196 ctx.JSON(http.StatusInternalServerError, private.Response{ 197 Err: fmt.Sprintf("Unable to get key: %d Error: %v", keyID, err), 198 }) 199 return 200 } 201 results.KeyName = key.Name 202 results.KeyID = key.ID 203 results.UserID = key.OwnerID 204 205 // If repo doesn't exist, deploy key doesn't make sense 206 if !repoExist && key.Type == asymkey_model.KeyTypeDeploy { 207 ctx.JSON(http.StatusNotFound, private.Response{ 208 UserMsg: fmt.Sprintf("Cannot find repository %s/%s", results.OwnerName, results.RepoName), 209 }) 210 return 211 } 212 213 // Deploy Keys have ownerID set to 0 therefore we can't use the owner 214 // So now we need to check if the key is a deploy key 215 // We'll keep hold of the deploy key here for permissions checking 216 var deployKey *asymkey_model.DeployKey 217 var user *user_model.User 218 if key.Type == asymkey_model.KeyTypeDeploy { 219 var err error 220 deployKey, err = asymkey_model.GetDeployKeyByRepo(ctx, key.ID, repo.ID) 221 if err != nil { 222 if asymkey_model.IsErrDeployKeyNotExist(err) { 223 ctx.JSON(http.StatusNotFound, private.Response{ 224 UserMsg: fmt.Sprintf("Public (Deploy) Key: %d:%s is not authorized to %s %s/%s.", key.ID, key.Name, modeString, results.OwnerName, results.RepoName), 225 }) 226 return 227 } 228 log.Error("Unable to get deploy for public (deploy) key: %d in %-v Error: %v", key.ID, repo, err) 229 ctx.JSON(http.StatusInternalServerError, private.Response{ 230 Err: fmt.Sprintf("Unable to get Deploy Key for Public Key: %d:%s in %s/%s.", key.ID, key.Name, results.OwnerName, results.RepoName), 231 }) 232 return 233 } 234 results.DeployKeyID = deployKey.ID 235 results.KeyName = deployKey.Name 236 237 // FIXME: Deploy keys aren't really the owner of the repo pushing changes 238 // however we don't have good way of representing deploy keys in hook.go 239 // so for now use the owner of the repository 240 results.UserName = results.OwnerName 241 results.UserID = repo.OwnerID 242 if !repo.Owner.KeepEmailPrivate { 243 results.UserEmail = repo.Owner.Email 244 } 245 } else { 246 // Get the user represented by the Key 247 var err error 248 user, err = user_model.GetUserByID(ctx, key.OwnerID) 249 if err != nil { 250 if user_model.IsErrUserNotExist(err) { 251 ctx.JSON(http.StatusUnauthorized, private.Response{ 252 UserMsg: fmt.Sprintf("Public Key: %d:%s owner %d does not exist.", key.ID, key.Name, key.OwnerID), 253 }) 254 return 255 } 256 log.Error("Unable to get owner: %d for public key: %d:%s Error: %v", key.OwnerID, key.ID, key.Name, err) 257 ctx.JSON(http.StatusInternalServerError, private.Response{ 258 Err: fmt.Sprintf("Unable to get Owner: %d for Deploy Key: %d:%s in %s/%s.", key.OwnerID, key.ID, key.Name, ownerName, repoName), 259 }) 260 return 261 } 262 263 if !user.IsActive || user.ProhibitLogin { 264 ctx.JSON(http.StatusForbidden, private.Response{ 265 UserMsg: "Your account is disabled.", 266 }) 267 return 268 } 269 270 results.UserName = user.Name 271 if !user.KeepEmailPrivate { 272 results.UserEmail = user.Email 273 } 274 } 275 276 // Don't allow pushing if the repo is archived 277 if repoExist && mode > perm.AccessModeRead && repo.IsArchived { 278 ctx.JSON(http.StatusUnauthorized, private.Response{ 279 UserMsg: fmt.Sprintf("Repo: %s/%s is archived.", results.OwnerName, results.RepoName), 280 }) 281 return 282 } 283 284 // Permissions checking: 285 if repoExist && 286 (mode > perm.AccessModeRead || 287 repo.IsPrivate || 288 owner.Visibility.IsPrivate() || 289 (user != nil && user.IsRestricted) || // user will be nil if the key is a deploykey 290 setting.Service.RequireSignInView) { 291 if key.Type == asymkey_model.KeyTypeDeploy { 292 if deployKey.Mode < mode { 293 ctx.JSON(http.StatusUnauthorized, private.Response{ 294 UserMsg: fmt.Sprintf("Deploy Key: %d:%s is not authorized to %s %s/%s.", key.ID, key.Name, modeString, results.OwnerName, results.RepoName), 295 }) 296 return 297 } 298 } else { 299 // Because of the special ref "refs/for" we will need to delay write permission check 300 if git.SupportProcReceive && unitType == unit.TypeCode { 301 mode = perm.AccessModeRead 302 } 303 304 perm, err := access_model.GetUserRepoPermission(ctx, repo, user) 305 if err != nil { 306 log.Error("Unable to get permissions for %-v with key %d in %-v Error: %v", user, key.ID, repo, err) 307 ctx.JSON(http.StatusInternalServerError, private.Response{ 308 Err: fmt.Sprintf("Unable to get permissions for user %d:%s with key %d in %s/%s Error: %v", user.ID, user.Name, key.ID, results.OwnerName, results.RepoName, err), 309 }) 310 return 311 } 312 313 userMode := perm.UnitAccessMode(unitType) 314 315 if userMode < mode { 316 log.Warn("Failed authentication attempt for %s with key %s (not authorized to %s %s/%s) from %s", user.Name, key.Name, modeString, ownerName, repoName, ctx.RemoteAddr()) 317 ctx.JSON(http.StatusUnauthorized, private.Response{ 318 UserMsg: fmt.Sprintf("User: %d:%s with Key: %d:%s is not authorized to %s %s/%s.", user.ID, user.Name, key.ID, key.Name, modeString, ownerName, repoName), 319 }) 320 return 321 } 322 } 323 } 324 325 // We already know we aren't using a deploy key 326 if !repoExist { 327 owner, err := user_model.GetUserByName(ctx, ownerName) 328 if err != nil { 329 ctx.JSON(http.StatusInternalServerError, private.Response{ 330 Err: fmt.Sprintf("Unable to get owner: %s %v", results.OwnerName, err), 331 }) 332 return 333 } 334 335 if owner.IsOrganization() && !setting.Repository.EnablePushCreateOrg { 336 ctx.JSON(http.StatusForbidden, private.Response{ 337 UserMsg: "Push to create is not enabled for organizations.", 338 }) 339 return 340 } 341 if !owner.IsOrganization() && !setting.Repository.EnablePushCreateUser { 342 ctx.JSON(http.StatusForbidden, private.Response{ 343 UserMsg: "Push to create is not enabled for users.", 344 }) 345 return 346 } 347 348 repo, err = repo_service.PushCreateRepo(ctx, user, owner, results.RepoName) 349 if err != nil { 350 log.Error("pushCreateRepo: %v", err) 351 ctx.JSON(http.StatusNotFound, private.Response{ 352 UserMsg: fmt.Sprintf("Cannot find repository: %s/%s", results.OwnerName, results.RepoName), 353 }) 354 return 355 } 356 results.RepoID = repo.ID 357 } 358 359 if results.IsWiki { 360 // Ensure the wiki is enabled before we allow access to it 361 if _, err := repo.GetUnit(ctx, unit.TypeWiki); err != nil { 362 if repo_model.IsErrUnitTypeNotExist(err) { 363 ctx.JSON(http.StatusForbidden, private.Response{ 364 UserMsg: "repository wiki is disabled", 365 }) 366 return 367 } 368 log.Error("Failed to get the wiki unit in %-v Error: %v", repo, err) 369 ctx.JSON(http.StatusInternalServerError, private.Response{ 370 Err: fmt.Sprintf("Failed to get the wiki unit in %s/%s Error: %v", ownerName, repoName, err), 371 }) 372 return 373 } 374 375 // Finally if we're trying to touch the wiki we should init it 376 if err = wiki_service.InitWiki(ctx, repo); err != nil { 377 log.Error("Failed to initialize the wiki in %-v Error: %v", repo, err) 378 ctx.JSON(http.StatusInternalServerError, private.Response{ 379 Err: fmt.Sprintf("Failed to initialize the wiki in %s/%s Error: %v", ownerName, repoName, err), 380 }) 381 return 382 } 383 } 384 log.Debug("Serv Results:\nIsWiki: %t\nDeployKeyID: %d\nKeyID: %d\tKeyName: %s\nUserName: %s\nUserID: %d\nOwnerName: %s\nRepoName: %s\nRepoID: %d", 385 results.IsWiki, 386 results.DeployKeyID, 387 results.KeyID, 388 results.KeyName, 389 results.UserName, 390 results.UserID, 391 results.OwnerName, 392 results.RepoName, 393 results.RepoID) 394 395 ctx.JSON(http.StatusOK, results) 396 // We will update the keys in a different call. 397 }