forgejo/tests/integration/org_test.go

272 lines
9.6 KiB
Go
Raw Normal View History

// Copyright 2019 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package integration
import (
"fmt"
"net/http"
"strings"
"testing"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/perm"
"code.gitea.io/gitea/models/unit"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/tests"
"github.com/stretchr/testify/assert"
)
func TestOrgRepos(t *testing.T) {
defer tests.PrepareTestEnv(t)()
var (
users = []string{"user1", "user2"}
cases = map[string][]string{
"alphabetically": {"repo21", "repo3", "repo5"},
"reversealphabetically": {"repo5", "repo3", "repo21"},
}
)
for _, user := range users {
t.Run(user, func(t *testing.T) {
session := loginUser(t, user)
for sortBy, repos := range cases {
req := NewRequest(t, "GET", "/org3?sort="+sortBy)
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
sel := htmlDoc.doc.Find("a.name")
2021-06-07 07:27:09 +02:00
assert.Len(t, repos, len(sel.Nodes))
for i := 0; i < len(repos); i++ {
assert.EqualValues(t, repos[i], strings.TrimSpace(sel.Eq(i).Text()))
}
}
})
}
}
func TestLimitedOrg(t *testing.T) {
defer tests.PrepareTestEnv(t)()
// not logged in user
req := NewRequest(t, "GET", "/limited_org")
MakeRequest(t, req, http.StatusNotFound)
req = NewRequest(t, "GET", "/limited_org/public_repo_on_limited_org")
MakeRequest(t, req, http.StatusNotFound)
req = NewRequest(t, "GET", "/limited_org/private_repo_on_limited_org")
MakeRequest(t, req, http.StatusNotFound)
// login non-org member user
session := loginUser(t, "user2")
req = NewRequest(t, "GET", "/limited_org")
session.MakeRequest(t, req, http.StatusOK)
req = NewRequest(t, "GET", "/limited_org/public_repo_on_limited_org")
session.MakeRequest(t, req, http.StatusOK)
req = NewRequest(t, "GET", "/limited_org/private_repo_on_limited_org")
session.MakeRequest(t, req, http.StatusNotFound)
// site admin
session = loginUser(t, "user1")
req = NewRequest(t, "GET", "/limited_org")
session.MakeRequest(t, req, http.StatusOK)
req = NewRequest(t, "GET", "/limited_org/public_repo_on_limited_org")
session.MakeRequest(t, req, http.StatusOK)
req = NewRequest(t, "GET", "/limited_org/private_repo_on_limited_org")
session.MakeRequest(t, req, http.StatusOK)
}
func TestPrivateOrg(t *testing.T) {
defer tests.PrepareTestEnv(t)()
// not logged in user
req := NewRequest(t, "GET", "/privated_org")
MakeRequest(t, req, http.StatusNotFound)
req = NewRequest(t, "GET", "/privated_org/public_repo_on_private_org")
MakeRequest(t, req, http.StatusNotFound)
req = NewRequest(t, "GET", "/privated_org/private_repo_on_private_org")
MakeRequest(t, req, http.StatusNotFound)
// login non-org member user
session := loginUser(t, "user2")
req = NewRequest(t, "GET", "/privated_org")
session.MakeRequest(t, req, http.StatusNotFound)
req = NewRequest(t, "GET", "/privated_org/public_repo_on_private_org")
session.MakeRequest(t, req, http.StatusNotFound)
req = NewRequest(t, "GET", "/privated_org/private_repo_on_private_org")
session.MakeRequest(t, req, http.StatusNotFound)
// non-org member who is collaborator on repo in private org
session = loginUser(t, "user4")
req = NewRequest(t, "GET", "/privated_org")
session.MakeRequest(t, req, http.StatusNotFound)
req = NewRequest(t, "GET", "/privated_org/public_repo_on_private_org") // colab of this repo
session.MakeRequest(t, req, http.StatusOK)
req = NewRequest(t, "GET", "/privated_org/private_repo_on_private_org")
session.MakeRequest(t, req, http.StatusNotFound)
// site admin
session = loginUser(t, "user1")
req = NewRequest(t, "GET", "/privated_org")
session.MakeRequest(t, req, http.StatusOK)
req = NewRequest(t, "GET", "/privated_org/public_repo_on_private_org")
session.MakeRequest(t, req, http.StatusOK)
req = NewRequest(t, "GET", "/privated_org/private_repo_on_private_org")
session.MakeRequest(t, req, http.StatusOK)
}
func TestOrgMembers(t *testing.T) {
defer tests.PrepareTestEnv(t)()
// not logged in user
req := NewRequest(t, "GET", "/org/org25/members")
MakeRequest(t, req, http.StatusOK)
// org member
session := loginUser(t, "user24")
req = NewRequest(t, "GET", "/org/org25/members")
session.MakeRequest(t, req, http.StatusOK)
// site admin
session = loginUser(t, "user1")
req = NewRequest(t, "GET", "/org/org25/members")
session.MakeRequest(t, req, http.StatusOK)
}
func TestOrgRestrictedUser(t *testing.T) {
defer tests.PrepareTestEnv(t)()
// privated_org is a private org who has id 23
orgName := "privated_org"
// public_repo_on_private_org is a public repo on privated_org
repoName := "public_repo_on_private_org"
// user29 is a restricted user who is not a member of the organization
restrictedUser := "user29"
// #17003 reports a bug whereby adding a restricted user to a read-only team doesn't work
// assert restrictedUser cannot see the org or the public repo
restrictedSession := loginUser(t, restrictedUser)
req := NewRequest(t, "GET", fmt.Sprintf("/%s", orgName))
restrictedSession.MakeRequest(t, req, http.StatusNotFound)
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s", orgName, repoName))
restrictedSession.MakeRequest(t, req, http.StatusNotFound)
// Therefore create a read-only team
adminSession := loginUser(t, "user1")
Redesign Scoped Access Tokens (#24767) ## Changes - Adds the following high level access scopes, each with `read` and `write` levels: - `activitypub` - `admin` (hidden if user is not a site admin) - `misc` - `notification` - `organization` - `package` - `issue` - `repository` - `user` - Adds new middleware function `tokenRequiresScopes()` in addition to `reqToken()` - `tokenRequiresScopes()` is used for each high-level api section - _if_ a scoped token is present, checks that the required scope is included based on the section and HTTP method - `reqToken()` is used for individual routes - checks that required authentication is present (but does not check scope levels as this will already have been handled by `tokenRequiresScopes()` - Adds migration to convert old scoped access tokens to the new set of scopes - Updates the user interface for scope selection ### User interface example <img width="903" alt="Screen Shot 2023-05-31 at 1 56 55 PM" src="https://github.com/go-gitea/gitea/assets/23248839/654766ec-2143-4f59-9037-3b51600e32f3"> <img width="917" alt="Screen Shot 2023-05-31 at 1 56 43 PM" src="https://github.com/go-gitea/gitea/assets/23248839/1ad64081-012c-4a73-b393-66b30352654c"> ## tokenRequiresScopes Design Decision - `tokenRequiresScopes()` was added to more reliably cover api routes. For an incoming request, this function uses the given scope category (say `AccessTokenScopeCategoryOrganization`) and the HTTP method (say `DELETE`) and verifies that any scoped tokens in use include `delete:organization`. - `reqToken()` is used to enforce auth for individual routes that require it. If a scoped token is not present for a request, `tokenRequiresScopes()` will not return an error ## TODO - [x] Alphabetize scope categories - [x] Change 'public repos only' to a radio button (private vs public). Also expand this to organizations - [X] Disable token creation if no scopes selected. Alternatively, show warning - [x] `reqToken()` is missing from many `POST/DELETE` routes in the api. `tokenRequiresScopes()` only checks that a given token has the correct scope, `reqToken()` must be used to check that a token (or some other auth) is present. - _This should be addressed in this PR_ - [x] The migration should be reviewed very carefully in order to minimize access changes to existing user tokens. - _This should be addressed in this PR_ - [x] Link to api to swagger documentation, clarify what read/write/delete levels correspond to - [x] Review cases where more than one scope is needed as this directly deviates from the api definition. - _This should be addressed in this PR_ - For example: ```go m.Group("/users/{username}/orgs", func() { m.Get("", reqToken(), org.ListUserOrgs) m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions) }, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context_service.UserAssignmentAPI()) ``` ## Future improvements - [ ] Add required scopes to swagger documentation - [ ] Redesign `reqToken()` to be opt-out rather than opt-in - [ ] Subdivide scopes like `repository` - [ ] Once a token is created, if it has no scopes, we should display text instead of an empty bullet point - [ ] If the 'public repos only' option is selected, should read categories be selected by default Closes #24501 Closes #24799 Co-authored-by: Jonathan Tran <jon@allspice.io> Co-authored-by: Kyle D <kdumontnu@gmail.com> Co-authored-by: silverwind <me@silverwind.io>
2023-06-04 20:57:16 +02:00
token := getTokenForLoggedInUser(t, adminSession, auth_model.AccessTokenScopeWriteOrganization)
teamToCreate := &api.CreateTeamOption{
Name: "codereader",
Description: "Code Reader",
IncludesAllRepositories: true,
Permission: "read",
Units: []string{"repo.code"},
}
req = NewRequestWithJSON(t, "POST", fmt.Sprintf("/api/v1/orgs/%s/teams", orgName), teamToCreate).
AddTokenAuth(token)
var apiTeam api.Team
resp := adminSession.MakeRequest(t, req, http.StatusCreated)
DecodeJSON(t, resp, &apiTeam)
checkTeamResponse(t, "CreateTeam_codereader", &apiTeam, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
Team permission allow different unit has different permission (#17811) * Team permission allow different unit has different permission * Finish the interface and the logic * Fix lint * Fix translation * align center for table cell content * Fix fixture * merge * Fix test * Add deprecated * Improve code * Add tooltip * Fix swagger * Fix newline * Fix tests * Fix tests * Fix test * Fix test * Max permission of external wiki and issues should be read * Move team units with limited max level below units table * Update label and column names * Some improvements * Fix lint * Some improvements * Fix template variables * Add permission docs * improve doc * Fix fixture * Fix bug * Fix some bug * fix * gofumpt * Integration test for migration (#18124) integrations: basic test for Gitea {dump,restore}-repo This is a first step for integration testing of DumpRepository and RestoreRepository. It: runs a Gitea server, dumps a repo via DumpRepository to the filesystem, restores the repo via RestoreRepository from the filesystem, dumps the restored repository to the filesystem, compares the first and second dump and expects them to be identical The verification is trivial and the goal is to add more tests for each topic of the dump. Signed-off-by: Loïc Dachary <loic@dachary.org> * Team permission allow different unit has different permission * Finish the interface and the logic * Fix lint * Fix translation * align center for table cell content * Fix fixture * merge * Fix test * Add deprecated * Improve code * Add tooltip * Fix swagger * Fix newline * Fix tests * Fix tests * Fix test * Fix test * Max permission of external wiki and issues should be read * Move team units with limited max level below units table * Update label and column names * Some improvements * Fix lint * Some improvements * Fix template variables * Add permission docs * improve doc * Fix fixture * Fix bug * Fix some bug * Fix bug Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: Aravinth Manivannan <realaravinth@batsense.net>
2022-01-05 04:37:00 +01:00
teamToCreate.Permission, teamToCreate.Units, nil)
checkTeamBean(t, apiTeam.ID, teamToCreate.Name, teamToCreate.Description, teamToCreate.IncludesAllRepositories,
Team permission allow different unit has different permission (#17811) * Team permission allow different unit has different permission * Finish the interface and the logic * Fix lint * Fix translation * align center for table cell content * Fix fixture * merge * Fix test * Add deprecated * Improve code * Add tooltip * Fix swagger * Fix newline * Fix tests * Fix tests * Fix test * Fix test * Max permission of external wiki and issues should be read * Move team units with limited max level below units table * Update label and column names * Some improvements * Fix lint * Some improvements * Fix template variables * Add permission docs * improve doc * Fix fixture * Fix bug * Fix some bug * fix * gofumpt * Integration test for migration (#18124) integrations: basic test for Gitea {dump,restore}-repo This is a first step for integration testing of DumpRepository and RestoreRepository. It: runs a Gitea server, dumps a repo via DumpRepository to the filesystem, restores the repo via RestoreRepository from the filesystem, dumps the restored repository to the filesystem, compares the first and second dump and expects them to be identical The verification is trivial and the goal is to add more tests for each topic of the dump. Signed-off-by: Loïc Dachary <loic@dachary.org> * Team permission allow different unit has different permission * Finish the interface and the logic * Fix lint * Fix translation * align center for table cell content * Fix fixture * merge * Fix test * Add deprecated * Improve code * Add tooltip * Fix swagger * Fix newline * Fix tests * Fix tests * Fix test * Fix test * Max permission of external wiki and issues should be read * Move team units with limited max level below units table * Update label and column names * Some improvements * Fix lint * Some improvements * Fix template variables * Add permission docs * improve doc * Fix fixture * Fix bug * Fix some bug * Fix bug Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: 6543 <6543@obermui.de> Co-authored-by: Aravinth Manivannan <realaravinth@batsense.net>
2022-01-05 04:37:00 +01:00
teamToCreate.Permission, teamToCreate.Units, nil)
// teamID := apiTeam.ID
// Now we need to add the restricted user to the team
req = NewRequest(t, "PUT", fmt.Sprintf("/api/v1/teams/%d/members/%s", apiTeam.ID, restrictedUser)).
AddTokenAuth(token)
_ = adminSession.MakeRequest(t, req, http.StatusNoContent)
// Now we need to check if the restrictedUser can access the repo
req = NewRequest(t, "GET", fmt.Sprintf("/%s", orgName))
restrictedSession.MakeRequest(t, req, http.StatusOK)
req = NewRequest(t, "GET", fmt.Sprintf("/%s/%s", orgName, repoName))
restrictedSession.MakeRequest(t, req, http.StatusOK)
}
func TestTeamSearch(t *testing.T) {
defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 15})
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 17})
var results TeamSearchResults
session := loginUser(t, user.Name)
csrf := GetCSRF(t, session, "/"+org.Name)
req := NewRequestf(t, "GET", "/org/%s/teams/-/search?q=%s", org.Name, "_team")
req.Header.Add("X-Csrf-Token", csrf)
resp := session.MakeRequest(t, req, http.StatusOK)
DecodeJSON(t, resp, &results)
assert.NotEmpty(t, results.Data)
assert.Len(t, results.Data, 2)
assert.Equal(t, "review_team", results.Data[0].Name)
assert.Equal(t, "test_team", results.Data[1].Name)
// no access if not organization member
user5 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 5})
session = loginUser(t, user5.Name)
csrf = GetCSRF(t, session, "/"+org.Name)
req = NewRequestf(t, "GET", "/org/%s/teams/-/search?q=%s", org.Name, "team")
req.Header.Add("X-Csrf-Token", csrf)
session.MakeRequest(t, req, http.StatusNotFound)
}
func TestOrgDashboardLabels(t *testing.T) {
defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 4})
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3, Type: user_model.UserTypeOrganization})
session := loginUser(t, user.Name)
req := NewRequestf(t, "GET", "/org/%s/issues?labels=3,4", org.Name)
resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body)
labelFilterHref, ok := htmlDoc.Find(".list-header-sort a").Attr("href")
assert.True(t, ok)
assert.Contains(t, labelFilterHref, "labels=3%2c4")
// Exclude label
req = NewRequestf(t, "GET", "/org/%s/issues?labels=3,-4", org.Name)
resp = session.MakeRequest(t, req, http.StatusOK)
htmlDoc = NewHTMLParser(t, resp.Body)
labelFilterHref, ok = htmlDoc.Find(".list-header-sort a").Attr("href")
assert.True(t, ok)
assert.Contains(t, labelFilterHref, "labels=3%2c-4")
}
func TestOwnerTeamUnit(t *testing.T) {
defer tests.PrepareTestEnv(t)()
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
org := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 3, Type: user_model.UserTypeOrganization})
session := loginUser(t, user.Name)
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{TeamID: 1, Type: unit.TypeIssues, AccessMode: perm.AccessModeOwner})
req := NewRequestWithValues(t, "GET", fmt.Sprintf("/org/%s/teams/owners/edit", org.Name), map[string]string{
"_csrf": GetCSRF(t, session, fmt.Sprintf("/org/%s/teams/owners/edit", org.Name)),
"team_name": "Owners",
"Description": "Just a description",
})
session.MakeRequest(t, req, http.StatusOK)
unittest.AssertExistsAndLoadBean(t, &organization.TeamUnit{TeamID: 1, Type: unit.TypeIssues, AccessMode: perm.AccessModeOwner})
}