summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--integrations/api_repo_collaborator_test.go131
-rw-r--r--models/fixtures/user.yml32
-rw-r--r--modules/convert/user.go9
-rw-r--r--modules/structs/repo_collaborator.go7
-rw-r--r--routers/api/v1/api.go9
-rw-r--r--routers/api/v1/repo/collaborators.go55
-rw-r--r--routers/api/v1/swagger/repo.go7
-rw-r--r--templates/swagger/v1_json.tmpl70
8 files changed, 317 insertions, 3 deletions
diff --git a/integrations/api_repo_collaborator_test.go b/integrations/api_repo_collaborator_test.go
new file mode 100644
index 0000000000..fdca1d9150
--- /dev/null
+++ b/integrations/api_repo_collaborator_test.go
@@ -0,0 +1,131 @@
+// 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) {
+ repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}).(*repo_model.Repository)
+ repo2Owner := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo2.OwnerID}).(*user_model.User)
+
+ user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4}).(*user_model.User)
+ user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5}).(*user_model.User)
+ user10 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 10}).(*user_model.User)
+ user11 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 11}).(*user_model.User)
+
+ session := loginUser(t, repo2Owner.Name)
+ testCtx := NewAPITestContext(t, repo2Owner.Name, repo2.Name)
+
+ t.Run("RepoOwnerShouldBeOwner", func(t *testing.T) {
+ req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo2Owner.Name, repo2.Name, repo2Owner.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", repo2Owner.Name, repo2.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", repo2Owner.Name, repo2.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", repo2Owner.Name, repo2.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("CollaboratorNotFound", func(t *testing.T) {
+ req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo2Owner.Name, repo2.Name, "non-existent-user", testCtx.Token)
+ session.MakeRequest(t, req, http.StatusNotFound)
+ })
+
+ t.Run("CollaboratorCanQueryItsPermissions", func(t *testing.T) {
+ t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead))
+
+ _session := loginUser(t, user5.Name)
+ _testCtx := NewAPITestContext(t, user5.Name, repo2.Name)
+
+ req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo2Owner.Name, repo2.Name, user5.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("CollaboratorCanQueryItsPermissions", func(t *testing.T) {
+ t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user5.Name, perm.AccessModeRead))
+
+ _session := loginUser(t, user5.Name)
+ _testCtx := NewAPITestContext(t, user5.Name, repo2.Name)
+
+ req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo2Owner.Name, repo2.Name, user5.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("RepoAdminCanQueryACollaboratorsPermissions", func(t *testing.T) {
+ t.Run("AddUserAsCollaboratorWithAdminAccess", doAPIAddCollaborator(testCtx, user10.Name, perm.AccessModeAdmin))
+ t.Run("AddUserAsCollaboratorWithReadAccess", doAPIAddCollaborator(testCtx, user11.Name, perm.AccessModeRead))
+
+ _session := loginUser(t, user10.Name)
+ _testCtx := NewAPITestContext(t, user10.Name, repo2.Name)
+
+ req := NewRequestf(t, "GET", "/api/v1/repos/%s/%s/collaborators/%s/permission?token=%s", repo2Owner.Name, repo2.Name, user11.Name, _testCtx.Token)
+ resp := _session.MakeRequest(t, req, http.StatusOK)
+
+ var repoPermission api.RepoCollaboratorPermission
+ DecodeJSON(t, resp, &repoPermission)
+
+ assert.Equal(t, "read", repoPermission.Permission)
+ })
+ })
+}
diff --git a/models/fixtures/user.yml b/models/fixtures/user.yml
index 670b305621..67ba869c76 100644
--- a/models/fixtures/user.yml
+++ b/models/fixtures/user.yml
@@ -4,6 +4,7 @@
id: 1
lower_name: user1
name: user1
+ login_name: user1
full_name: User One
email: user1@example.com
email_notifications_preference: enabled
@@ -21,6 +22,7 @@
id: 2
lower_name: user2
name: user2
+ login_name: user2
full_name: " < U<se>r Tw<o > >< "
email: user2@example.com
keep_email_private: true
@@ -42,6 +44,7 @@
id: 3
lower_name: user3
name: user3
+ login_name: user3
full_name: " <<<< >> >> > >> > >>> >> "
email: user3@example.com
email_notifications_preference: onmention
@@ -60,6 +63,7 @@
id: 4
lower_name: user4
name: user4
+ login_name: user4
full_name: " "
email: user4@example.com
email_notifications_preference: onmention
@@ -78,6 +82,7 @@
id: 5
lower_name: user5
name: user5
+ login_name: user5
full_name: User Five
email: user5@example.com
email_notifications_preference: enabled
@@ -97,6 +102,7 @@
id: 6
lower_name: user6
name: user6
+ login_name: user6
full_name: User Six
email: user6@example.com
email_notifications_preference: enabled
@@ -115,6 +121,7 @@
id: 7
lower_name: user7
name: user7
+ login_name: user7
full_name: User Seven
email: user7@example.com
email_notifications_preference: disabled
@@ -133,6 +140,7 @@
id: 8
lower_name: user8
name: user8
+ login_name: user8
full_name: User Eight
email: user8@example.com
email_notifications_preference: enabled
@@ -152,6 +160,7 @@
id: 9
lower_name: user9
name: user9
+ login_name: user9
full_name: User Nine
email: user9@example.com
email_notifications_preference: onmention
@@ -169,6 +178,7 @@
id: 10
lower_name: user10
name: user10
+ login_name: user10
full_name: User Ten
email: user10@example.com
passwd_hash_algo: argon2
@@ -185,6 +195,7 @@
id: 11
lower_name: user11
name: user11
+ login_name: user11
full_name: User Eleven
email: user11@example.com
passwd_hash_algo: argon2
@@ -201,6 +212,7 @@
id: 12
lower_name: user12
name: user12
+ login_name: user12
full_name: User 12
email: user12@example.com
passwd_hash_algo: argon2
@@ -217,6 +229,7 @@
id: 13
lower_name: user13
name: user13
+ login_name: user13
full_name: User 13
email: user13@example.com
passwd_hash_algo: argon2
@@ -233,6 +246,7 @@
id: 14
lower_name: user14
name: user14
+ login_name: user14
full_name: User 14
email: user14@example.com
passwd_hash_algo: argon2
@@ -249,6 +263,7 @@
id: 15
lower_name: user15
name: user15
+ login_name: user15
full_name: User 15
email: user15@example.com
passwd_hash_algo: argon2
@@ -265,6 +280,7 @@
id: 16
lower_name: user16
name: user16
+ login_name: user16
full_name: User 16
email: user16@example.com
passwd_hash_algo: argon2
@@ -281,6 +297,7 @@
id: 17
lower_name: user17
name: user17
+ login_name: user17
full_name: User 17
email: user17@example.com
passwd_hash_algo: argon2
@@ -299,6 +316,7 @@
id: 18
lower_name: user18
name: user18
+ login_name: user18
full_name: User 18
email: user18@example.com
passwd_hash_algo: argon2
@@ -315,6 +333,7 @@
id: 19
lower_name: user19
name: user19
+ login_name: user19
full_name: User 19
email: user19@example.com
passwd_hash_algo: argon2
@@ -333,6 +352,7 @@
id: 20
lower_name: user20
name: user20
+ login_name: user20
full_name: User 20
email: user20@example.com
passwd_hash_algo: argon2
@@ -349,6 +369,7 @@
id: 21
lower_name: user21
name: user21
+ login_name: user21
full_name: User 21
email: user21@example.com
passwd_hash_algo: argon2
@@ -365,6 +386,7 @@
id: 22
lower_name: limited_org
name: limited_org
+ login_name: limited_org
full_name: Limited Org
email: limited_org@example.com
passwd_hash_algo: argon2
@@ -384,6 +406,7 @@
id: 23
lower_name: privated_org
name: privated_org
+ login_name: privated_org
full_name: Privated Org
email: privated_org@example.com
passwd_hash_algo: argon2
@@ -403,6 +426,7 @@
id: 24
lower_name: user24
name: user24
+ login_name: user24
full_name: "user24"
email: user24@example.com
keep_email_private: true
@@ -423,6 +447,7 @@
id: 25
lower_name: org25
name: org25
+ login_name: org25
full_name: "org25"
email: org25@example.com
passwd_hash_algo: argon2
@@ -440,6 +465,7 @@
id: 26
lower_name: org26
name: org26
+ login_name: org26
full_name: "Org26"
email: org26@example.com
email_notifications_preference: onmention
@@ -459,6 +485,7 @@
id: 27
lower_name: user27
name: user27
+ login_name: user27
full_name: User Twenty-Seven
email: user27@example.com
email_notifications_preference: enabled
@@ -475,6 +502,7 @@
id: 28
lower_name: user28
name: user28
+ login_name: user28
full_name: "user27"
email: user28@example.com
keep_email_private: true
@@ -495,6 +523,7 @@
id: 29
lower_name: user29
name: user29
+ login_name: user29
full_name: User 29
email: user29@example.com
passwd_hash_algo: argon2
@@ -512,6 +541,7 @@
id: 30
lower_name: user30
name: user30
+ login_name: user30
full_name: User Thirty
email: user30@example.com
passwd_hash_algo: argon2
@@ -530,6 +560,7 @@
id: 31
lower_name: user31
name: user31
+ login_name: user31
full_name: "user31"
email: user31@example.com
passwd_hash_algo: argon2
@@ -547,6 +578,7 @@
id: 32
lower_name: user32
name: user32
+ login_name: user32
full_name: User 32 (U2F test)
email: user32@example.com
passwd: 7d93daa0d1e6f2305cc8fa496847d61dc7320bb16262f9c55dd753480207234cdd96a93194e408341971742f4701772a025a # password
diff --git a/modules/convert/user.go b/modules/convert/user.go
index dc4a8c49c7..2b07d21838 100644
--- a/modules/convert/user.go
+++ b/modules/convert/user.go
@@ -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(),
+ }
+}
diff --git a/modules/structs/repo_collaborator.go b/modules/structs/repo_collaborator.go
index 2b4fa390d2..2f9c8992a1 100644
--- a/modules/structs/repo_collaborator.go
+++ b/modules/structs/repo_collaborator.go
@@ -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"`
+}
diff --git a/routers/api/v1/api.go b/routers/api/v1/api.go
index 782500e6c8..9351cc1510 100644
--- a/routers/api/v1/api.go
+++ b/routers/api/v1/api.go
@@ -810,9 +810,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)
diff --git a/routers/api/v1/repo/collaborators.go b/routers/api/v1/repo/collaborators.go
index 3bb6113d77..2db1724b2a 100644
--- a/routers/api/v1/repo/collaborators.go
+++ b/routers/api/v1/repo/collaborators.go
@@ -233,6 +233,61 @@ 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"
+
+ 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, 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
diff --git a/routers/api/v1/swagger/repo.go b/routers/api/v1/swagger/repo.go
index 40aeca677d..ab802db781 100644
--- a/routers/api/v1/swagger/repo.go
+++ b/routers/api/v1/swagger/repo.go
@@ -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"`
+}
diff --git a/templates/swagger/v1_json.tmpl b/templates/swagger/v1_json.tmpl
index d57a3a580b..3e4813f22c 100644
--- a/templates/swagger/v1_json.tmpl
+++ b/templates/swagger/v1_json.tmpl
@@ -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": [
@@ -17451,6 +17497,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.",
@@ -19126,6 +19190,12 @@
}
}
},
+ "RepoCollaboratorPermission": {
+ "description": "RepoCollaboratorPermission",
+ "schema": {
+ "$ref": "#/definitions/RepoCollaboratorPermission"
+ }
+ },
"Repository": {
"description": "Repository",
"schema": {