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