mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-29 10:57:44 +09:00 
			
		
		
		
	Fix bug when a token is given public only (#32204)
This commit is contained in:
		| @@ -408,6 +408,10 @@ func (u *User) IsIndividual() bool { | ||||
| 	return u.Type == UserTypeIndividual | ||||
| } | ||||
|  | ||||
| func (u *User) IsUser() bool { | ||||
| 	return u.Type == UserTypeIndividual || u.Type == UserTypeBot | ||||
| } | ||||
|  | ||||
| // IsBot returns whether or not the user is of type bot | ||||
| func (u *User) IsBot() bool { | ||||
| 	return u.Type == UserTypeBot | ||||
|   | ||||
| @@ -63,6 +63,20 @@ func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.Context) { | ||||
| 					ctx.Error(http.StatusUnauthorized, "reqPackageAccess", "user should have specific permission or be a site admin") | ||||
| 					return | ||||
| 				} | ||||
|  | ||||
| 				// check if scope only applies to public resources | ||||
| 				publicOnly, err := scope.PublicOnly() | ||||
| 				if err != nil { | ||||
| 					ctx.Error(http.StatusForbidden, "tokenRequiresScope", "parsing public resource scope failed: "+err.Error()) | ||||
| 					return | ||||
| 				} | ||||
|  | ||||
| 				if publicOnly { | ||||
| 					if ctx.Package != nil && ctx.Package.Owner.Visibility.IsPrivate() { | ||||
| 						ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public packages") | ||||
| 						return | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
|   | ||||
| @@ -235,6 +235,62 @@ func reqPackageAccess(accessMode perm.AccessMode) func(ctx *context.APIContext) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func checkTokenPublicOnly() func(ctx *context.APIContext) { | ||||
| 	return func(ctx *context.APIContext) { | ||||
| 		if !ctx.PublicOnly { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		requiredScopeCategories, ok := ctx.Data["requiredScopeCategories"].([]auth_model.AccessTokenScopeCategory) | ||||
| 		if !ok || len(requiredScopeCategories) == 0 { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// public Only permission check | ||||
| 		switch { | ||||
| 		case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryRepository): | ||||
| 			if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate { | ||||
| 				ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public repos") | ||||
| 				return | ||||
| 			} | ||||
| 		case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryIssue): | ||||
| 			if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate { | ||||
| 				ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public issues") | ||||
| 				return | ||||
| 			} | ||||
| 		case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryOrganization): | ||||
| 			if ctx.Org.Organization != nil && ctx.Org.Organization.Visibility != api.VisibleTypePublic { | ||||
| 				ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs") | ||||
| 				return | ||||
| 			} | ||||
| 			if ctx.ContextUser != nil && ctx.ContextUser.IsOrganization() && ctx.ContextUser.Visibility != api.VisibleTypePublic { | ||||
| 				ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs") | ||||
| 				return | ||||
| 			} | ||||
| 		case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryUser): | ||||
| 			if ctx.ContextUser != nil && ctx.ContextUser.IsUser() && ctx.ContextUser.Visibility != api.VisibleTypePublic { | ||||
| 				ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public users") | ||||
| 				return | ||||
| 			} | ||||
| 		case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryActivityPub): | ||||
| 			if ctx.ContextUser != nil && ctx.ContextUser.IsUser() && ctx.ContextUser.Visibility != api.VisibleTypePublic { | ||||
| 				ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public activitypub") | ||||
| 				return | ||||
| 			} | ||||
| 		case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryNotification): | ||||
| 			if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate { | ||||
| 				ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public notifications") | ||||
| 				return | ||||
| 			} | ||||
| 		case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryPackage): | ||||
| 			if ctx.Package != nil && ctx.Package.Owner.Visibility.IsPrivate() { | ||||
| 				ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public packages") | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // if a token is being used for auth, we check that it contains the required scope | ||||
| // if a token is not being used, reqToken will enforce other sign in methods | ||||
| func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeCategory) func(ctx *context.APIContext) { | ||||
| @@ -250,9 +306,6 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		ctx.Data["ApiTokenScopePublicRepoOnly"] = false | ||||
| 		ctx.Data["ApiTokenScopePublicOrgOnly"] = false | ||||
|  | ||||
| 		// use the http method to determine the access level | ||||
| 		requiredScopeLevel := auth_model.Read | ||||
| 		if ctx.Req.Method == "POST" || ctx.Req.Method == "PUT" || ctx.Req.Method == "PATCH" || ctx.Req.Method == "DELETE" { | ||||
| @@ -261,6 +314,18 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC | ||||
|  | ||||
| 		// get the required scope for the given access level and category | ||||
| 		requiredScopes := auth_model.GetRequiredScopes(requiredScopeLevel, requiredScopeCategories...) | ||||
| 		allow, err := scope.HasScope(requiredScopes...) | ||||
| 		if err != nil { | ||||
| 			ctx.Error(http.StatusForbidden, "tokenRequiresScope", "checking scope failed: "+err.Error()) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		if !allow { | ||||
| 			ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s): %v", requiredScopes)) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		ctx.Data["requiredScopeCategories"] = requiredScopeCategories | ||||
|  | ||||
| 		// check if scope only applies to public resources | ||||
| 		publicOnly, err := scope.PublicOnly() | ||||
| @@ -269,21 +334,8 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// this context is used by the middleware in the specific route | ||||
| 		ctx.Data["ApiTokenScopePublicRepoOnly"] = publicOnly && auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryRepository) | ||||
| 		ctx.Data["ApiTokenScopePublicOrgOnly"] = publicOnly && auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryOrganization) | ||||
|  | ||||
| 		allow, err := scope.HasScope(requiredScopes...) | ||||
| 		if err != nil { | ||||
| 			ctx.Error(http.StatusForbidden, "tokenRequiresScope", "checking scope failed: "+err.Error()) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		if allow { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s): %v", requiredScopes)) | ||||
| 		// assign to true so that those searching should only filter public repositories/users/organizations | ||||
| 		ctx.PublicOnly = publicOnly | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -295,25 +347,6 @@ func reqToken() func(ctx *context.APIContext) { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		if true == ctx.Data["IsApiToken"] { | ||||
| 			publicRepo, pubRepoExists := ctx.Data["ApiTokenScopePublicRepoOnly"] | ||||
| 			publicOrg, pubOrgExists := ctx.Data["ApiTokenScopePublicOrgOnly"] | ||||
|  | ||||
| 			if pubRepoExists && publicRepo.(bool) && | ||||
| 				ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate { | ||||
| 				ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public repos") | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			if pubOrgExists && publicOrg.(bool) && | ||||
| 				ctx.Org.Organization != nil && ctx.Org.Organization.Visibility != api.VisibleTypePublic { | ||||
| 				ctx.Error(http.StatusForbidden, "reqToken", "token scope is limited to public orgs") | ||||
| 				return | ||||
| 			} | ||||
|  | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		if ctx.IsSigned { | ||||
| 			return | ||||
| 		} | ||||
| @@ -879,11 +912,11 @@ func Routes() *web.Router { | ||||
| 				m.Group("/user/{username}", func() { | ||||
| 					m.Get("", activitypub.Person) | ||||
| 					m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox) | ||||
| 				}, context.UserAssignmentAPI()) | ||||
| 				}, context.UserAssignmentAPI(), checkTokenPublicOnly()) | ||||
| 				m.Group("/user-id/{user-id}", func() { | ||||
| 					m.Get("", activitypub.Person) | ||||
| 					m.Post("/inbox", activitypub.ReqHTTPSignature(), activitypub.PersonInbox) | ||||
| 				}, context.UserIDAssignmentAPI()) | ||||
| 				}, context.UserIDAssignmentAPI(), checkTokenPublicOnly()) | ||||
| 			}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryActivityPub)) | ||||
| 		} | ||||
|  | ||||
| @@ -939,7 +972,7 @@ func Routes() *web.Router { | ||||
| 				}, reqSelfOrAdmin(), reqBasicOrRevProxyAuth()) | ||||
|  | ||||
| 				m.Get("/activities/feeds", user.ListUserActivityFeeds) | ||||
| 			}, context.UserAssignmentAPI(), individualPermsChecker) | ||||
| 			}, context.UserAssignmentAPI(), checkTokenPublicOnly(), individualPermsChecker) | ||||
| 		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser)) | ||||
|  | ||||
| 		// Users (requires user scope) | ||||
| @@ -957,7 +990,7 @@ func Routes() *web.Router { | ||||
| 				m.Get("/starred", user.GetStarredRepos) | ||||
|  | ||||
| 				m.Get("/subscriptions", user.GetWatchedRepos) | ||||
| 			}, context.UserAssignmentAPI()) | ||||
| 			}, context.UserAssignmentAPI(), checkTokenPublicOnly()) | ||||
| 		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken()) | ||||
|  | ||||
| 		// Users (requires user scope) | ||||
| @@ -1044,7 +1077,7 @@ func Routes() *web.Router { | ||||
| 					m.Get("", user.IsStarring) | ||||
| 					m.Put("", user.Star) | ||||
| 					m.Delete("", user.Unstar) | ||||
| 				}, repoAssignment()) | ||||
| 				}, repoAssignment(), checkTokenPublicOnly()) | ||||
| 			}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)) | ||||
| 			m.Get("/times", repo.ListMyTrackedTimes) | ||||
| 			m.Get("/stopwatches", repo.GetStopwatches) | ||||
| @@ -1069,18 +1102,20 @@ func Routes() *web.Router { | ||||
| 					m.Get("", user.CheckUserBlock) | ||||
| 					m.Put("", user.BlockUser) | ||||
| 					m.Delete("", user.UnblockUser) | ||||
| 				}, context.UserAssignmentAPI()) | ||||
| 				}, context.UserAssignmentAPI(), checkTokenPublicOnly()) | ||||
| 			}) | ||||
| 		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken()) | ||||
|  | ||||
| 		// Repositories (requires repo scope, org scope) | ||||
| 		m.Post("/org/{org}/repos", | ||||
| 			// FIXME: we need org in context | ||||
| 			tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization, auth_model.AccessTokenScopeCategoryRepository), | ||||
| 			reqToken(), | ||||
| 			bind(api.CreateRepoOption{}), | ||||
| 			repo.CreateOrgRepoDeprecated) | ||||
|  | ||||
| 		// requires repo scope | ||||
| 		// FIXME: Don't expose repository id outside of the system | ||||
| 		m.Combo("/repositories/{id}", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).Get(repo.GetByID) | ||||
|  | ||||
| 		// Repos (requires repo scope) | ||||
| @@ -1334,7 +1369,7 @@ func Routes() *web.Router { | ||||
| 					m.Post("", bind(api.UpdateRepoAvatarOption{}), repo.UpdateAvatar) | ||||
| 					m.Delete("", repo.DeleteAvatar) | ||||
| 				}, reqAdmin(), reqToken()) | ||||
| 			}, repoAssignment()) | ||||
| 			}, repoAssignment(), checkTokenPublicOnly()) | ||||
| 		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)) | ||||
|  | ||||
| 		// Notifications (requires notifications scope) | ||||
| @@ -1343,7 +1378,7 @@ func Routes() *web.Router { | ||||
| 				m.Combo("/notifications", reqToken()). | ||||
| 					Get(notify.ListRepoNotifications). | ||||
| 					Put(notify.ReadRepoNotifications) | ||||
| 			}, repoAssignment()) | ||||
| 			}, repoAssignment(), checkTokenPublicOnly()) | ||||
| 		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification)) | ||||
|  | ||||
| 		// Issue (requires issue scope) | ||||
| @@ -1457,7 +1492,7 @@ func Routes() *web.Router { | ||||
| 						Patch(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), bind(api.EditMilestoneOption{}), repo.EditMilestone). | ||||
| 						Delete(reqToken(), reqRepoWriter(unit.TypeIssues, unit.TypePullRequests), repo.DeleteMilestone) | ||||
| 				}) | ||||
| 			}, repoAssignment()) | ||||
| 			}, repoAssignment(), checkTokenPublicOnly()) | ||||
| 		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryIssue)) | ||||
|  | ||||
| 		// NOTE: these are Gitea package management API - see packages.CommonRoutes and packages.DockerContainerRoutes for endpoints that implement package manager APIs | ||||
| @@ -1468,14 +1503,14 @@ func Routes() *web.Router { | ||||
| 				m.Get("/files", reqToken(), packages.ListPackageFiles) | ||||
| 			}) | ||||
| 			m.Get("/", reqToken(), packages.ListPackages) | ||||
| 		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead)) | ||||
| 		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead), checkTokenPublicOnly()) | ||||
|  | ||||
| 		// Organizations | ||||
| 		m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs) | ||||
| 		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.UserAssignmentAPI()) | ||||
| 		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), context.UserAssignmentAPI(), checkTokenPublicOnly()) | ||||
| 		m.Post("/orgs", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), reqToken(), bind(api.CreateOrgOption{}), org.Create) | ||||
| 		m.Get("/orgs", org.GetAll, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization)) | ||||
| 		m.Group("/orgs/{org}", func() { | ||||
| @@ -1533,7 +1568,7 @@ func Routes() *web.Router { | ||||
| 					m.Delete("", org.UnblockUser) | ||||
| 				}) | ||||
| 			}, reqToken(), reqOrgOwnership()) | ||||
| 		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true)) | ||||
| 		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(true), checkTokenPublicOnly()) | ||||
| 		m.Group("/teams/{teamid}", func() { | ||||
| 			m.Combo("").Get(reqToken(), org.GetTeam). | ||||
| 				Patch(reqToken(), reqOrgOwnership(), bind(api.EditTeamOption{}), org.EditTeam). | ||||
| @@ -1553,7 +1588,7 @@ func Routes() *web.Router { | ||||
| 					Get(reqToken(), org.GetTeamRepo) | ||||
| 			}) | ||||
| 			m.Get("/activities/feeds", org.ListTeamActivityFeeds) | ||||
| 		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), reqTeamMembership()) | ||||
| 		}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryOrganization), orgAssignment(false, true), reqToken(), reqTeamMembership(), checkTokenPublicOnly()) | ||||
|  | ||||
| 		m.Group("/admin", func() { | ||||
| 			m.Group("/cron", func() { | ||||
|   | ||||
| @@ -191,7 +191,7 @@ func GetAll(ctx *context.APIContext) { | ||||
| 	//     "$ref": "#/responses/OrganizationList" | ||||
|  | ||||
| 	vMode := []api.VisibleType{api.VisibleTypePublic} | ||||
| 	if ctx.IsSigned { | ||||
| 	if ctx.IsSigned && !ctx.PublicOnly { | ||||
| 		vMode = append(vMode, api.VisibleTypeLimited) | ||||
| 		if ctx.Doer.IsAdmin { | ||||
| 			vMode = append(vMode, api.VisibleTypePrivate) | ||||
|   | ||||
| @@ -149,7 +149,7 @@ func SearchIssues(ctx *context.APIContext) { | ||||
| 			Actor:   ctx.Doer, | ||||
| 		} | ||||
| 		if ctx.IsSigned { | ||||
| 			opts.Private = true | ||||
| 			opts.Private = !ctx.PublicOnly | ||||
| 			opts.AllLimited = true | ||||
| 		} | ||||
| 		if ctx.FormString("owner") != "" { | ||||
|   | ||||
| @@ -129,6 +129,11 @@ func Search(ctx *context.APIContext) { | ||||
| 	//   "422": | ||||
| 	//     "$ref": "#/responses/validationError" | ||||
|  | ||||
| 	private := ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private")) | ||||
| 	if ctx.PublicOnly { | ||||
| 		private = false | ||||
| 	} | ||||
|  | ||||
| 	opts := &repo_model.SearchRepoOptions{ | ||||
| 		ListOptions:        utils.GetListOptions(ctx), | ||||
| 		Actor:              ctx.Doer, | ||||
| @@ -138,7 +143,7 @@ func Search(ctx *context.APIContext) { | ||||
| 		TeamID:             ctx.FormInt64("team_id"), | ||||
| 		TopicOnly:          ctx.FormBool("topic"), | ||||
| 		Collaborate:        optional.None[bool](), | ||||
| 		Private:            ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private")), | ||||
| 		Private:            private, | ||||
| 		Template:           optional.None[bool](), | ||||
| 		StarredByID:        ctx.FormInt64("starredBy"), | ||||
| 		IncludeDescription: ctx.FormBool("includeDesc"), | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import ( | ||||
|  | ||||
| 	activities_model "code.gitea.io/gitea/models/activities" | ||||
| 	user_model "code.gitea.io/gitea/models/user" | ||||
| 	"code.gitea.io/gitea/modules/structs" | ||||
| 	"code.gitea.io/gitea/routers/api/v1/utils" | ||||
| 	"code.gitea.io/gitea/services/context" | ||||
| 	"code.gitea.io/gitea/services/convert" | ||||
| @@ -67,12 +68,17 @@ func Search(ctx *context.APIContext) { | ||||
| 		maxResults = 1 | ||||
| 		users = []*user_model.User{user_model.NewActionsUser()} | ||||
| 	default: | ||||
| 		var visible []structs.VisibleType | ||||
| 		if ctx.PublicOnly { | ||||
| 			visible = []structs.VisibleType{structs.VisibleTypePublic} | ||||
| 		} | ||||
| 		users, maxResults, err = user_model.SearchUsers(ctx, &user_model.SearchUserOptions{ | ||||
| 			Actor:         ctx.Doer, | ||||
| 			Keyword:       ctx.FormTrim("q"), | ||||
| 			UID:           uid, | ||||
| 			Type:          user_model.UserTypeIndividual, | ||||
| 			SearchByEmail: true, | ||||
| 			Visible:       visible, | ||||
| 			ListOptions:   listOptions, | ||||
| 		}) | ||||
| 		if err != nil { | ||||
|   | ||||
| @@ -38,6 +38,7 @@ type APIContext struct { | ||||
| 	Repo       *Repository | ||||
| 	Org        *APIOrganization | ||||
| 	Package    *Package | ||||
| 	PublicOnly bool // Whether the request is for a public endpoint | ||||
| } | ||||
|  | ||||
| func init() { | ||||
|   | ||||
| @@ -75,6 +75,34 @@ func TestAPIListIssues(t *testing.T) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestAPIListIssuesPublicOnly(t *testing.T) { | ||||
| 	defer tests.PrepareTestEnv(t)() | ||||
|  | ||||
| 	repo1 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 1}) | ||||
| 	owner1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo1.OwnerID}) | ||||
|  | ||||
| 	session := loginUser(t, owner1.Name) | ||||
| 	token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) | ||||
| 	link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner1.Name, repo1.Name)) | ||||
| 	link.RawQuery = url.Values{"state": {"all"}}.Encode() | ||||
| 	req := NewRequest(t, "GET", link.String()).AddTokenAuth(token) | ||||
| 	MakeRequest(t, req, http.StatusOK) | ||||
|  | ||||
| 	repo2 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 2}) | ||||
| 	owner2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: repo2.OwnerID}) | ||||
|  | ||||
| 	session = loginUser(t, owner2.Name) | ||||
| 	token = getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue) | ||||
| 	link, _ = url.Parse(fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner2.Name, repo2.Name)) | ||||
| 	link.RawQuery = url.Values{"state": {"all"}}.Encode() | ||||
| 	req = NewRequest(t, "GET", link.String()).AddTokenAuth(token) | ||||
| 	MakeRequest(t, req, http.StatusOK) | ||||
|  | ||||
| 	publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopePublicOnly) | ||||
| 	req = NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken) | ||||
| 	MakeRequest(t, req, http.StatusForbidden) | ||||
| } | ||||
|  | ||||
| func TestAPICreateIssue(t *testing.T) { | ||||
| 	defer tests.PrepareTestEnv(t)() | ||||
| 	const body, title = "apiTestBody", "apiTestTitle" | ||||
| @@ -243,6 +271,12 @@ func TestAPISearchIssues(t *testing.T) { | ||||
| 	DecodeJSON(t, resp, &apiIssues) | ||||
| 	assert.Len(t, apiIssues, expectedIssueCount) | ||||
|  | ||||
| 	publicOnlyToken := getUserToken(t, "user1", auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopePublicOnly) | ||||
| 	req = NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken) | ||||
| 	resp = MakeRequest(t, req, http.StatusOK) | ||||
| 	DecodeJSON(t, resp, &apiIssues) | ||||
| 	assert.Len(t, apiIssues, 15) // 15 public issues | ||||
|  | ||||
| 	since := "2000-01-01T00:50:01+00:00" // 946687801 | ||||
| 	before := time.Unix(999307200, 0).Format(time.RFC3339) | ||||
| 	query.Add("since", since) | ||||
|   | ||||
| @@ -28,9 +28,13 @@ func TestAPIRepoBranchesPlain(t *testing.T) { | ||||
| 		repo3 := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 3}) | ||||
| 		user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) | ||||
| 		session := loginUser(t, user1.LowerName) | ||||
| 		token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) | ||||
|  | ||||
| 		// public only token should be forbidden | ||||
| 		publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopePublicOnly, auth_model.AccessTokenScopeWriteRepository) | ||||
| 		link, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches", repo3.Name)) // a plain repo | ||||
| 		MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden) | ||||
|  | ||||
| 		token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository) | ||||
| 		resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK) | ||||
| 		bs, err := io.ReadAll(resp.Body) | ||||
| 		assert.NoError(t, err) | ||||
| @@ -42,6 +46,8 @@ func TestAPIRepoBranchesPlain(t *testing.T) { | ||||
| 		assert.EqualValues(t, "master", branches[1].Name) | ||||
|  | ||||
| 		link2, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch", repo3.Name)) | ||||
| 		MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden) | ||||
|  | ||||
| 		resp = MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(token), http.StatusOK) | ||||
| 		bs, err = io.ReadAll(resp.Body) | ||||
| 		assert.NoError(t, err) | ||||
| @@ -49,6 +55,8 @@ func TestAPIRepoBranchesPlain(t *testing.T) { | ||||
| 		assert.NoError(t, json.Unmarshal(bs, &branch)) | ||||
| 		assert.EqualValues(t, "test_branch", branch.Name) | ||||
|  | ||||
| 		MakeRequest(t, NewRequest(t, "POST", link.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden) | ||||
|  | ||||
| 		req := NewRequest(t, "POST", link.String()).AddTokenAuth(token) | ||||
| 		req.Header.Add("Content-Type", "application/json") | ||||
| 		req.Body = io.NopCloser(bytes.NewBufferString(`{"new_branch_name":"test_branch2", "old_branch_name": "test_branch", "old_ref_name":"refs/heads/test_branch"}`)) | ||||
| @@ -73,6 +81,7 @@ func TestAPIRepoBranchesPlain(t *testing.T) { | ||||
|  | ||||
| 		link3, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch2", repo3.Name)) | ||||
| 		MakeRequest(t, NewRequest(t, "DELETE", link3.String()), http.StatusNotFound) | ||||
| 		MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden) | ||||
|  | ||||
| 		MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(token), http.StatusNoContent) | ||||
| 		assert.NoError(t, err) | ||||
|   | ||||
| @@ -38,6 +38,19 @@ func TestAPIUserSearchLoggedIn(t *testing.T) { | ||||
| 		assert.Contains(t, user.UserName, query) | ||||
| 		assert.NotEmpty(t, user.Email) | ||||
| 	} | ||||
|  | ||||
| 	publicToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopePublicOnly) | ||||
| 	req = NewRequestf(t, "GET", "/api/v1/users/search?q=%s", query). | ||||
| 		AddTokenAuth(publicToken) | ||||
| 	resp = MakeRequest(t, req, http.StatusOK) | ||||
| 	results = SearchResults{} | ||||
| 	DecodeJSON(t, resp, &results) | ||||
| 	assert.NotEmpty(t, results.Data) | ||||
| 	for _, user := range results.Data { | ||||
| 		assert.Contains(t, user.UserName, query) | ||||
| 		assert.NotEmpty(t, user.Email) | ||||
| 		assert.True(t, user.Visibility == "public") | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestAPIUserSearchNotLoggedIn(t *testing.T) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user