Skip to content
Merged
104 changes: 104 additions & 0 deletions integrations/api_repo_collaborator_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package integrations

import (
"net/http"
"net/url"
"testing"

"code.gitea.io/gitea/models/perm"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs"

"github.com/stretchr/testify/assert"
)

func TestAPIRepoCollaboratorPermission(t *testing.T) {
onGiteaRun(t, func(t *testing.T, u *url.URL) {
//user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2}).(*user_model.User)
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User)
user20 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 20}).(*user_model.User)
repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}).(*repo_model.Repository)
repo1Owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo1.OwnerID}).(*user_model.User)

// Login as User2.
session := loginUser(t, repo1Owner.Name)
testCtx := NewAPITestContext(t, repo1Owner.Name, repo1.Name)

t.Run("RepoOwnerShouldBeAdmin", func(t *testing.T) {
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo1Owner.Name, repo1.Name, repo1Owner.Name, testCtx.Token)
resp := session.MakeRequest(t, req, http.StatusOK)

var repoPermission api.RepoCollaboratorPermission
DecodeJSON(t, resp, &repoPermission)

assert.Equal(t, "owner", repoPermission.Permission)
})

t.Run("CollaboratorWithReadAccess", func(t *testing.T) {
t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeRead))

req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo1Owner.Name, repo1.Name, user4.Name, testCtx.Token)
resp := session.MakeRequest(t, req, http.StatusOK)

var repoPermission api.RepoCollaboratorPermission
DecodeJSON(t, resp, &repoPermission)

assert.Equal(t, "read", repoPermission.Permission)
})

t.Run("CollaboratorWithWriteAccess", func(t *testing.T) {
t.Run("AddUserAsCollaboratorWithWriteAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeWrite))

req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo1Owner.Name, repo1.Name, user4.Name, testCtx.Token)
resp := session.MakeRequest(t, req, http.StatusOK)

var repoPermission api.RepoCollaboratorPermission
DecodeJSON(t, resp, &repoPermission)

assert.Equal(t, "write", repoPermission.Permission)
})

t.Run("CollaboratorWithAdminAccess", func(t *testing.T) {
t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user4.Name, perm.AccessModeAdmin))

req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo1Owner.Name, repo1.Name, user4.Name, testCtx.Token)
resp := session.MakeRequest(t, req, http.StatusOK)

var repoPermission api.RepoCollaboratorPermission
DecodeJSON(t, resp, &repoPermission)

assert.Equal(t, "admin", repoPermission.Permission)
})

t.Run("WhyHasUser20ReadAccess", func(t *testing.T) {
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo1Owner.Name, repo1.Name, user20.Name, testCtx.Token)
resp := session.MakeRequest(t, req, http.StatusOK)

var repoPermission api.RepoCollaboratorPermission
DecodeJSON(t, resp, &repoPermission)

assert.Equal(t, "read", repoPermission.Permission)
})

t.Run("WhyHasUser5ReadAccess", func(t *testing.T) {
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo1Owner.Name, repo1.Name, "user5", testCtx.Token)
resp := session.MakeRequest(t, req, http.StatusOK)

var repoPermission api.RepoCollaboratorPermission
DecodeJSON(t, resp, &repoPermission)

assert.Equal(t, "read", repoPermission.Permission)
})

t.Run("CollaboratorNotFound", func(t *testing.T) {
req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo1Owner.Name, repo1.Name, "non-existent-user", testCtx.Token)
session.MakeRequest(t, req, http.StatusNotFound)
})
})
}
9 changes: 9 additions & 0 deletions modules/convert/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,12 @@ func User2UserSettings(user *user_model.User) api.UserSettings {
DiffViewStyle: user.DiffViewStyle,
}
}

// ToUserAndPermission return User and its collaboration permission for a repository
func ToUserAndPermission(user, doer *user_model.User, accessMode perm.AccessMode) api.RepoCollaboratorPermission {
return api.RepoCollaboratorPermission{
User: ToUser(user, doer),
Permission: accessMode.String(),
RoleName: accessMode.String(),
}
}
7 changes: 7 additions & 0 deletions modules/structs/repo_collaborator.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,10 @@ package structs
type AddCollaboratorOption struct {
Permission *string `json:"permission"`
}

// RepoCollaboratorPermission to get repository permission for a collaborator
type RepoCollaboratorPermission struct {
Permission string `json:"permission"`
RoleName string `json:"role_name"`
User *User `json:"user"`
}
9 changes: 6 additions & 3 deletions routers/api/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -801,9 +801,12 @@ func Routes() *web.Route {
}, reqToken(), reqAdmin(), reqWebhooksEnabled())
m.Group("/collaborators", func() {
m.Get("", reqAnyRepoReader(), repo.ListCollaborators)
m.Combo("/{collaborator}").Get(reqAnyRepoReader(), repo.IsCollaborator).
Put(reqAdmin(), bind(api.AddCollaboratorOption{}), repo.AddCollaborator).
Delete(reqAdmin(), repo.DeleteCollaborator)
m.Group("/{collaborator}", func() {
m.Combo("").Get(reqAnyRepoReader(), repo.IsCollaborator).
Put(reqAdmin(), bind(api.AddCollaboratorOption{}), repo.AddCollaborator).
Delete(reqAdmin(), repo.DeleteCollaborator)
m.Get("/permission", repo.GetRepoPermissions)
}, reqToken())
}, reqToken())
m.Get("/assignees", reqToken(), reqAnyRepoReader(), repo.GetAssignees)
m.Get("/reviewers", reqToken(), reqAnyRepoReader(), repo.GetReviewers)
Expand Down
56 changes: 56 additions & 0 deletions routers/api/v1/repo/collaborators.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,62 @@ func DeleteCollaborator(ctx *context.APIContext) {
ctx.Status(http.StatusNoContent)
}

// GetRepoPermissions gets repository permissions for a user
func GetRepoPermissions(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/collaborators/{collaborator}/permission repository repoGetRepoPermissions
// ---
// summary: Get repository permissions for a user
// produces:
// - application/json
// parameters:
// - name: owner
// in: path
// description: owner of the repo
// type: string
// required: true
// - name: repo
// in: path
// description: name of the repo
// type: string
// required: true
// - name: collaborator
// in: path
// description: username of the collaborator
// type: string
// required: true
// responses:
// "200":
// "$ref": "#/responses/RepoCollaboratorPermission"
// "404":
// "$ref": "#/responses/notFound"
// "403":
// "$ref": "#/responses/forbidden"

// only admins can query all permissions, repo admins can query all repo permissions, collaborators can query only their own
if !ctx.Doer.IsAdmin && ctx.Doer.LoginName != ctx.Params(":collaborator") && !ctx.IsUserRepoAdmin() {
ctx.Error(http.StatusForbidden, "User", "Only admins can query all permissions, repo admins can query all repo permissions, collaborators can query only their own")
return
}

collaborator, err := user_model.GetUserByName(ctx.Params(":collaborator"))
if err != nil {
if user_model.IsErrUserNotExist(err) {
ctx.Error(http.StatusNotFound, "GetUserByName", err)
} else {
ctx.Error(http.StatusInternalServerError, "GetUserByName", err)
}
return
}

permission, err := models.GetUserRepoPermission(ctx.Repo.Repository, collaborator)
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetUserRepoPermission", err)
return
}

ctx.JSON(http.StatusOK, convert.ToUserAndPermission(collaborator, ctx.ContextUser, permission.AccessMode))
}

// GetReviewers return all users that can be requested to review in this repo
func GetReviewers(ctx *context.APIContext) {
// swagger:operation GET /repos/{owner}/{repo}/reviewers repository repoGetReviewers
Expand Down
7 changes: 7 additions & 0 deletions routers/api/v1/swagger/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,3 +344,10 @@ type swaggerWikiCommitList struct {
// in:body
Body api.WikiCommitList `json:"body"`
}

// RepoCollaboratorPermission
// swagger:response RepoCollaboratorPermission
type swaggerRepoCollaboratorPermission struct {
// in:body
Body api.RepoCollaboratorPermission `json:"body"`
}
70 changes: 70 additions & 0 deletions templates/swagger/v1_json.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -3129,6 +3129,52 @@
}
}
},
"/repos/{owner}/{repo}/collaborators/{collaborator}/permission": {
"get": {
"produces": [
"application/json"
],
"tags": [
"repository"
],
"summary": "Get repository permissions for a user",
"operationId": "repoGetRepoPermissions",
"parameters": [
{
"type": "string",
"description": "owner of the repo",
"name": "owner",
"in": "path",
"required": true
},
{
"type": "string",
"description": "name of the repo",
"name": "repo",
"in": "path",
"required": true
},
{
"type": "string",
"description": "username of the collaborator",
"name": "collaborator",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"$ref": "#/responses/RepoCollaboratorPermission"
},
"403": {
"$ref": "#/responses/forbidden"
},
"404": {
"$ref": "#/responses/notFound"
}
}
}
},
"/repos/{owner}/{repo}/commits": {
"get": {
"produces": [
Expand Down Expand Up @@ -17443,6 +17489,24 @@
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"RepoCollaboratorPermission": {
"description": "RepoCollaboratorPermission to get repository permission for a collaborator",
"type": "object",
"properties": {
"permission": {
"type": "string",
"x-go-name": "Permission"
},
"role_name": {
"type": "string",
"x-go-name": "RoleName"
},
"user": {
"$ref": "#/definitions/User"
}
},
"x-go-package": "code.gitea.io/gitea/modules/structs"
},
"RepoCommit": {
"type": "object",
"title": "RepoCommit contains information of a commit in the context of a repository.",
Expand Down Expand Up @@ -19118,6 +19182,12 @@
}
}
},
"RepoCollaboratorPermission": {
"description": "RepoCollaboratorPermission",
"schema": {
"$ref": "#/definitions/RepoCollaboratorPermission"
}
},
"Repository": {
"description": "Repository",
"schema": {
Expand Down