code.gitea.io/gitea@v1.22.3/routers/api/v1/repo/collaborators.go (about)

     1  // Copyright 2016 The Gogs Authors. All rights reserved.
     2  // Copyright 2018 The Gitea Authors. All rights reserved.
     3  // SPDX-License-Identifier: MIT
     4  
     5  package repo
     6  
     7  import (
     8  	"errors"
     9  	"net/http"
    10  
    11  	"code.gitea.io/gitea/models/perm"
    12  	access_model "code.gitea.io/gitea/models/perm/access"
    13  	repo_model "code.gitea.io/gitea/models/repo"
    14  	user_model "code.gitea.io/gitea/models/user"
    15  	repo_module "code.gitea.io/gitea/modules/repository"
    16  	api "code.gitea.io/gitea/modules/structs"
    17  	"code.gitea.io/gitea/modules/web"
    18  	"code.gitea.io/gitea/routers/api/v1/utils"
    19  	"code.gitea.io/gitea/services/context"
    20  	"code.gitea.io/gitea/services/convert"
    21  	repo_service "code.gitea.io/gitea/services/repository"
    22  )
    23  
    24  // ListCollaborators list a repository's collaborators
    25  func ListCollaborators(ctx *context.APIContext) {
    26  	// swagger:operation GET /repos/{owner}/{repo}/collaborators repository repoListCollaborators
    27  	// ---
    28  	// summary: List a repository's collaborators
    29  	// produces:
    30  	// - application/json
    31  	// parameters:
    32  	// - name: owner
    33  	//   in: path
    34  	//   description: owner of the repo
    35  	//   type: string
    36  	//   required: true
    37  	// - name: repo
    38  	//   in: path
    39  	//   description: name of the repo
    40  	//   type: string
    41  	//   required: true
    42  	// - name: page
    43  	//   in: query
    44  	//   description: page number of results to return (1-based)
    45  	//   type: integer
    46  	// - name: limit
    47  	//   in: query
    48  	//   description: page size of results
    49  	//   type: integer
    50  	// responses:
    51  	//   "200":
    52  	//     "$ref": "#/responses/UserList"
    53  	//   "404":
    54  	//     "$ref": "#/responses/notFound"
    55  
    56  	collaborators, total, err := repo_model.GetCollaborators(ctx, &repo_model.FindCollaborationOptions{
    57  		ListOptions: utils.GetListOptions(ctx),
    58  		RepoID:      ctx.Repo.Repository.ID,
    59  	})
    60  	if err != nil {
    61  		ctx.Error(http.StatusInternalServerError, "ListCollaborators", err)
    62  		return
    63  	}
    64  
    65  	users := make([]*api.User, len(collaborators))
    66  	for i, collaborator := range collaborators {
    67  		users[i] = convert.ToUser(ctx, collaborator.User, ctx.Doer)
    68  	}
    69  
    70  	ctx.SetTotalCountHeader(total)
    71  	ctx.JSON(http.StatusOK, users)
    72  }
    73  
    74  // IsCollaborator check if a user is a collaborator of a repository
    75  func IsCollaborator(ctx *context.APIContext) {
    76  	// swagger:operation GET /repos/{owner}/{repo}/collaborators/{collaborator} repository repoCheckCollaborator
    77  	// ---
    78  	// summary: Check if a user is a collaborator of a repository
    79  	// produces:
    80  	// - application/json
    81  	// parameters:
    82  	// - name: owner
    83  	//   in: path
    84  	//   description: owner of the repo
    85  	//   type: string
    86  	//   required: true
    87  	// - name: repo
    88  	//   in: path
    89  	//   description: name of the repo
    90  	//   type: string
    91  	//   required: true
    92  	// - name: collaborator
    93  	//   in: path
    94  	//   description: username of the collaborator
    95  	//   type: string
    96  	//   required: true
    97  	// responses:
    98  	//   "204":
    99  	//     "$ref": "#/responses/empty"
   100  	//   "404":
   101  	//     "$ref": "#/responses/notFound"
   102  	//   "422":
   103  	//     "$ref": "#/responses/validationError"
   104  
   105  	user, err := user_model.GetUserByName(ctx, ctx.Params(":collaborator"))
   106  	if err != nil {
   107  		if user_model.IsErrUserNotExist(err) {
   108  			ctx.Error(http.StatusUnprocessableEntity, "", err)
   109  		} else {
   110  			ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
   111  		}
   112  		return
   113  	}
   114  	isColab, err := repo_model.IsCollaborator(ctx, ctx.Repo.Repository.ID, user.ID)
   115  	if err != nil {
   116  		ctx.Error(http.StatusInternalServerError, "IsCollaborator", err)
   117  		return
   118  	}
   119  	if isColab {
   120  		ctx.Status(http.StatusNoContent)
   121  	} else {
   122  		ctx.NotFound()
   123  	}
   124  }
   125  
   126  // AddCollaborator add a collaborator to a repository
   127  func AddCollaborator(ctx *context.APIContext) {
   128  	// swagger:operation PUT /repos/{owner}/{repo}/collaborators/{collaborator} repository repoAddCollaborator
   129  	// ---
   130  	// summary: Add a collaborator to a repository
   131  	// produces:
   132  	// - application/json
   133  	// parameters:
   134  	// - name: owner
   135  	//   in: path
   136  	//   description: owner of the repo
   137  	//   type: string
   138  	//   required: true
   139  	// - name: repo
   140  	//   in: path
   141  	//   description: name of the repo
   142  	//   type: string
   143  	//   required: true
   144  	// - name: collaborator
   145  	//   in: path
   146  	//   description: username of the collaborator to add
   147  	//   type: string
   148  	//   required: true
   149  	// - name: body
   150  	//   in: body
   151  	//   schema:
   152  	//     "$ref": "#/definitions/AddCollaboratorOption"
   153  	// responses:
   154  	//   "204":
   155  	//     "$ref": "#/responses/empty"
   156  	//   "403":
   157  	//     "$ref": "#/responses/forbidden"
   158  	//   "404":
   159  	//     "$ref": "#/responses/notFound"
   160  	//   "422":
   161  	//     "$ref": "#/responses/validationError"
   162  
   163  	form := web.GetForm(ctx).(*api.AddCollaboratorOption)
   164  
   165  	collaborator, err := user_model.GetUserByName(ctx, ctx.Params(":collaborator"))
   166  	if err != nil {
   167  		if user_model.IsErrUserNotExist(err) {
   168  			ctx.Error(http.StatusUnprocessableEntity, "", err)
   169  		} else {
   170  			ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
   171  		}
   172  		return
   173  	}
   174  
   175  	if !collaborator.IsActive {
   176  		ctx.Error(http.StatusInternalServerError, "InactiveCollaborator", errors.New("collaborator's account is inactive"))
   177  		return
   178  	}
   179  
   180  	if err := repo_module.AddCollaborator(ctx, ctx.Repo.Repository, collaborator); err != nil {
   181  		if errors.Is(err, user_model.ErrBlockedUser) {
   182  			ctx.Error(http.StatusForbidden, "AddCollaborator", err)
   183  		} else {
   184  			ctx.Error(http.StatusInternalServerError, "AddCollaborator", err)
   185  		}
   186  		return
   187  	}
   188  
   189  	if form.Permission != nil {
   190  		if err := repo_model.ChangeCollaborationAccessMode(ctx, ctx.Repo.Repository, collaborator.ID, perm.ParseAccessMode(*form.Permission)); err != nil {
   191  			ctx.Error(http.StatusInternalServerError, "ChangeCollaborationAccessMode", err)
   192  			return
   193  		}
   194  	}
   195  
   196  	ctx.Status(http.StatusNoContent)
   197  }
   198  
   199  // DeleteCollaborator delete a collaborator from a repository
   200  func DeleteCollaborator(ctx *context.APIContext) {
   201  	// swagger:operation DELETE /repos/{owner}/{repo}/collaborators/{collaborator} repository repoDeleteCollaborator
   202  	// ---
   203  	// summary: Delete a collaborator from a repository
   204  	// produces:
   205  	// - application/json
   206  	// parameters:
   207  	// - name: owner
   208  	//   in: path
   209  	//   description: owner of the repo
   210  	//   type: string
   211  	//   required: true
   212  	// - name: repo
   213  	//   in: path
   214  	//   description: name of the repo
   215  	//   type: string
   216  	//   required: true
   217  	// - name: collaborator
   218  	//   in: path
   219  	//   description: username of the collaborator to delete
   220  	//   type: string
   221  	//   required: true
   222  	// responses:
   223  	//   "204":
   224  	//     "$ref": "#/responses/empty"
   225  	//   "404":
   226  	//     "$ref": "#/responses/notFound"
   227  	//   "422":
   228  	//     "$ref": "#/responses/validationError"
   229  
   230  	collaborator, err := user_model.GetUserByName(ctx, ctx.Params(":collaborator"))
   231  	if err != nil {
   232  		if user_model.IsErrUserNotExist(err) {
   233  			ctx.Error(http.StatusUnprocessableEntity, "", err)
   234  		} else {
   235  			ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
   236  		}
   237  		return
   238  	}
   239  
   240  	if err := repo_service.DeleteCollaboration(ctx, ctx.Repo.Repository, collaborator); err != nil {
   241  		ctx.Error(http.StatusInternalServerError, "DeleteCollaboration", err)
   242  		return
   243  	}
   244  	ctx.Status(http.StatusNoContent)
   245  }
   246  
   247  // GetRepoPermissions gets repository permissions for a user
   248  func GetRepoPermissions(ctx *context.APIContext) {
   249  	// swagger:operation GET /repos/{owner}/{repo}/collaborators/{collaborator}/permission repository repoGetRepoPermissions
   250  	// ---
   251  	// summary: Get repository permissions for a user
   252  	// produces:
   253  	// - application/json
   254  	// parameters:
   255  	// - name: owner
   256  	//   in: path
   257  	//   description: owner of the repo
   258  	//   type: string
   259  	//   required: true
   260  	// - name: repo
   261  	//   in: path
   262  	//   description: name of the repo
   263  	//   type: string
   264  	//   required: true
   265  	// - name: collaborator
   266  	//   in: path
   267  	//   description: username of the collaborator
   268  	//   type: string
   269  	//   required: true
   270  	// responses:
   271  	//   "200":
   272  	//     "$ref": "#/responses/RepoCollaboratorPermission"
   273  	//   "404":
   274  	//     "$ref": "#/responses/notFound"
   275  	//   "403":
   276  	//     "$ref": "#/responses/forbidden"
   277  
   278  	if !ctx.Doer.IsAdmin && ctx.Doer.LoginName != ctx.Params(":collaborator") && !ctx.IsUserRepoAdmin() {
   279  		ctx.Error(http.StatusForbidden, "User", "Only admins can query all permissions, repo admins can query all repo permissions, collaborators can query only their own")
   280  		return
   281  	}
   282  
   283  	collaborator, err := user_model.GetUserByName(ctx, ctx.Params(":collaborator"))
   284  	if err != nil {
   285  		if user_model.IsErrUserNotExist(err) {
   286  			ctx.Error(http.StatusNotFound, "GetUserByName", err)
   287  		} else {
   288  			ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
   289  		}
   290  		return
   291  	}
   292  
   293  	permission, err := access_model.GetUserRepoPermission(ctx, ctx.Repo.Repository, collaborator)
   294  	if err != nil {
   295  		ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
   296  		return
   297  	}
   298  
   299  	ctx.JSON(http.StatusOK, convert.ToUserAndPermission(ctx, collaborator, ctx.ContextUser, permission.AccessMode))
   300  }
   301  
   302  // GetReviewers return all users that can be requested to review in this repo
   303  func GetReviewers(ctx *context.APIContext) {
   304  	// swagger:operation GET /repos/{owner}/{repo}/reviewers repository repoGetReviewers
   305  	// ---
   306  	// summary: Return all users that can be requested to review in this repo
   307  	// produces:
   308  	// - application/json
   309  	// parameters:
   310  	// - name: owner
   311  	//   in: path
   312  	//   description: owner of the repo
   313  	//   type: string
   314  	//   required: true
   315  	// - name: repo
   316  	//   in: path
   317  	//   description: name of the repo
   318  	//   type: string
   319  	//   required: true
   320  	// responses:
   321  	//   "200":
   322  	//     "$ref": "#/responses/UserList"
   323  	//   "404":
   324  	//     "$ref": "#/responses/notFound"
   325  
   326  	reviewers, err := repo_model.GetReviewers(ctx, ctx.Repo.Repository, ctx.Doer.ID, 0)
   327  	if err != nil {
   328  		ctx.Error(http.StatusInternalServerError, "ListCollaborators", err)
   329  		return
   330  	}
   331  	ctx.JSON(http.StatusOK, convert.ToUsers(ctx, ctx.Doer, reviewers))
   332  }
   333  
   334  // GetAssignees return all users that have write access and can be assigned to issues
   335  func GetAssignees(ctx *context.APIContext) {
   336  	// swagger:operation GET /repos/{owner}/{repo}/assignees repository repoGetAssignees
   337  	// ---
   338  	// summary: Return all users that have write access and can be assigned to issues
   339  	// produces:
   340  	// - application/json
   341  	// parameters:
   342  	// - name: owner
   343  	//   in: path
   344  	//   description: owner of the repo
   345  	//   type: string
   346  	//   required: true
   347  	// - name: repo
   348  	//   in: path
   349  	//   description: name of the repo
   350  	//   type: string
   351  	//   required: true
   352  	// responses:
   353  	//   "200":
   354  	//     "$ref": "#/responses/UserList"
   355  	//   "404":
   356  	//     "$ref": "#/responses/notFound"
   357  
   358  	assignees, err := repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository)
   359  	if err != nil {
   360  		ctx.Error(http.StatusInternalServerError, "ListCollaborators", err)
   361  		return
   362  	}
   363  	ctx.JSON(http.StatusOK, convert.ToUsers(ctx, ctx.Doer, assignees))
   364  }