2019-04-24 21:20:22 +03:00
// Copyright 2019 The Gitea Authors. All rights reserved.
2022-11-27 21:20:29 +03:00
// SPDX-License-Identifier: MIT
2019-04-24 21:20:22 +03:00
2022-09-02 22:18:23 +03:00
package integration
2019-04-24 21:20:22 +03:00
import (
2021-10-28 05:54:40 +03:00
"fmt"
2019-04-24 21:20:22 +03:00
"net/http"
"strings"
"testing"
2023-01-18 00:46:03 +03:00
auth_model "code.gitea.io/gitea/models/auth"
2022-04-07 21:59:56 +03:00
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
2021-10-28 05:54:40 +03:00
api "code.gitea.io/gitea/modules/structs"
2022-09-02 22:18:23 +03:00
"code.gitea.io/gitea/tests"
2021-11-17 15:34:35 +03:00
2019-04-24 21:20:22 +03:00
"github.com/stretchr/testify/assert"
)
func TestOrgRepos ( t * testing . T ) {
2022-09-02 22:18:23 +03:00
defer tests . PrepareTestEnv ( t ) ( )
2019-04-24 21:20:22 +03:00
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 {
2023-09-14 05:59:53 +03:00
req := NewRequest ( t , "GET" , "/org3?sort=" + sortBy )
2019-04-24 21:20:22 +03:00
resp := session . MakeRequest ( t , req , http . StatusOK )
htmlDoc := NewHTMLParser ( t , resp . Body )
sel := htmlDoc . doc . Find ( "a.name" )
2021-06-07 08:27:09 +03:00
assert . Len ( t , repos , len ( sel . Nodes ) )
2019-04-24 21:20:22 +03:00
for i := 0 ; i < len ( repos ) ; i ++ {
assert . EqualValues ( t , repos [ i ] , strings . TrimSpace ( sel . Eq ( i ) . Text ( ) ) )
}
}
} )
}
}
2019-04-25 21:59:10 +03:00
func TestLimitedOrg ( t * testing . T ) {
2022-09-02 22:18:23 +03:00
defer tests . PrepareTestEnv ( t ) ( )
2019-04-25 21:59:10 +03:00
// 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 ) {
2022-09-02 22:18:23 +03:00
defer tests . PrepareTestEnv ( t ) ( )
2019-04-25 21:59:10 +03:00
// 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 )
2019-05-16 18:48:40 +03:00
// 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 )
2019-04-25 21:59:10 +03:00
// 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 )
}
2021-10-28 05:54:40 +03:00
2022-07-26 19:13:24 +03:00
func TestOrgMembers ( t * testing . T ) {
2022-09-02 22:18:23 +03:00
defer tests . PrepareTestEnv ( t ) ( )
2022-07-26 19:13:24 +03:00
// 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 )
}
2021-10-28 05:54:40 +03:00
func TestOrgRestrictedUser ( t * testing . T ) {
2022-09-02 22:18:23 +03:00
defer tests . PrepareTestEnv ( t ) ( )
2021-10-28 05:54:40 +03:00
// 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 21:57:16 +03:00
token := getTokenForLoggedInUser ( t , adminSession , auth_model . AccessTokenScopeWriteOrganization )
2021-10-28 05:54:40 +03:00
teamToCreate := & api . CreateTeamOption {
Name : "codereader" ,
Description : "Code Reader" ,
IncludesAllRepositories : true ,
Permission : "read" ,
Units : [ ] string { "repo.code" } ,
}
2023-12-22 02:59:59 +03:00
req = NewRequestWithJSON ( t , "POST" , fmt . Sprintf ( "/api/v1/orgs/%s/teams" , orgName ) , teamToCreate ) .
AddTokenAuth ( token )
2021-10-28 05:54:40 +03:00
var apiTeam api . Team
resp := adminSession . MakeRequest ( t , req , http . StatusCreated )
DecodeJSON ( t , resp , & apiTeam )
2022-05-13 20:27:58 +03:00
checkTeamResponse ( t , "CreateTeam_codereader" , & apiTeam , teamToCreate . Name , teamToCreate . Description , teamToCreate . IncludesAllRepositories ,
2022-01-05 06:37:00 +03:00
teamToCreate . Permission , teamToCreate . Units , nil )
2021-10-28 05:54:40 +03:00
checkTeamBean ( t , apiTeam . ID , teamToCreate . Name , teamToCreate . Description , teamToCreate . IncludesAllRepositories ,
2022-01-05 06:37:00 +03:00
teamToCreate . Permission , teamToCreate . Units , nil )
// teamID := apiTeam.ID
2021-10-28 05:54:40 +03:00
// Now we need to add the restricted user to the team
2023-12-22 02:59:59 +03:00
req = NewRequest ( t , "PUT" , fmt . Sprintf ( "/api/v1/teams/%d/members/%s" , apiTeam . ID , restrictedUser ) ) .
AddTokenAuth ( token )
2021-10-28 05:54:40 +03:00
_ = 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 )
}
2022-04-07 21:59:56 +03:00
func TestTeamSearch ( t * testing . T ) {
2022-09-02 22:18:23 +03:00
defer tests . PrepareTestEnv ( t ) ( )
2022-04-07 21:59:56 +03:00
2022-08-21 19:24:05 +03:00
user := unittest . AssertExistsAndLoadBean ( t , & user_model . User { ID : 15 } )
org := unittest . AssertExistsAndLoadBean ( t , & user_model . User { ID : 17 } )
2022-04-07 21:59:56 +03:00
var results TeamSearchResults
session := loginUser ( t , user . Name )
req := NewRequestf ( t , "GET" , "/org/%s/teams/-/search?q=%s" , org . Name , "_team" )
resp := session . MakeRequest ( t , req , http . StatusOK )
DecodeJSON ( t , resp , & results )
assert . NotEmpty ( t , results . Data )
2022-08-21 19:24:05 +03:00
assert . Len ( t , results . Data , 2 )
assert . Equal ( t , "review_team" , results . Data [ 0 ] . Name )
assert . Equal ( t , "test_team" , results . Data [ 1 ] . Name )
2022-04-07 21:59:56 +03:00
// no access if not organization member
2022-08-16 05:22:25 +03:00
user5 := unittest . AssertExistsAndLoadBean ( t , & user_model . User { ID : 5 } )
2022-04-07 21:59:56 +03:00
session = loginUser ( t , user5 . Name )
req = NewRequestf ( t , "GET" , "/org/%s/teams/-/search?q=%s" , org . Name , "team" )
session . MakeRequest ( t , req , http . StatusNotFound )
}