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  }