Compare commits
13 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 71d1ce75de | |||
| 87bff9c251 | |||
| 72c806fd6a | |||
| 1f1f5063f1 | |||
| 2c749ce548 | |||
| f540f57354 | |||
| 1c2d5e9b03 | |||
| a859221a62 | |||
| d37f7b44a9 | |||
| a34eac5ef4 | |||
| 6d2b02dac1 | |||
| 1b70a4451a | |||
| bc29cd0d3d |
@@ -0,0 +1,57 @@
|
|||||||
|
# AtayMakhzan
|
||||||
|
|
||||||
|
AtayMakhzan is Saad ibn Zoubayr's sovereign Moroccan developer forge.
|
||||||
|
|
||||||
|
This repository is an intentional source fork of upstream Gitea. The goal is not to create a cosmetic copy. The goal is to build a controlled forge system for Saad's ventures while preserving upstream security and maintainability discipline.
|
||||||
|
|
||||||
|
## Upstream basis
|
||||||
|
|
||||||
|
Initial fork basis:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Gitea v1.26.2
|
||||||
|
```
|
||||||
|
|
||||||
|
Upstream remote:
|
||||||
|
|
||||||
|
```text
|
||||||
|
https://github.com/go-gitea/gitea.git
|
||||||
|
```
|
||||||
|
|
||||||
|
AtayMakhzan origin:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ssh://git@ataymakhzan.com:2222/ibnezzoubayr/Atay-Makhzan.git
|
||||||
|
```
|
||||||
|
|
||||||
|
## Product doctrine
|
||||||
|
|
||||||
|
AtayMakhzan should be:
|
||||||
|
|
||||||
|
- sovereign: code, repos, and infra controlled by Saad;
|
||||||
|
- Moroccan: tasteful identity, not decorative folklore;
|
||||||
|
- developer-first: fast, calm, reliable, and sharp;
|
||||||
|
- operations-safe: backups and rollback before production changes;
|
||||||
|
- upstream-aware: security releases are merged deliberately.
|
||||||
|
|
||||||
|
## Fork discipline
|
||||||
|
|
||||||
|
- Preserve upstream MIT license and copyright notices.
|
||||||
|
- Track upstream releases and security patches.
|
||||||
|
- Document every divergence in `FORK_CHANGELOG.md`.
|
||||||
|
- Add tests for behavioral changes.
|
||||||
|
- Avoid schema/auth/storage changes until a migration and rollback plan exists.
|
||||||
|
- Keep production deploy logic in `Atay-Makhzan-Ops`, not in this source repo.
|
||||||
|
|
||||||
|
## First milestone
|
||||||
|
|
||||||
|
The first milestone is a buildable, deployable fork that behaves like Gitea 1.26.2 with safe AtayMakhzan identity changes only.
|
||||||
|
|
||||||
|
Non-goals for the first milestone:
|
||||||
|
|
||||||
|
- no database schema change;
|
||||||
|
- no authentication rewrite;
|
||||||
|
- no SSH/Git transport change;
|
||||||
|
- no permission model change;
|
||||||
|
- no broad UI rewrite;
|
||||||
|
- no production deployment until staging/smoke verification passes.
|
||||||
@@ -4,6 +4,68 @@ This changelog goes through the changes that have been made in each release
|
|||||||
without substantial changes to our git log; to see the highlights of what has
|
without substantial changes to our git log; to see the highlights of what has
|
||||||
been added to each release, please refer to the [blog](https://blog.gitea.com).
|
been added to each release, please refer to the [blog](https://blog.gitea.com).
|
||||||
|
|
||||||
|
## [1.26.2](https://github.com/go-gitea/gitea/releases/tag/1.26.2) - 2026-05-20
|
||||||
|
|
||||||
|
* SECURITY
|
||||||
|
* fix(permissions): Fix reading permission (#37769)
|
||||||
|
* fix(actions): make artifact signature payloads unambiguous (#37707)
|
||||||
|
* fix: Unify public-only token filtering in API queries and repo access checks (#37118)
|
||||||
|
* fix: Add missed token scope checking (#37735)
|
||||||
|
* fix(oauth): bind token exchanges to the original client request (#37704)
|
||||||
|
* fix(oauth): strengthen PKCE validation and refresh token replay protection (#37706)
|
||||||
|
* fix(web): enforce token scopes on raw, media, and attachment downloads (#37698)
|
||||||
|
* fix(security): enforce wiki git writes and LFS token access at request time (#37695)
|
||||||
|
* feat(api): encrypt AWS creds (#37679)
|
||||||
|
* fix(deps): update dependency mermaid to v11.15.0 [security], add e2e test
|
||||||
|
* fix(packages): Add label for private and internal package and fix composor package source permission check (#37610)
|
||||||
|
* fix(git): Fix smart http request scope bug (#37583)
|
||||||
|
* Fix basic auth bug (#37503)
|
||||||
|
* Fix allow maintainer edit permission check (#37479) (#37484)
|
||||||
|
* Fix URL sanitization to handle schemeless credentials (#37440) (#37471)
|
||||||
|
* Fix attachment Content-Security-Policy (#37455) (#37464)
|
||||||
|
* chore(deps): bump go-git/go-git/v5 to 5.19.0 (#37608)
|
||||||
|
|
||||||
|
* BUGFIXES
|
||||||
|
* fix(pull): handle empty pull request files view to allow reviews (#37783)
|
||||||
|
* fix(markup): make RenderString never fail (#37779)
|
||||||
|
* fix: add natural sort to sortTreeViewNodes (#37772)
|
||||||
|
* fix: package creation unique conflict (#37774)
|
||||||
|
* fix!: add DEFAULT_TITLE_SOURCE setting for pull request title default behavior (#37465)
|
||||||
|
* fix: Allow direct commits for unprotected files with push restrictions (#37657)
|
||||||
|
* fix(actions): wrong assumption that run id always >= job id (#37737)
|
||||||
|
* fix(auth): set User-Agent on avatar fetch and sync avatar on link-account register (#37564) (#37588)
|
||||||
|
* fix(actions): deadlock between PrepareRunAndInsert and UpdateTaskByState (#37692)
|
||||||
|
* fix(repo): /generate must sync the branch table for the new repo (#37693)
|
||||||
|
* build: Fix snap build (1.26)
|
||||||
|
* fix(actions): run TransferLogs on UpdateLog{Rows:[], NoMore:true} (#37631)
|
||||||
|
* fix show correct mergebase
|
||||||
|
* fix: make clone URL respect public URL detection setting (#37615)
|
||||||
|
* fix: "run as root" check (#37622)
|
||||||
|
* chore(deps): update dependency go to v1.26.3 (#37601)
|
||||||
|
* Compare dropdown fails when selecting branch with no common merge-base (#37470)
|
||||||
|
* fix: treat email addresses case-insensitively (#37600)
|
||||||
|
* fix(actions): fix blank lines after ::endgroup:: (#37597)
|
||||||
|
* fix(actions): report individual step status in workflow job API response (#37592)
|
||||||
|
* fix: Invalid UTF-8 commit messages in JSON API responses (#37542)
|
||||||
|
* fix: use consistent GetUser family functions (#37553)
|
||||||
|
* fix(api): return 409 message instead of empty JSON for wrong commit id (#37572)
|
||||||
|
* fix(actions): prevent panic when workflow contains null jobs (#37570)
|
||||||
|
* Make ServeSetHeaders default to download attachment if filename exists (#37552) (#37555)
|
||||||
|
* Fix(actions): validate workflow param to prevent 500 error (#37546) (#37554)
|
||||||
|
* Don't unblock run-level-concurrency-blocked runs in the resolver (#37461) (#37538)
|
||||||
|
* Fix(packages): use file names for generic web downloads (#37514) (#37520)
|
||||||
|
* Fix merge autodetect can't close other PRs but only the last one when multiple PRs are pushed at once (#37512) (#37516)
|
||||||
|
* Fix update branch protection order (#37508) (#37513)
|
||||||
|
* Fix mCaptcha broken after Vite migration (#37492) (#37509)
|
||||||
|
* Fix review submission from single-commit PR view (#37475) (#37485)
|
||||||
|
* Fix scheduled action panic with null event payload (#37459) (#37466)
|
||||||
|
* Make GetPossibleUserByID can handle deleted user (#37430) (#37431)
|
||||||
|
* Remove excessive quote from terraform instructions (#37424) (#37426)
|
||||||
|
* Fix color regressions, add `priority` color (#37417) (#37421)
|
||||||
|
|
||||||
|
* MISC
|
||||||
|
* Add CurrentURL template variable back (#37444) (#37449)
|
||||||
|
|
||||||
## [1.26.1](https://github.com/go-gitea/gitea/releases/tag/v1.26.1) - 2026-04-21
|
## [1.26.1](https://github.com/go-gitea/gitea/releases/tag/v1.26.1) - 2026-04-21
|
||||||
|
|
||||||
* BUGFIXES
|
* BUGFIXES
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# AtayMakhzan Fork Changelog
|
||||||
|
|
||||||
|
This file records every intentional divergence from upstream Gitea.
|
||||||
|
|
||||||
|
## Fork basis
|
||||||
|
|
||||||
|
| Item | Value |
|
||||||
|
|---|---|
|
||||||
|
| Upstream project | `go-gitea/gitea` |
|
||||||
|
| Initial tag | `v1.26.2` |
|
||||||
|
| Local branch | `atay/v1.26.2` |
|
||||||
|
| Origin | `ssh://git@ataymakhzan.com:2222/ibnezzoubayr/Atay-Makhzan.git` |
|
||||||
|
|
||||||
|
## 0.1.0-planning
|
||||||
|
|
||||||
|
- Created AtayMakhzan fork identity documentation.
|
||||||
|
- Created public source-fork remote at `ibnezzoubayr/Atay-Makhzan`.
|
||||||
|
- Pushed branch `atay/v1.26.2` as the default branch.
|
||||||
|
- No source-code behavior changes yet.
|
||||||
|
- No database, auth, Git transport, or permission changes.
|
||||||
|
|
||||||
|
Verification on 2026-06-20:
|
||||||
|
|
||||||
|
- Local Go toolchain: `go1.26.4 linux/amd64`.
|
||||||
|
- `make deps-backend` passed.
|
||||||
|
- `make deps-frontend` passed.
|
||||||
|
- `make build` passed.
|
||||||
|
- Built binary smoke: `./gitea --version` passed.
|
||||||
|
- `make test-frontend` passed: 35 files, 92 tests.
|
||||||
|
- Backend smoke tests passed: `go test ./modules/setting ./modules/util -count=1`.
|
||||||
|
|
||||||
|
## Divergence rules
|
||||||
|
|
||||||
|
Every future entry must include:
|
||||||
|
|
||||||
|
1. upstream base commit/tag;
|
||||||
|
2. files changed;
|
||||||
|
3. reason for divergence;
|
||||||
|
4. test/build verification;
|
||||||
|
5. production migration/rollback notes if deployment-relevant.
|
||||||
Generated
+3
-3
@@ -2,11 +2,11 @@
|
|||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1775036866,
|
"lastModified": 1782467914,
|
||||||
"narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=",
|
"narHash": "sha256-pGvFkM8N0xEkIIXDe5YYfbEAvHrk4IxBrjB/x8OomhE=",
|
||||||
"owner": "nixos",
|
"owner": "nixos",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "6201e203d09599479a3b3450ed24fa81537ebc4e",
|
"rev": "e73de5be04e0eff4190a1432b946d469c794e7b4",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|||||||
@@ -436,6 +436,12 @@ type GetFeedsOptions struct {
|
|||||||
DontCount bool // do counting in GetFeeds
|
DontCount bool // do counting in GetFeeds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (opts *GetFeedsOptions) ApplyPublicOnly(publicOnly bool) {
|
||||||
|
if publicOnly {
|
||||||
|
opts.IncludePrivate = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ActivityReadable return whether doer can read activities of user
|
// ActivityReadable return whether doer can read activities of user
|
||||||
func ActivityReadable(user, doer *user_model.User) bool {
|
func ActivityReadable(user, doer *user_model.User) bool {
|
||||||
return !user.KeepActivityPrivate ||
|
return !user.KeepActivityPrivate ||
|
||||||
|
|||||||
@@ -54,6 +54,12 @@ type FindOrgOptions struct {
|
|||||||
IncludeVisibility structs.VisibleType
|
IncludeVisibility structs.VisibleType
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (opts *FindOrgOptions) ApplyPublicOnly(publicOnly bool) {
|
||||||
|
if publicOnly {
|
||||||
|
opts.IncludeVisibility = structs.VisibleTypePublic
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder {
|
func queryUserOrgIDs(userID int64, includePrivate bool) *builder.Builder {
|
||||||
cond := builder.Eq{"uid": userID}
|
cond := builder.Eq{"uid": userID}
|
||||||
if !includePrivate {
|
if !includePrivate {
|
||||||
|
|||||||
@@ -212,6 +212,13 @@ type SearchRepoOptions struct {
|
|||||||
OnlyShowRelevant bool
|
OnlyShowRelevant bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (opts *SearchRepoOptions) ApplyPublicOnly(publicOnly bool) {
|
||||||
|
if publicOnly {
|
||||||
|
opts.Private = false
|
||||||
|
opts.AllLimited = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// UserOwnedRepoCond returns user ownered repositories
|
// UserOwnedRepoCond returns user ownered repositories
|
||||||
func UserOwnedRepoCond(userID int64) builder.Cond {
|
func UserOwnedRepoCond(userID int64) builder.Cond {
|
||||||
return builder.Eq{
|
return builder.Eq{
|
||||||
|
|||||||
@@ -24,6 +24,12 @@ type StarredReposOptions struct {
|
|||||||
IncludePrivate bool
|
IncludePrivate bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (opts *StarredReposOptions) ApplyPublicOnly(publicOnly bool) {
|
||||||
|
if publicOnly {
|
||||||
|
opts.IncludePrivate = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (opts *StarredReposOptions) ToConds() builder.Cond {
|
func (opts *StarredReposOptions) ToConds() builder.Cond {
|
||||||
var cond builder.Cond = builder.Eq{
|
var cond builder.Cond = builder.Eq{
|
||||||
"star.uid": opts.StarrerID,
|
"star.uid": opts.StarrerID,
|
||||||
@@ -62,6 +68,12 @@ type WatchedReposOptions struct {
|
|||||||
IncludePrivate bool
|
IncludePrivate bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (opts *WatchedReposOptions) ApplyPublicOnly(publicOnly bool) {
|
||||||
|
if publicOnly {
|
||||||
|
opts.IncludePrivate = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (opts *WatchedReposOptions) ToConds() builder.Cond {
|
func (opts *WatchedReposOptions) ToConds() builder.Cond {
|
||||||
var cond builder.Cond = builder.Eq{
|
var cond builder.Cond = builder.Eq{
|
||||||
"watch.user_id": opts.WatcherID,
|
"watch.user_id": opts.WatcherID,
|
||||||
|
|||||||
@@ -59,6 +59,12 @@ type SearchUserOptions struct {
|
|||||||
IncludeReserved bool
|
IncludeReserved bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (opts *SearchUserOptions) ApplyPublicOnly(publicOnly bool) {
|
||||||
|
if publicOnly {
|
||||||
|
opts.Visible = []structs.VisibleType{structs.VisibleTypePublic}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Session {
|
func (opts *SearchUserOptions) toSearchQueryBase(ctx context.Context) *xorm.Session {
|
||||||
var cond builder.Cond
|
var cond builder.Cond
|
||||||
cond = builder.In("type", opts.Types)
|
cond = builder.In("type", opts.Types)
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
package actions
|
package actions
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/hmac"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/binary"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -15,6 +19,22 @@ import (
|
|||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type tagType string
|
||||||
|
|
||||||
|
// BuildSignature builds a hmac signature for the input values.
|
||||||
|
// "tag" is an internal pre-defined static string to distinguish the signatures for different purpose.
|
||||||
|
func BuildSignature(tag tagType, vals ...string) []byte {
|
||||||
|
m := hmac.New(sha256.New, setting.GetGeneralTokenSigningSecret())
|
||||||
|
_, _ = io.WriteString(m, string(tag))
|
||||||
|
var buf8 [8]byte
|
||||||
|
for _, v := range vals {
|
||||||
|
binary.LittleEndian.PutUint64(buf8[:], uint64(len(v)))
|
||||||
|
_, _ = m.Write(buf8[:])
|
||||||
|
_, _ = io.WriteString(m, v)
|
||||||
|
}
|
||||||
|
return m.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
// IsArtifactV4 detects whether the artifact is likely from v4.
|
// IsArtifactV4 detects whether the artifact is likely from v4.
|
||||||
// V4 backend stores the files as a single combined zip file per artifact, and ensures ContentEncoding contains a slash
|
// V4 backend stores the files as a single combined zip file per artifact, and ensures ContentEncoding contains a slash
|
||||||
// (otherwise this uses application/zip instead of the custom mime type), which is not the case for the old backend.
|
// (otherwise this uses application/zip instead of the custom mime type), which is not the case for the old backend.
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package actions
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuildSignature(t *testing.T) {
|
||||||
|
a := BuildSignature("v0", "x")
|
||||||
|
b := BuildSignature("v0", "x")
|
||||||
|
assert.Equal(t, a, b)
|
||||||
|
|
||||||
|
a = BuildSignature("v0", "x", "yz")
|
||||||
|
b = BuildSignature("v0", "xy", "z")
|
||||||
|
assert.NotEqual(t, a, b)
|
||||||
|
|
||||||
|
a = BuildSignature("v1", "x")
|
||||||
|
b = BuildSignature("v2", "x")
|
||||||
|
assert.NotEqual(t, a, b)
|
||||||
|
|
||||||
|
a = BuildSignature("v0", "x")
|
||||||
|
b = BuildSignature("v0x")
|
||||||
|
assert.NotEqual(t, a, b)
|
||||||
|
|
||||||
|
a = BuildSignature("v0", "", "x")
|
||||||
|
b = BuildSignature("v0", "x", "")
|
||||||
|
assert.NotEqual(t, a, b)
|
||||||
|
|
||||||
|
a = BuildSignature("v0")
|
||||||
|
b = BuildSignature("v0")
|
||||||
|
assert.Equal(t, a, b)
|
||||||
|
}
|
||||||
@@ -270,7 +270,9 @@ func Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error
|
|||||||
func RenderString(ctx *markup.RenderContext, content string) (template.HTML, error) {
|
func RenderString(ctx *markup.RenderContext, content string) (template.HTML, error) {
|
||||||
var buf strings.Builder
|
var buf strings.Builder
|
||||||
if err := Render(ctx, strings.NewReader(content), &buf); err != nil {
|
if err := Render(ctx, strings.NewReader(content), &buf); err != nil {
|
||||||
return "", err
|
log.Warn("Unable to RenderString: %v, content: %s", err, giteautil.TruncateRunes(content, 200))
|
||||||
|
err = nil
|
||||||
|
return template.HTML(template.HTMLEscapeString(content)), err
|
||||||
}
|
}
|
||||||
return template.HTML(buf.String()), nil
|
return template.HTML(buf.String()), nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,13 +161,7 @@ func ArtifactsV4Routes(prefix string) *web.Router {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *artifactV4Routes) buildSignature(endpoint, expires, artifactName string, taskID, artifactID int64) []byte {
|
func (r *artifactV4Routes) buildSignature(endpoint, expires, artifactName string, taskID, artifactID int64) []byte {
|
||||||
mac := hmac.New(sha256.New, setting.GetGeneralTokenSigningSecret())
|
return actions.BuildSignature("v4", endpoint, expires, artifactName, strconv.FormatInt(taskID, 10), strconv.FormatInt(artifactID, 10))
|
||||||
mac.Write([]byte(endpoint))
|
|
||||||
mac.Write([]byte(expires))
|
|
||||||
mac.Write([]byte(artifactName))
|
|
||||||
_, _ = fmt.Fprint(mac, taskID)
|
|
||||||
_, _ = fmt.Fprint(mac, artifactID)
|
|
||||||
return mac.Sum(nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *artifactV4Routes) buildArtifactURL(ctx *ArtifactContext, endpoint, artifactName string, taskID, artifactID int64) string {
|
func (r *artifactV4Routes) buildArtifactURL(ctx *ArtifactContext, endpoint, artifactName string, taskID, artifactID int64) string {
|
||||||
|
|||||||
+84
-61
@@ -212,6 +212,11 @@ func repoAssignment() func(ctx *context.APIContext) {
|
|||||||
ctx.APIErrorNotFound()
|
ctx.APIErrorNotFound()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !ctx.TokenCanAccessRepo(repo) {
|
||||||
|
ctx.APIErrorNotFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,51 +254,66 @@ func checkTokenPublicOnly() func(ctx *context.APIContext) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// public Only permission check
|
for _, category := range requiredScopeCategories {
|
||||||
switch {
|
switch category {
|
||||||
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryRepository):
|
case auth_model.AccessTokenScopeCategoryRepository:
|
||||||
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
|
if !ctx.TokenCanAccessRepo(ctx.Repo.Repository) {
|
||||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public repos")
|
ctx.APIError(http.StatusForbidden, "token scope is limited to public repos")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryIssue):
|
case auth_model.AccessTokenScopeCategoryIssue:
|
||||||
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
|
if !ctx.TokenCanAccessRepo(ctx.Repo.Repository) {
|
||||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public issues")
|
ctx.APIError(http.StatusForbidden, "token scope is limited to public issues")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryOrganization):
|
case auth_model.AccessTokenScopeCategoryOrganization:
|
||||||
if ctx.Org.Organization != nil && ctx.Org.Organization.Visibility != api.VisibleTypePublic {
|
orgPrivate := ctx.Org.Organization != nil && !ctx.Org.Organization.Visibility.IsPublic()
|
||||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public orgs")
|
userOrgPrivate := ctx.ContextUser != nil && ctx.ContextUser.IsOrganization() && !ctx.ContextUser.Visibility.IsPublic()
|
||||||
return
|
if orgPrivate || userOrgPrivate {
|
||||||
}
|
ctx.APIError(http.StatusForbidden, "token scope is limited to public orgs")
|
||||||
if ctx.ContextUser != nil && ctx.ContextUser.IsOrganization() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
|
return
|
||||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public orgs")
|
}
|
||||||
return
|
case auth_model.AccessTokenScopeCategoryUser:
|
||||||
}
|
if ctx.ContextUser != nil && ctx.ContextUser.IsTokenAccessAllowed() && !ctx.ContextUser.Visibility.IsPublic() {
|
||||||
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryUser):
|
ctx.APIError(http.StatusForbidden, "token scope is limited to public users")
|
||||||
if ctx.ContextUser != nil && ctx.ContextUser.IsTokenAccessAllowed() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
|
return
|
||||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public users")
|
}
|
||||||
return
|
case auth_model.AccessTokenScopeCategoryActivityPub:
|
||||||
}
|
if ctx.ContextUser != nil && ctx.ContextUser.IsTokenAccessAllowed() && !ctx.ContextUser.Visibility.IsPublic() {
|
||||||
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryActivityPub):
|
ctx.APIError(http.StatusForbidden, "token scope is limited to public activitypub")
|
||||||
if ctx.ContextUser != nil && ctx.ContextUser.IsTokenAccessAllowed() && ctx.ContextUser.Visibility != api.VisibleTypePublic {
|
return
|
||||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public activitypub")
|
}
|
||||||
return
|
case auth_model.AccessTokenScopeCategoryNotification:
|
||||||
}
|
if !ctx.TokenCanAccessRepo(ctx.Repo.Repository) {
|
||||||
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryNotification):
|
ctx.APIError(http.StatusForbidden, "token scope is limited to public notifications")
|
||||||
if ctx.Repo.Repository != nil && ctx.Repo.Repository.IsPrivate {
|
return
|
||||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public notifications")
|
}
|
||||||
return
|
case auth_model.AccessTokenScopeCategoryPackage:
|
||||||
}
|
if ctx.Package != nil && ctx.Package.Owner.Visibility.IsPrivate() {
|
||||||
case auth_model.ContainsCategory(requiredScopeCategories, auth_model.AccessTokenScopeCategoryPackage):
|
ctx.APIError(http.StatusForbidden, "token scope is limited to public packages")
|
||||||
if ctx.Package != nil && ctx.Package.Owner.Visibility.IsPrivate() {
|
return
|
||||||
ctx.APIError(http.StatusForbidden, "token scope is limited to public packages")
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rejectPublicOnly() func(ctx *context.APIContext) {
|
||||||
|
return func(ctx *context.APIContext) {
|
||||||
|
if !ctx.PublicOnly {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.APIError(http.StatusForbidden, "this endpoint is not available for public-only tokens")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func contextAuthenticatedUser() func(ctx *context.APIContext) {
|
||||||
|
return func(ctx *context.APIContext) {
|
||||||
|
ctx.ContextUser = ctx.Doer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if a token is being used for auth, we check that it contains the required scope
|
// 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
|
// if a token is not being used, reqToken will enforce other sign in methods
|
||||||
func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeCategory) func(ctx *context.APIContext) {
|
func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeCategory) func(ctx *context.APIContext) {
|
||||||
@@ -958,6 +978,8 @@ func Routes() *web.Router {
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Notifications (requires 'notifications' scope)
|
// Notifications (requires 'notifications' scope)
|
||||||
|
// The notifications API is not available for public-only tokens because a user's notifications mix
|
||||||
|
// public and private repository events in the same mailbox.
|
||||||
m.Group("/notifications", func() {
|
m.Group("/notifications", func() {
|
||||||
m.Combo("").
|
m.Combo("").
|
||||||
Get(reqToken(), notify.ListNotifications).
|
Get(reqToken(), notify.ListNotifications).
|
||||||
@@ -966,7 +988,7 @@ func Routes() *web.Router {
|
|||||||
m.Combo("/threads/{id}").
|
m.Combo("/threads/{id}").
|
||||||
Get(reqToken(), notify.GetThread).
|
Get(reqToken(), notify.GetThread).
|
||||||
Patch(reqToken(), notify.ReadThread)
|
Patch(reqToken(), notify.ReadThread)
|
||||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification))
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryNotification), rejectPublicOnly())
|
||||||
|
|
||||||
// Users (requires user scope)
|
// Users (requires user scope)
|
||||||
m.Group("/users", func() {
|
m.Group("/users", func() {
|
||||||
@@ -1014,8 +1036,9 @@ func Routes() *web.Router {
|
|||||||
m.Group("/settings", func() {
|
m.Group("/settings", func() {
|
||||||
m.Get("", user.GetUserSettings)
|
m.Get("", user.GetUserSettings)
|
||||||
m.Patch("", bind(api.UserSettingsOptions{}), user.UpdateUserSettings)
|
m.Patch("", bind(api.UserSettingsOptions{}), user.UpdateUserSettings)
|
||||||
}, reqToken())
|
}, rejectPublicOnly())
|
||||||
m.Combo("/emails").
|
// Email addresses are always private account data.
|
||||||
|
m.Combo("/emails", rejectPublicOnly()).
|
||||||
Get(user.ListEmails).
|
Get(user.ListEmails).
|
||||||
Post(bind(api.CreateEmailOption{}), user.AddEmail).
|
Post(bind(api.CreateEmailOption{}), user.AddEmail).
|
||||||
Delete(bind(api.DeleteEmailOption{}), user.DeleteEmail)
|
Delete(bind(api.DeleteEmailOption{}), user.DeleteEmail)
|
||||||
@@ -1047,7 +1070,7 @@ func Routes() *web.Router {
|
|||||||
|
|
||||||
m.Get("/runs", reqToken(), user.ListWorkflowRuns)
|
m.Get("/runs", reqToken(), user.ListWorkflowRuns)
|
||||||
m.Get("/jobs", reqToken(), user.ListWorkflowJobs)
|
m.Get("/jobs", reqToken(), user.ListWorkflowJobs)
|
||||||
})
|
}, rejectPublicOnly())
|
||||||
|
|
||||||
m.Get("/followers", user.ListMyFollowers)
|
m.Get("/followers", user.ListMyFollowers)
|
||||||
m.Group("/following", func() {
|
m.Group("/following", func() {
|
||||||
@@ -1065,7 +1088,7 @@ func Routes() *web.Router {
|
|||||||
Post(bind(api.CreateKeyOption{}), user.CreatePublicKey)
|
Post(bind(api.CreateKeyOption{}), user.CreatePublicKey)
|
||||||
m.Combo("/{id}").Get(user.GetPublicKey).
|
m.Combo("/{id}").Get(user.GetPublicKey).
|
||||||
Delete(user.DeletePublicKey)
|
Delete(user.DeletePublicKey)
|
||||||
})
|
}, rejectPublicOnly())
|
||||||
|
|
||||||
// (admin:application scope)
|
// (admin:application scope)
|
||||||
m.Group("/applications", func() {
|
m.Group("/applications", func() {
|
||||||
@@ -1076,7 +1099,7 @@ func Routes() *web.Router {
|
|||||||
Delete(user.DeleteOauth2Application).
|
Delete(user.DeleteOauth2Application).
|
||||||
Patch(bind(api.CreateOAuth2ApplicationOptions{}), user.UpdateOauth2Application).
|
Patch(bind(api.CreateOAuth2ApplicationOptions{}), user.UpdateOauth2Application).
|
||||||
Get(user.GetOauth2Application)
|
Get(user.GetOauth2Application)
|
||||||
})
|
}, rejectPublicOnly())
|
||||||
|
|
||||||
// (admin:gpg_key scope)
|
// (admin:gpg_key scope)
|
||||||
m.Group("/gpg_keys", func() {
|
m.Group("/gpg_keys", func() {
|
||||||
@@ -1084,13 +1107,13 @@ func Routes() *web.Router {
|
|||||||
Post(bind(api.CreateGPGKeyOption{}), user.CreateGPGKey)
|
Post(bind(api.CreateGPGKeyOption{}), user.CreateGPGKey)
|
||||||
m.Combo("/{id}").Get(user.GetGPGKey).
|
m.Combo("/{id}").Get(user.GetGPGKey).
|
||||||
Delete(user.DeleteGPGKey)
|
Delete(user.DeleteGPGKey)
|
||||||
})
|
}, rejectPublicOnly())
|
||||||
m.Get("/gpg_key_token", user.GetVerificationToken)
|
m.Get("/gpg_key_token", rejectPublicOnly(), user.GetVerificationToken)
|
||||||
m.Post("/gpg_key_verify", bind(api.VerifyGPGKeyOption{}), user.VerifyUserGPGKey)
|
m.Post("/gpg_key_verify", rejectPublicOnly(), bind(api.VerifyGPGKeyOption{}), user.VerifyUserGPGKey)
|
||||||
|
|
||||||
// (repo scope)
|
// (repo scope)
|
||||||
m.Combo("/repos", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).Get(user.ListMyRepos).
|
m.Combo("/repos", tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository)).Get(user.ListMyRepos).
|
||||||
Post(bind(api.CreateRepoOption{}), repo.Create)
|
Post(rejectPublicOnly(), bind(api.CreateRepoOption{}), repo.Create)
|
||||||
|
|
||||||
// (repo scope)
|
// (repo scope)
|
||||||
m.Group("/starred", func() {
|
m.Group("/starred", func() {
|
||||||
@@ -1101,22 +1124,22 @@ func Routes() *web.Router {
|
|||||||
m.Delete("", user.Unstar)
|
m.Delete("", user.Unstar)
|
||||||
}, repoAssignment(), checkTokenPublicOnly())
|
}, repoAssignment(), checkTokenPublicOnly())
|
||||||
}, reqStarsEnabled(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
|
}, reqStarsEnabled(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryRepository))
|
||||||
m.Get("/times", repo.ListMyTrackedTimes)
|
m.Get("/times", rejectPublicOnly(), repo.ListMyTrackedTimes)
|
||||||
m.Get("/stopwatches", repo.GetStopwatches)
|
m.Get("/stopwatches", rejectPublicOnly(), repo.GetStopwatches)
|
||||||
m.Get("/subscriptions", user.GetMyWatchedRepos)
|
m.Get("/subscriptions", user.GetMyWatchedRepos)
|
||||||
m.Get("/teams", org.ListUserTeams)
|
m.Get("/teams", rejectPublicOnly(), org.ListUserTeams)
|
||||||
m.Group("/hooks", func() {
|
m.Group("/hooks", func() {
|
||||||
m.Combo("").Get(user.ListHooks).
|
m.Combo("").Get(user.ListHooks).
|
||||||
Post(bind(api.CreateHookOption{}), user.CreateHook)
|
Post(bind(api.CreateHookOption{}), user.CreateHook)
|
||||||
m.Combo("/{id}").Get(user.GetHook).
|
m.Combo("/{id}").Get(user.GetHook).
|
||||||
Patch(bind(api.EditHookOption{}), user.EditHook).
|
Patch(bind(api.EditHookOption{}), user.EditHook).
|
||||||
Delete(user.DeleteHook)
|
Delete(user.DeleteHook)
|
||||||
}, reqWebhooksEnabled())
|
}, reqWebhooksEnabled(), rejectPublicOnly())
|
||||||
|
|
||||||
m.Group("/avatar", func() {
|
m.Group("/avatar", func() {
|
||||||
m.Post("", bind(api.UpdateUserAvatarOption{}), user.UpdateAvatar)
|
m.Post("", bind(api.UpdateUserAvatarOption{}), user.UpdateAvatar)
|
||||||
m.Delete("", user.DeleteAvatar)
|
m.Delete("", user.DeleteAvatar)
|
||||||
})
|
}, rejectPublicOnly())
|
||||||
|
|
||||||
m.Group("/blocks", func() {
|
m.Group("/blocks", func() {
|
||||||
m.Get("", user.ListBlocks)
|
m.Get("", user.ListBlocks)
|
||||||
@@ -1125,8 +1148,8 @@ func Routes() *web.Router {
|
|||||||
m.Put("", user.BlockUser)
|
m.Put("", user.BlockUser)
|
||||||
m.Delete("", user.UnblockUser)
|
m.Delete("", user.UnblockUser)
|
||||||
}, context.UserAssignmentAPI(), checkTokenPublicOnly())
|
}, context.UserAssignmentAPI(), checkTokenPublicOnly())
|
||||||
})
|
}, rejectPublicOnly())
|
||||||
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken())
|
}, tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser), reqToken(), contextAuthenticatedUser(), checkTokenPublicOnly())
|
||||||
|
|
||||||
// Repositories (requires repo scope, org scope)
|
// Repositories (requires repo scope, org scope)
|
||||||
m.Post("/org/{org}/repos",
|
m.Post("/org/{org}/repos",
|
||||||
@@ -1426,9 +1449,9 @@ func Routes() *web.Router {
|
|||||||
Delete(reqToken(), repo.DeleteTopic)
|
Delete(reqToken(), repo.DeleteTopic)
|
||||||
}, reqAdmin())
|
}, reqAdmin())
|
||||||
}, reqAnyRepoReader())
|
}, reqAnyRepoReader())
|
||||||
m.Get("/issue_templates", context.ReferencesGitRepo(), repo.GetIssueTemplates)
|
m.Get("/issue_templates", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(), repo.GetIssueTemplates)
|
||||||
m.Get("/issue_config", context.ReferencesGitRepo(), repo.GetIssueConfig)
|
m.Get("/issue_config", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(), repo.GetIssueConfig)
|
||||||
m.Get("/issue_config/validate", context.ReferencesGitRepo(), repo.ValidateIssueConfig)
|
m.Get("/issue_config/validate", reqRepoReader(unit.TypeCode), context.ReferencesGitRepo(), repo.ValidateIssueConfig)
|
||||||
m.Get("/languages", reqRepoReader(unit.TypeCode), repo.GetLanguages)
|
m.Get("/languages", reqRepoReader(unit.TypeCode), repo.GetLanguages)
|
||||||
m.Get("/licenses", reqRepoReader(unit.TypeCode), repo.GetLicenses)
|
m.Get("/licenses", reqRepoReader(unit.TypeCode), repo.GetLicenses)
|
||||||
m.Get("/activities/feeds", repo.ListRepoActivityFeeds)
|
m.Get("/activities/feeds", repo.ListRepoActivityFeeds)
|
||||||
@@ -1597,7 +1620,7 @@ func Routes() *web.Router {
|
|||||||
}, reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead), checkTokenPublicOnly())
|
}, reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryPackage), context.UserAssignmentAPI(), context.PackageAssignmentAPI(), reqPackageAccess(perm.AccessModeRead), checkTokenPublicOnly())
|
||||||
|
|
||||||
// Organizations
|
// Organizations
|
||||||
m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), org.ListMyOrgs)
|
m.Get("/user/orgs", reqToken(), tokenRequiresScopes(auth_model.AccessTokenScopeCategoryUser, auth_model.AccessTokenScopeCategoryOrganization), checkTokenPublicOnly(), org.ListMyOrgs)
|
||||||
m.Group("/users/{username}/orgs", func() {
|
m.Group("/users/{username}/orgs", func() {
|
||||||
m.Get("", reqToken(), org.ListUserOrgs)
|
m.Get("", reqToken(), org.ListUserOrgs)
|
||||||
m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions)
|
m.Get("/{org}/permissions", reqToken(), org.GetUserOrgsPermissions)
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ func listUserOrgs(ctx *context.APIContext, u *user_model.User) {
|
|||||||
UserID: u.ID,
|
UserID: u.ID,
|
||||||
IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, u),
|
IncludeVisibility: organization.DoerViewOtherVisibility(ctx.Doer, u),
|
||||||
}
|
}
|
||||||
|
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||||
orgs, maxResults, err := db.FindAndCount[organization.Organization](ctx, opts)
|
orgs, maxResults, err := db.FindAndCount[organization.Organization](ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
@@ -192,7 +193,7 @@ func GetAll(ctx *context.APIContext) {
|
|||||||
// "$ref": "#/responses/OrganizationList"
|
// "$ref": "#/responses/OrganizationList"
|
||||||
|
|
||||||
vMode := []api.VisibleType{api.VisibleTypePublic}
|
vMode := []api.VisibleType{api.VisibleTypePublic}
|
||||||
if ctx.IsSigned && !ctx.PublicOnly {
|
if ctx.IsSigned {
|
||||||
vMode = append(vMode, api.VisibleTypeLimited)
|
vMode = append(vMode, api.VisibleTypeLimited)
|
||||||
if ctx.Doer.IsAdmin {
|
if ctx.Doer.IsAdmin {
|
||||||
vMode = append(vMode, api.VisibleTypePrivate)
|
vMode = append(vMode, api.VisibleTypePrivate)
|
||||||
@@ -201,13 +202,16 @@ func GetAll(ctx *context.APIContext) {
|
|||||||
|
|
||||||
listOptions := utils.GetListOptions(ctx)
|
listOptions := utils.GetListOptions(ctx)
|
||||||
|
|
||||||
publicOrgs, maxResults, err := user_model.SearchUsers(ctx, user_model.SearchUserOptions{
|
searchOpts := user_model.SearchUserOptions{
|
||||||
Actor: ctx.Doer,
|
Actor: ctx.Doer,
|
||||||
ListOptions: listOptions,
|
ListOptions: listOptions,
|
||||||
Types: []user_model.UserType{user_model.UserTypeOrganization},
|
Types: []user_model.UserType{user_model.UserTypeOrganization},
|
||||||
OrderBy: db.SearchOrderByAlphabetically,
|
OrderBy: db.SearchOrderByAlphabetically,
|
||||||
Visible: vMode,
|
Visible: vMode,
|
||||||
})
|
}
|
||||||
|
searchOpts.ApplyPublicOnly(ctx.PublicOnly)
|
||||||
|
|
||||||
|
publicOrgs, maxResults, err := user_model.SearchUsers(ctx, searchOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
@@ -487,6 +491,7 @@ func ListOrgActivityFeeds(ctx *context.APIContext) {
|
|||||||
Date: ctx.FormString("date"),
|
Date: ctx.FormString("date"),
|
||||||
ListOptions: listOptions,
|
ListOptions: listOptions,
|
||||||
}
|
}
|
||||||
|
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||||
|
|
||||||
feeds, count, err := feed_service.GetFeeds(ctx, opts)
|
feeds, count, err := feed_service.GetFeeds(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ package repo
|
|||||||
import (
|
import (
|
||||||
go_context "context"
|
go_context "context"
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -23,7 +22,6 @@ import (
|
|||||||
secret_model "code.gitea.io/gitea/models/secret"
|
secret_model "code.gitea.io/gitea/models/secret"
|
||||||
"code.gitea.io/gitea/modules/actions"
|
"code.gitea.io/gitea/modules/actions"
|
||||||
"code.gitea.io/gitea/modules/httplib"
|
"code.gitea.io/gitea/modules/httplib"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/util"
|
"code.gitea.io/gitea/modules/util"
|
||||||
"code.gitea.io/gitea/modules/web"
|
"code.gitea.io/gitea/modules/web"
|
||||||
@@ -1770,11 +1768,7 @@ func DeleteArtifact(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildSignature(endp string, expires, artifactID int64) []byte {
|
func buildSignature(endp string, expires, artifactID int64) []byte {
|
||||||
mac := hmac.New(sha256.New, setting.GetGeneralTokenSigningSecret())
|
return actions.BuildSignature("api", endp, strconv.FormatInt(expires, 10), strconv.FormatInt(artifactID, 10))
|
||||||
mac.Write([]byte(endp))
|
|
||||||
fmt.Fprint(mac, expires)
|
|
||||||
fmt.Fprint(mac, artifactID)
|
|
||||||
return mac.Sum(nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildDownloadRawEndpoint(repo *repo_model.Repository, artifactID int64) string {
|
func buildDownloadRawEndpoint(repo *repo_model.Repository, artifactID int64) string {
|
||||||
|
|||||||
@@ -47,9 +47,10 @@ func buildSearchIssuesRepoIDs(ctx *context.APIContext) (repoIDs []int64, allPubl
|
|||||||
Actor: ctx.Doer,
|
Actor: ctx.Doer,
|
||||||
}
|
}
|
||||||
if ctx.IsSigned {
|
if ctx.IsSigned {
|
||||||
opts.Private = !ctx.PublicOnly
|
opts.Private = true
|
||||||
opts.AllLimited = true
|
opts.AllLimited = true
|
||||||
}
|
}
|
||||||
|
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||||
if ctx.FormString("owner") != "" {
|
if ctx.FormString("owner") != "" {
|
||||||
owner, err := user_model.GetUserByName(ctx, ctx.FormString("owner"))
|
owner, err := user_model.GetUserByName(ctx, ctx.FormString("owner"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -131,9 +131,6 @@ func Search(ctx *context.APIContext) {
|
|||||||
// "$ref": "#/responses/validationError"
|
// "$ref": "#/responses/validationError"
|
||||||
|
|
||||||
private := ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private"))
|
private := ctx.IsSigned && (ctx.FormString("private") == "" || ctx.FormBool("private"))
|
||||||
if ctx.PublicOnly {
|
|
||||||
private = false
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := repo_model.SearchRepoOptions{
|
opts := repo_model.SearchRepoOptions{
|
||||||
ListOptions: utils.GetListOptions(ctx),
|
ListOptions: utils.GetListOptions(ctx),
|
||||||
@@ -149,6 +146,7 @@ func Search(ctx *context.APIContext) {
|
|||||||
StarredByID: ctx.FormInt64("starredBy"),
|
StarredByID: ctx.FormInt64("starredBy"),
|
||||||
IncludeDescription: ctx.FormBool("includeDesc"),
|
IncludeDescription: ctx.FormBool("includeDesc"),
|
||||||
}
|
}
|
||||||
|
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||||
|
|
||||||
if ctx.FormString("template") != "" {
|
if ctx.FormString("template") != "" {
|
||||||
opts.Template = optional.Some(ctx.FormBool("template"))
|
opts.Template = optional.Some(ctx.FormBool("template"))
|
||||||
@@ -567,6 +565,10 @@ func GetByID(ctx *context.APIContext) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if !ctx.TokenCanAccessRepo(repo) {
|
||||||
|
ctx.APIErrorNotFound()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
|
permission, err := access_model.GetDoerRepoPermission(ctx, repo, ctx.Doer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -1254,6 +1256,7 @@ func ListRepoActivityFeeds(ctx *context.APIContext) {
|
|||||||
Date: ctx.FormString("date"),
|
Date: ctx.FormString("date"),
|
||||||
ListOptions: listOptions,
|
ListOptions: listOptions,
|
||||||
}
|
}
|
||||||
|
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||||
|
|
||||||
feeds, count, err := feed_service.GetFeeds(ctx, opts)
|
feeds, count, err := feed_service.GetFeeds(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -19,12 +19,15 @@ import (
|
|||||||
func listUserRepos(ctx *context.APIContext, u *user_model.User, private bool) {
|
func listUserRepos(ctx *context.APIContext, u *user_model.User, private bool) {
|
||||||
opts := utils.GetListOptions(ctx)
|
opts := utils.GetListOptions(ctx)
|
||||||
|
|
||||||
repos, count, err := repo_model.GetUserRepositories(ctx, repo_model.SearchRepoOptions{
|
searchOpts := repo_model.SearchRepoOptions{
|
||||||
Actor: u,
|
Actor: u,
|
||||||
Private: private,
|
Private: private,
|
||||||
ListOptions: opts,
|
ListOptions: opts,
|
||||||
OrderBy: "id ASC",
|
OrderBy: "id ASC",
|
||||||
})
|
}
|
||||||
|
searchOpts.ApplyPublicOnly(ctx.PublicOnly)
|
||||||
|
|
||||||
|
repos, count, err := repo_model.GetUserRepositories(ctx, searchOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.APIErrorInternal(err)
|
ctx.APIErrorInternal(err)
|
||||||
return
|
return
|
||||||
@@ -79,8 +82,7 @@ func ListUserRepos(ctx *context.APIContext) {
|
|||||||
// "404":
|
// "404":
|
||||||
// "$ref": "#/responses/notFound"
|
// "$ref": "#/responses/notFound"
|
||||||
|
|
||||||
private := ctx.IsSigned
|
listUserRepos(ctx, ctx.ContextUser, ctx.IsSigned)
|
||||||
listUserRepos(ctx, ctx.ContextUser, private)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListMyRepos - list the repositories you own or have access to.
|
// ListMyRepos - list the repositories you own or have access to.
|
||||||
@@ -110,6 +112,7 @@ func ListMyRepos(ctx *context.APIContext) {
|
|||||||
Private: ctx.IsSigned,
|
Private: ctx.IsSigned,
|
||||||
IncludeDescription: true,
|
IncludeDescription: true,
|
||||||
}
|
}
|
||||||
|
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||||
|
|
||||||
repos, count, err := repo_model.SearchRepository(ctx, opts)
|
repos, count, err := repo_model.SearchRepository(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -20,11 +20,14 @@ import (
|
|||||||
// getStarredRepos returns the repos that the user with the specified userID has
|
// getStarredRepos returns the repos that the user with the specified userID has
|
||||||
// starred
|
// starred
|
||||||
func getStarredRepos(ctx *context.APIContext, user *user_model.User, private bool) ([]*api.Repository, error) {
|
func getStarredRepos(ctx *context.APIContext, user *user_model.User, private bool) ([]*api.Repository, error) {
|
||||||
starredRepos, err := repo_model.GetStarredRepos(ctx, &repo_model.StarredReposOptions{
|
opts := &repo_model.StarredReposOptions{
|
||||||
ListOptions: utils.GetListOptions(ctx),
|
ListOptions: utils.GetListOptions(ctx),
|
||||||
StarrerID: user.ID,
|
StarrerID: user.ID,
|
||||||
IncludePrivate: private,
|
IncludePrivate: private,
|
||||||
})
|
}
|
||||||
|
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||||
|
|
||||||
|
starredRepos, err := repo_model.GetStarredRepos(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import (
|
|||||||
|
|
||||||
activities_model "code.gitea.io/gitea/models/activities"
|
activities_model "code.gitea.io/gitea/models/activities"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
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/routers/api/v1/utils"
|
||||||
"code.gitea.io/gitea/services/context"
|
"code.gitea.io/gitea/services/context"
|
||||||
"code.gitea.io/gitea/services/convert"
|
"code.gitea.io/gitea/services/convert"
|
||||||
@@ -69,19 +68,16 @@ func Search(ctx *context.APIContext) {
|
|||||||
maxResults = 1
|
maxResults = 1
|
||||||
users = []*user_model.User{user_model.NewActionsUser()}
|
users = []*user_model.User{user_model.NewActionsUser()}
|
||||||
default:
|
default:
|
||||||
var visible []structs.VisibleType
|
opts := user_model.SearchUserOptions{
|
||||||
if ctx.PublicOnly {
|
|
||||||
visible = []structs.VisibleType{structs.VisibleTypePublic}
|
|
||||||
}
|
|
||||||
users, maxResults, err = user_model.SearchUsers(ctx, user_model.SearchUserOptions{
|
|
||||||
Actor: ctx.Doer,
|
Actor: ctx.Doer,
|
||||||
Keyword: ctx.FormTrim("q"),
|
Keyword: ctx.FormTrim("q"),
|
||||||
UID: uid,
|
UID: uid,
|
||||||
Types: []user_model.UserType{user_model.UserTypeIndividual},
|
Types: []user_model.UserType{user_model.UserTypeIndividual},
|
||||||
SearchByEmail: true,
|
SearchByEmail: true,
|
||||||
Visible: visible,
|
|
||||||
ListOptions: listOptions,
|
ListOptions: listOptions,
|
||||||
})
|
}
|
||||||
|
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||||
|
users, maxResults, err = user_model.SearchUsers(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.JSON(http.StatusInternalServerError, map[string]any{
|
ctx.JSON(http.StatusInternalServerError, map[string]any{
|
||||||
"ok": false,
|
"ok": false,
|
||||||
@@ -214,6 +210,7 @@ func ListUserActivityFeeds(ctx *context.APIContext) {
|
|||||||
Date: ctx.FormString("date"),
|
Date: ctx.FormString("date"),
|
||||||
ListOptions: listOptions,
|
ListOptions: listOptions,
|
||||||
}
|
}
|
||||||
|
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||||
|
|
||||||
feeds, count, err := feed_service.GetFeeds(ctx, opts)
|
feeds, count, err := feed_service.GetFeeds(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -18,11 +18,14 @@ import (
|
|||||||
|
|
||||||
// getWatchedRepos returns the repos that the user with the specified userID is watching
|
// getWatchedRepos returns the repos that the user with the specified userID is watching
|
||||||
func getWatchedRepos(ctx *context.APIContext, user *user_model.User, private bool) ([]*api.Repository, int64, error) {
|
func getWatchedRepos(ctx *context.APIContext, user *user_model.User, private bool) ([]*api.Repository, int64, error) {
|
||||||
watchedRepos, total, err := repo_model.GetWatchedRepos(ctx, &repo_model.WatchedReposOptions{
|
opts := &repo_model.WatchedReposOptions{
|
||||||
ListOptions: utils.GetListOptions(ctx),
|
ListOptions: utils.GetListOptions(ctx),
|
||||||
WatcherID: user.ID,
|
WatcherID: user.ID,
|
||||||
IncludePrivate: private,
|
IncludePrivate: private,
|
||||||
})
|
}
|
||||||
|
opts.ApplyPublicOnly(ctx.PublicOnly)
|
||||||
|
|
||||||
|
watchedRepos, total, err := repo_model.GetWatchedRepos(ctx, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-23
@@ -714,6 +714,8 @@ func indexCommit(commits []*git.Commit, commitID string) *git.Commit {
|
|||||||
|
|
||||||
// ViewPullFiles render pull request changed files list page
|
// ViewPullFiles render pull request changed files list page
|
||||||
func viewPullFiles(ctx *context.Context, beforeCommitID, afterCommitID string) {
|
func viewPullFiles(ctx *context.Context, beforeCommitID, afterCommitID string) {
|
||||||
|
var err error
|
||||||
|
|
||||||
ctx.Data["PageIsPullList"] = true
|
ctx.Data["PageIsPullList"] = true
|
||||||
ctx.Data["PageIsPullFiles"] = true
|
ctx.Data["PageIsPullFiles"] = true
|
||||||
|
|
||||||
@@ -740,43 +742,53 @@ func viewPullFiles(ctx *context.Context, beforeCommitID, afterCommitID string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isSingleCommit := beforeCommitID == "" && afterCommitID != ""
|
isSingleCommit := beforeCommitID == "" && afterCommitID != ""
|
||||||
ctx.Data["IsShowingOnlySingleCommit"] = isSingleCommit
|
// FIXME: when afterCommitID==headCommitID, isSingleCommit and isShowAllCommits can be both true, which doesn't seem right
|
||||||
isShowAllCommits := (beforeCommitID == "" || beforeCommitID == prInfo.MergeBase) && (afterCommitID == "" || afterCommitID == headCommitID)
|
isShowAllCommits := (beforeCommitID == "" || beforeCommitID == prInfo.MergeBase) && (afterCommitID == "" || afterCommitID == headCommitID)
|
||||||
|
|
||||||
|
ctx.Data["IsShowingOnlySingleCommit"] = isSingleCommit
|
||||||
ctx.Data["IsShowingAllCommits"] = isShowAllCommits
|
ctx.Data["IsShowingAllCommits"] = isShowAllCommits
|
||||||
|
|
||||||
if afterCommitID == "" || afterCommitID == headCommitID {
|
// "commits list" is half-open, half-closed: (base, head]
|
||||||
afterCommitID = headCommitID
|
// * base commit is not in the list
|
||||||
}
|
// * if the PR is empty, the list is also empty (head commit is not in the list)
|
||||||
|
|
||||||
|
afterCommitID = util.IfZero(afterCommitID, headCommitID)
|
||||||
afterCommit := indexCommit(prInfo.Commits, afterCommitID)
|
afterCommit := indexCommit(prInfo.Commits, afterCommitID)
|
||||||
|
if afterCommit == nil && afterCommitID == headCommitID {
|
||||||
|
afterCommit, err = gitRepo.GetCommit(afterCommitID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetCommit(afterCommitID)", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
if afterCommit == nil {
|
if afterCommit == nil {
|
||||||
ctx.HTTPError(http.StatusBadRequest, "after commit not found in PR commits")
|
ctx.NotFound(nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var beforeCommit *git.Commit
|
var beforeCommit *git.Commit
|
||||||
if !isSingleCommit {
|
if isSingleCommit {
|
||||||
if beforeCommitID == "" || beforeCommitID == prInfo.MergeBase {
|
|
||||||
beforeCommitID = prInfo.MergeBase
|
|
||||||
// mergebase commit is not in the list of the pull request commits
|
|
||||||
beforeCommit, err = gitRepo.GetCommit(beforeCommitID)
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("GetCommit", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
beforeCommit = indexCommit(prInfo.Commits, beforeCommitID)
|
|
||||||
if beforeCommit == nil {
|
|
||||||
ctx.HTTPError(http.StatusBadRequest, "before commit not found in PR commits")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
beforeCommit, err = afterCommit.Parent(0)
|
beforeCommit, err = afterCommit.Parent(0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("Parent", err)
|
ctx.ServerError("afterCommit.Parent", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
beforeCommitID = beforeCommit.ID.String()
|
beforeCommitID = beforeCommit.ID.String()
|
||||||
|
} else {
|
||||||
|
beforeCommitID = util.IfZero(beforeCommitID, prInfo.MergeBase)
|
||||||
|
beforeCommit = indexCommit(prInfo.Commits, beforeCommitID)
|
||||||
|
if beforeCommit == nil && beforeCommitID == prInfo.MergeBase {
|
||||||
|
// mergebase commit is not in the list of the pull request commits
|
||||||
|
beforeCommit, err = gitRepo.GetCommit(beforeCommitID)
|
||||||
|
if err != nil {
|
||||||
|
ctx.ServerError("GetCommit(beforeCommitID)", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if beforeCommit == nil {
|
||||||
|
ctx.NotFound(nil)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
ctx.Data["Username"] = ctx.Repo.Owner.Name
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unit"
|
"code.gitea.io/gitea/models/unit"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
"code.gitea.io/gitea/modules/cache"
|
"code.gitea.io/gitea/modules/cache"
|
||||||
@@ -47,6 +48,12 @@ type APIContext struct {
|
|||||||
PublicOnly bool // Whether the request is for a public endpoint
|
PublicOnly bool // Whether the request is for a public endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TokenCanAccessRepo reports whether the current API token is allowed to access the repository.
|
||||||
|
// A public-only token cannot reach a private repo; any other token is unrestricted by this check.
|
||||||
|
func (ctx *APIContext) TokenCanAccessRepo(repo *repo_model.Repository) bool {
|
||||||
|
return repo == nil || !ctx.PublicOnly || !repo.IsPrivate
|
||||||
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
web.RegisterResponseStatusProvider[*APIContext](func(req *http.Request) web_types.ResponseStatusProvider {
|
web.RegisterResponseStatusProvider[*APIContext](func(req *http.Request) web_types.ResponseStatusProvider {
|
||||||
return req.Context().Value(apiContextKey).(*APIContext)
|
return req.Context().Value(apiContextKey).(*APIContext)
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
packages_model "code.gitea.io/gitea/models/packages"
|
packages_model "code.gitea.io/gitea/models/packages"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
|
"code.gitea.io/gitea/modules/globallock"
|
||||||
"code.gitea.io/gitea/modules/json"
|
"code.gitea.io/gitea/modules/json"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/optional"
|
"code.gitea.io/gitea/modules/optional"
|
||||||
@@ -78,8 +79,13 @@ func CreatePackageAndAddFile(ctx context.Context, pvci *PackageCreationInfo, pfc
|
|||||||
}
|
}
|
||||||
|
|
||||||
// CreatePackageOrAddFileToExisting creates a package with a file or adds the file if the package exists already
|
// CreatePackageOrAddFileToExisting creates a package with a file or adds the file if the package exists already
|
||||||
func CreatePackageOrAddFileToExisting(ctx context.Context, pvci *PackageCreationInfo, pfci *PackageFileCreationInfo) (*packages_model.PackageVersion, *packages_model.PackageFile, error) {
|
func CreatePackageOrAddFileToExisting(ctx context.Context, pvci *PackageCreationInfo, pfci *PackageFileCreationInfo) (pv *packages_model.PackageVersion, pf *packages_model.PackageFile, err error) {
|
||||||
return createPackageAndAddFile(ctx, pvci, pfci, true)
|
lockKey := fmt.Sprintf("pkg-upsert-%v-%v-%v", pvci.PackageType, pvci.Name, pvci.Version)
|
||||||
|
err = globallock.LockAndDo(ctx, lockKey, func(ctx context.Context) error {
|
||||||
|
pv, pf, err = createPackageAndAddFile(ctx, pvci, pfci, true)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
return pv, pf, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func createPackageAndAddFile(ctx context.Context, pvci *PackageCreationInfo, pfci *PackageFileCreationInfo, allowDuplicate bool) (*packages_model.PackageVersion, *packages_model.PackageFile, error) {
|
func createPackageAndAddFile(ctx context.Context, pvci *PackageCreationInfo, pfci *PackageFileCreationInfo, allowDuplicate bool) (*packages_model.PackageVersion, *packages_model.PackageFile, error) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
|
"code.gitea.io/gitea/modules/base"
|
||||||
"code.gitea.io/gitea/modules/fileicon"
|
"code.gitea.io/gitea/modules/fileicon"
|
||||||
"code.gitea.io/gitea/modules/git"
|
"code.gitea.io/gitea/modules/git"
|
||||||
"code.gitea.io/gitea/modules/log"
|
"code.gitea.io/gitea/modules/log"
|
||||||
@@ -170,7 +171,7 @@ func sortTreeViewNodes(nodes []*TreeViewNode) {
|
|||||||
if a != b {
|
if a != b {
|
||||||
return a < b
|
return a < b
|
||||||
}
|
}
|
||||||
return nodes[i].EntryName < nodes[j].EntryName
|
return base.NaturalSortCompare(nodes[i].EntryName, nodes[j].EntryName) < 0
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
@@ -179,3 +180,19 @@ func TestAPIRepoValidateIssueConfig(t *testing.T) {
|
|||||||
assert.NotEmpty(t, issueConfigValidation.Message)
|
assert.NotEmpty(t, issueConfigValidation.Message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIRepoIssueConfigRequiresCodeUnit(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 24})
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
|
||||||
|
for _, path := range []string{
|
||||||
|
fmt.Sprintf("/api/v1/repos/%s/issue_config", repo.FullName()),
|
||||||
|
fmt.Sprintf("/api/v1/repos/%s/issue_config/validate", repo.FullName()),
|
||||||
|
} {
|
||||||
|
req := NewRequest(t, "GET", path).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusForbidden)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,10 +8,12 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
repo_model "code.gitea.io/gitea/models/repo"
|
repo_model "code.gitea.io/gitea/models/repo"
|
||||||
"code.gitea.io/gitea/models/unittest"
|
"code.gitea.io/gitea/models/unittest"
|
||||||
user_model "code.gitea.io/gitea/models/user"
|
user_model "code.gitea.io/gitea/models/user"
|
||||||
api "code.gitea.io/gitea/modules/structs"
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@@ -53,3 +55,19 @@ about: bar
|
|||||||
assert.Equal(t, "error occurs when parsing issue template: count=2", resp.Header().Get("X-Gitea-Warning"))
|
assert.Equal(t, "error occurs when parsing issue template: count=2", resp.Header().Get("X-Gitea-Warning"))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIIssueTemplateRequiresCodeUnit(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
repo := unittest.AssertExistsAndLoadBean(t, &repo_model.Repository{ID: 24})
|
||||||
|
user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
token := getUserToken(t, user.Name, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
issueTemplatesURL := "/api/v1/repos/" + repo.FullName() + "/issue_templates"
|
||||||
|
languagesURL := "/api/v1/repos/" + repo.FullName() + "/languages"
|
||||||
|
|
||||||
|
req := NewRequest(t, "GET", issueTemplatesURL).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusForbidden)
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", languagesURL).AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusForbidden)
|
||||||
|
}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ func testAPIListIssuesPublicOnly(t *testing.T) {
|
|||||||
|
|
||||||
publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopePublicOnly)
|
publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeReadIssue, auth_model.AccessTokenScopePublicOnly)
|
||||||
req = NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken)
|
req = NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken)
|
||||||
MakeRequest(t, req, http.StatusForbidden)
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testAPICreateIssue(t *testing.T) {
|
func testAPICreateIssue(t *testing.T) {
|
||||||
|
|||||||
@@ -212,3 +212,23 @@ func TestAPINotificationPUT(t *testing.T) {
|
|||||||
assert.True(t, apiNL[0].Unread)
|
assert.True(t, apiNL[0].Unread)
|
||||||
assert.False(t, apiNL[0].Pinned)
|
assert.False(t, apiNL[0].Pinned)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPINotificationPublicOnly(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
user2 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
|
||||||
|
thread5 := unittest.AssertExistsAndLoadBean(t, &activities_model.Notification{ID: 5})
|
||||||
|
|
||||||
|
token := getUserToken(t, user2.Name, auth_model.AccessTokenScopeReadNotification, auth_model.AccessTokenScopePublicOnly)
|
||||||
|
req := NewRequest(t, "GET", "/api/v1/notifications").
|
||||||
|
AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusForbidden)
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", "/api/v1/notifications/new").
|
||||||
|
AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusForbidden)
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", fmt.Sprintf("/api/v1/notifications/threads/%d", thread5.ID)).
|
||||||
|
AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusForbidden)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
api "code.gitea.io/gitea/modules/structs"
|
||||||
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAPIUserReposPublicOnly(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly)
|
||||||
|
req := NewRequest(t, "GET", "/api/v1/user/repos").
|
||||||
|
AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
var repos []api.Repository
|
||||||
|
DecodeJSON(t, resp, &repos)
|
||||||
|
assert.NotEmpty(t, repos)
|
||||||
|
for _, repo := range repos {
|
||||||
|
assert.False(t, repo.Private)
|
||||||
|
}
|
||||||
|
assert.NotContains(t, repoNames(repos), "user2/repo2")
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", "/api/v1/users/user2/repos").
|
||||||
|
AddTokenAuth(token)
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &repos)
|
||||||
|
assert.NotEmpty(t, repos)
|
||||||
|
for _, repo := range repos {
|
||||||
|
assert.False(t, repo.Private)
|
||||||
|
}
|
||||||
|
assert.NotContains(t, repoNames(repos), "user2/repo2")
|
||||||
|
}
|
||||||
|
|
||||||
|
func repoNames(repos []api.Repository) []string {
|
||||||
|
names := make([]string, 0, len(repos))
|
||||||
|
for _, repo := range repos {
|
||||||
|
names = append(names, repo.FullName)
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIRepoByIDPublicOnly(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly)
|
||||||
|
req := NewRequest(t, "GET", "/api/v1/repositories/1").
|
||||||
|
AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", "/api/v1/repositories/2").
|
||||||
|
AddTokenAuth(token)
|
||||||
|
MakeRequest(t, req, http.StatusNotFound)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAPIActivityFeedsPublicOnly(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser)
|
||||||
|
req := NewRequest(t, "GET", "/api/v1/users/user2/activities/feeds").
|
||||||
|
AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
var activities []api.Activity
|
||||||
|
DecodeJSON(t, resp, &activities)
|
||||||
|
assert.NotEmpty(t, activities)
|
||||||
|
|
||||||
|
publicToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopePublicOnly)
|
||||||
|
req = NewRequest(t, "GET", "/api/v1/users/user2/activities/feeds").
|
||||||
|
AddTokenAuth(publicToken)
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &activities)
|
||||||
|
assertPublicActivitiesOnly(t, activities)
|
||||||
|
|
||||||
|
orgToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadOrganization)
|
||||||
|
req = NewRequest(t, "GET", "/api/v1/orgs/org3/activities/feeds").
|
||||||
|
AddTokenAuth(orgToken)
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &activities)
|
||||||
|
assert.NotEmpty(t, activities)
|
||||||
|
|
||||||
|
publicOrgToken := getUserToken(t, "user2", auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopePublicOnly)
|
||||||
|
req = NewRequest(t, "GET", "/api/v1/orgs/org3/activities/feeds").
|
||||||
|
AddTokenAuth(publicOrgToken)
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
DecodeJSON(t, resp, &activities)
|
||||||
|
assertPublicActivitiesOnly(t, activities)
|
||||||
|
}
|
||||||
|
|
||||||
|
func assertPublicActivitiesOnly(t *testing.T, activities []api.Activity) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
for _, activity := range activities {
|
||||||
|
assert.False(t, activity.IsPrivate)
|
||||||
|
if activity.Repo != nil {
|
||||||
|
assert.False(t, activity.Repo.Private)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,10 +29,10 @@ func TestAPIRepoBranchesPlain(t *testing.T) {
|
|||||||
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1})
|
||||||
session := loginUser(t, user1.LowerName)
|
session := loginUser(t, user1.LowerName)
|
||||||
|
|
||||||
// public only token should be forbidden
|
// public-only token cannot see a private repo
|
||||||
publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopePublicOnly, auth_model.AccessTokenScopeWriteRepository)
|
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
|
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)
|
MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound)
|
||||||
|
|
||||||
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
token := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository)
|
||||||
resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK)
|
resp := MakeRequest(t, NewRequest(t, "GET", link.String()).AddTokenAuth(token), http.StatusOK)
|
||||||
@@ -46,7 +46,7 @@ func TestAPIRepoBranchesPlain(t *testing.T) {
|
|||||||
assert.Equal(t, "master", branches[1].Name)
|
assert.Equal(t, "master", branches[1].Name)
|
||||||
|
|
||||||
link2, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch", repo3.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)
|
MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound)
|
||||||
|
|
||||||
resp = MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(token), http.StatusOK)
|
resp = MakeRequest(t, NewRequest(t, "GET", link2.String()).AddTokenAuth(token), http.StatusOK)
|
||||||
bs, err = io.ReadAll(resp.Body)
|
bs, err = io.ReadAll(resp.Body)
|
||||||
@@ -55,7 +55,7 @@ func TestAPIRepoBranchesPlain(t *testing.T) {
|
|||||||
assert.NoError(t, json.Unmarshal(bs, &branch))
|
assert.NoError(t, json.Unmarshal(bs, &branch))
|
||||||
assert.Equal(t, "test_branch", branch.Name)
|
assert.Equal(t, "test_branch", branch.Name)
|
||||||
|
|
||||||
MakeRequest(t, NewRequest(t, "POST", link.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden)
|
MakeRequest(t, NewRequest(t, "POST", link.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound)
|
||||||
|
|
||||||
req := NewRequest(t, "POST", link.String()).AddTokenAuth(token)
|
req := NewRequest(t, "POST", link.String()).AddTokenAuth(token)
|
||||||
req.Header.Add("Content-Type", "application/json")
|
req.Header.Add("Content-Type", "application/json")
|
||||||
@@ -81,7 +81,7 @@ func TestAPIRepoBranchesPlain(t *testing.T) {
|
|||||||
|
|
||||||
link3, _ := url.Parse(fmt.Sprintf("/api/v1/repos/org3/%s/branches/test_branch2", repo3.Name))
|
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()), http.StatusNotFound)
|
||||||
MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(publicOnlyToken), http.StatusForbidden)
|
MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(publicOnlyToken), http.StatusNotFound)
|
||||||
|
|
||||||
MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(token), http.StatusNoContent)
|
MakeRequest(t, NewRequest(t, "DELETE", link3.String()).AddTokenAuth(token), http.StatusNoContent)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|||||||
@@ -155,3 +155,44 @@ func TestMyOrgs(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}, orgs)
|
}, orgs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMyOrgsPublicOnly(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
normalUsername := "user2"
|
||||||
|
token := getUserToken(t, normalUsername, auth_model.AccessTokenScopeReadOrganization, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopePublicOnly)
|
||||||
|
req := NewRequest(t, "GET", "/api/v1/user/orgs").
|
||||||
|
AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
var orgs []*api.Organization
|
||||||
|
DecodeJSON(t, resp, &orgs)
|
||||||
|
org3 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org3"})
|
||||||
|
org17 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "org17"})
|
||||||
|
|
||||||
|
assert.Equal(t, []*api.Organization{
|
||||||
|
{
|
||||||
|
ID: 17,
|
||||||
|
Name: org17.Name,
|
||||||
|
UserName: org17.Name,
|
||||||
|
FullName: org17.FullName,
|
||||||
|
Email: org17.Email,
|
||||||
|
AvatarURL: org17.AvatarLink(t.Context()),
|
||||||
|
Description: "",
|
||||||
|
Website: "",
|
||||||
|
Location: "",
|
||||||
|
Visibility: "public",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: 3,
|
||||||
|
Name: org3.Name,
|
||||||
|
UserName: org3.Name,
|
||||||
|
FullName: org3.FullName,
|
||||||
|
Email: org3.Email,
|
||||||
|
AvatarURL: org3.AvatarLink(t.Context()),
|
||||||
|
Description: "",
|
||||||
|
Website: "",
|
||||||
|
Location: "",
|
||||||
|
Visibility: "public",
|
||||||
|
},
|
||||||
|
}, orgs)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
package integration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
auth_model "code.gitea.io/gitea/models/auth"
|
||||||
|
"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/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAPIPublicOnlySelfUserRoutes(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user31"})
|
||||||
|
require.True(t, privateUser.Visibility.IsPrivate())
|
||||||
|
|
||||||
|
privateSession := loginUser(t, privateUser.Name)
|
||||||
|
privateReadUserToken := getTokenForLoggedInUser(t, privateSession,
|
||||||
|
auth_model.AccessTokenScopePublicOnly,
|
||||||
|
auth_model.AccessTokenScopeReadUser,
|
||||||
|
)
|
||||||
|
privateWriteUserToken := getTokenForLoggedInUser(t, privateSession,
|
||||||
|
auth_model.AccessTokenScopePublicOnly,
|
||||||
|
auth_model.AccessTokenScopeWriteUser,
|
||||||
|
)
|
||||||
|
|
||||||
|
t.Run("PrivateProfileForbidden", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", "/api/v1/users/user31").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("PrivateSensitiveSelfRoutesForbidden", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/settings").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||||
|
hideEmail := true
|
||||||
|
settingsReq := NewRequestWithJSON(t, "PATCH", "/api/v1/user/settings", &api.UserSettingsOptions{
|
||||||
|
HideEmail: &hideEmail,
|
||||||
|
}).AddTokenAuth(privateWriteUserToken)
|
||||||
|
MakeRequest(t, settingsReq, http.StatusForbidden)
|
||||||
|
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/emails").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||||
|
emailReq := NewRequestWithJSON(t, "POST", "/api/v1/user/emails", &api.CreateEmailOption{
|
||||||
|
Emails: []string{"user31-public-only@example.com"},
|
||||||
|
}).AddTokenAuth(privateWriteUserToken)
|
||||||
|
MakeRequest(t, emailReq, http.StatusForbidden)
|
||||||
|
|
||||||
|
keyReq := NewRequestWithJSON(t, "POST", "/api/v1/user/keys", api.CreateKeyOption{
|
||||||
|
Title: "public-only-private-key",
|
||||||
|
Key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC4cn+iXnA4KvcQYSV88vGn0Yi91vG47t1P7okprVmhNTkipNRIHWr6WdCO4VDr/cvsRkuVJAsLO2enwjGWWueOO6BodiBgyAOZ/5t5nJNMCNuLGT5UIo/RI1b0WRQwxEZTRjt6mFNw6lH14wRd8ulsr9toSWBPMOGWoYs1PDeDL0JuTjL+tr1SZi/EyxCngpYszKdXllJEHyI79KQgeD0Vt3pTrkbNVTOEcCNqZePSVmUH8X8Vhugz3bnE0/iE9Pb5fkWO9c4AnM1FgI/8Bvp27Fw2ShryIXuR6kKvUqhVMTuOSDHwu6A8jLE5Owt3GAYugDpDYuwTVNGrHLXKpPzrGGPE/jPmaLCMZcsdkec95dYeU3zKODEm8UQZFhmJmDeWVJ36nGrGZHL4J5aTTaeFUJmmXDaJYiJ+K2/ioKgXqnXvltu0A9R8/LGy4nrTJRr4JMLuJFoUXvGm1gXQ70w2LSpk6yl71RNC0hCtsBe8BP8IhYCM0EP5jh7eCMQZNvM= nocomment",
|
||||||
|
}).AddTokenAuth(privateWriteUserToken)
|
||||||
|
MakeRequest(t, keyReq, http.StatusForbidden)
|
||||||
|
|
||||||
|
oauthReq := NewRequestWithJSON(t, "POST", "/api/v1/user/applications/oauth2", &api.CreateOAuth2ApplicationOptions{
|
||||||
|
Name: "public-only-private-oauth-app",
|
||||||
|
RedirectURIs: []string{"https://example.com/callback"},
|
||||||
|
ConfidentialClient: true,
|
||||||
|
}).AddTokenAuth(privateWriteUserToken)
|
||||||
|
MakeRequest(t, oauthReq, http.StatusForbidden)
|
||||||
|
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/gpg_keys").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||||
|
gpgKeyReq := NewRequestWithJSON(t, "POST", "/api/v1/user/gpg_keys", &api.CreateGPGKeyOption{
|
||||||
|
ArmoredKey: "-----BEGIN PGP PUBLIC KEY BLOCK-----\ncomment\n-----END PGP PUBLIC KEY BLOCK-----",
|
||||||
|
}).AddTokenAuth(privateWriteUserToken)
|
||||||
|
MakeRequest(t, gpgKeyReq, http.StatusForbidden)
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/gpg_key_token").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||||
|
gpgVerifyReq := NewRequestWithJSON(t, "POST", "/api/v1/user/gpg_key_verify", &api.VerifyGPGKeyOption{
|
||||||
|
KeyID: "deadbeef",
|
||||||
|
Signature: "invalid-signature",
|
||||||
|
}).AddTokenAuth(privateWriteUserToken)
|
||||||
|
MakeRequest(t, gpgVerifyReq, http.StatusForbidden)
|
||||||
|
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/actions/variables").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||||
|
MakeRequest(t, NewRequest(t, "DELETE", "/api/v1/user/actions/secrets/PRIVATE_SECRET").AddTokenAuth(privateWriteUserToken), http.StatusForbidden)
|
||||||
|
variableReq := NewRequestWithJSON(t, "POST", "/api/v1/user/actions/variables/PRIVATE_VAR", api.CreateVariableOption{
|
||||||
|
Value: "private-value",
|
||||||
|
Description: "must stay private",
|
||||||
|
}).AddTokenAuth(privateWriteUserToken)
|
||||||
|
MakeRequest(t, variableReq, http.StatusForbidden)
|
||||||
|
|
||||||
|
MakeRequest(t, NewRequest(t, "POST", "/api/v1/user/actions/runners/registration-token").AddTokenAuth(privateWriteUserToken), http.StatusForbidden)
|
||||||
|
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/hooks").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||||
|
hookReq := NewRequestWithJSON(t, "POST", "/api/v1/user/hooks", api.CreateHookOption{
|
||||||
|
Type: "gitea",
|
||||||
|
Config: api.CreateHookOptionConfig{
|
||||||
|
"content_type": "json",
|
||||||
|
"url": "http://example.com/",
|
||||||
|
},
|
||||||
|
Name: "public-only-private-hook",
|
||||||
|
}).AddTokenAuth(privateWriteUserToken)
|
||||||
|
MakeRequest(t, hookReq, http.StatusForbidden)
|
||||||
|
|
||||||
|
avatarReq := NewRequestWithJSON(t, "POST", "/api/v1/user/avatar", &api.UpdateUserAvatarOption{
|
||||||
|
Image: "aGVsbG8=",
|
||||||
|
}).AddTokenAuth(privateWriteUserToken)
|
||||||
|
MakeRequest(t, avatarReq, http.StatusForbidden)
|
||||||
|
MakeRequest(t, NewRequest(t, "DELETE", "/api/v1/user/avatar").AddTokenAuth(privateWriteUserToken), http.StatusForbidden)
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/times").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/stopwatches").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||||
|
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/subscriptions").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/teams").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||||
|
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/blocks").AddTokenAuth(privateReadUserToken), http.StatusForbidden)
|
||||||
|
MakeRequest(t, NewRequest(t, "PUT", "/api/v1/user/blocks/user2").AddTokenAuth(privateWriteUserToken), http.StatusForbidden)
|
||||||
|
|
||||||
|
MakeRequest(t, NewRequest(t, "PUT", "/api/v1/user/following/user2").AddTokenAuth(privateWriteUserToken), http.StatusForbidden)
|
||||||
|
MakeRequest(t, NewRequest(t, "DELETE", "/api/v1/user/following/user2").AddTokenAuth(privateWriteUserToken), http.StatusForbidden)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("PublicRepoRoutesFilterAndRejectMutations", func(t *testing.T) {
|
||||||
|
defer tests.PrintCurrentTest(t)()
|
||||||
|
|
||||||
|
publicSession := loginUser(t, "user2")
|
||||||
|
fullWriteRepoToken := getTokenForLoggedInUser(t, publicSession,
|
||||||
|
auth_model.AccessTokenScopeWriteUser,
|
||||||
|
auth_model.AccessTokenScopeWriteRepository,
|
||||||
|
)
|
||||||
|
publicOnlyReadRepoToken := getTokenForLoggedInUser(t, publicSession,
|
||||||
|
auth_model.AccessTokenScopePublicOnly,
|
||||||
|
auth_model.AccessTokenScopeReadUser,
|
||||||
|
auth_model.AccessTokenScopeReadRepository,
|
||||||
|
)
|
||||||
|
publicOnlyWriteRepoToken := getTokenForLoggedInUser(t, publicSession,
|
||||||
|
auth_model.AccessTokenScopePublicOnly,
|
||||||
|
auth_model.AccessTokenScopeWriteUser,
|
||||||
|
auth_model.AccessTokenScopeWriteRepository,
|
||||||
|
)
|
||||||
|
|
||||||
|
publicRepoName := "public-only-visible-self-repo"
|
||||||
|
privateRepoName := "public-only-hidden-self-repo"
|
||||||
|
|
||||||
|
resp := MakeRequest(t, NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{
|
||||||
|
Name: publicRepoName,
|
||||||
|
Private: false,
|
||||||
|
}).AddTokenAuth(fullWriteRepoToken), http.StatusCreated)
|
||||||
|
publicRepo := DecodeJSON(t, resp, &api.Repository{})
|
||||||
|
require.Equal(t, "user2/"+publicRepoName, publicRepo.FullName)
|
||||||
|
|
||||||
|
resp = MakeRequest(t, NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{
|
||||||
|
Name: privateRepoName,
|
||||||
|
Private: true,
|
||||||
|
}).AddTokenAuth(fullWriteRepoToken), http.StatusCreated)
|
||||||
|
privateRepo := DecodeJSON(t, resp, &api.Repository{})
|
||||||
|
require.Equal(t, "user2/"+privateRepoName, privateRepo.FullName)
|
||||||
|
|
||||||
|
MakeRequest(t, NewRequest(t, "GET", "/api/v1/repos/user2/"+privateRepoName).AddTokenAuth(publicOnlyReadRepoToken), http.StatusNotFound)
|
||||||
|
|
||||||
|
resp = MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/repos").AddTokenAuth(publicOnlyReadRepoToken), http.StatusOK)
|
||||||
|
repos := DecodeJSON(t, resp, []api.Repository{})
|
||||||
|
|
||||||
|
foundPublicRepo := false
|
||||||
|
for _, repo := range repos {
|
||||||
|
require.NotEqual(t, privateRepo.FullName, repo.FullName)
|
||||||
|
if repo.FullName == publicRepo.FullName {
|
||||||
|
foundPublicRepo = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.True(t, foundPublicRepo)
|
||||||
|
|
||||||
|
MakeRequest(t, NewRequestWithJSON(t, "POST", "/api/v1/user/repos", &api.CreateRepoOption{
|
||||||
|
Name: "public-only-rejected-self-repo",
|
||||||
|
Private: false,
|
||||||
|
}).AddTokenAuth(publicOnlyWriteRepoToken), http.StatusForbidden)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"code.gitea.io/gitea/tests"
|
"code.gitea.io/gitea/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAPIStar(t *testing.T) {
|
func TestAPIStar(t *testing.T) {
|
||||||
@@ -155,3 +156,24 @@ func TestAPIStarDisabled(t *testing.T) {
|
|||||||
MakeRequest(t, req, http.StatusForbidden)
|
MakeRequest(t, req, http.StatusForbidden)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIStarPublicOnly(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
token := getUserToken(t, "user2", auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository, auth_model.AccessTokenScopePublicOnly)
|
||||||
|
req := NewRequest(t, "GET", "/api/v1/user/starred").
|
||||||
|
AddTokenAuth(token)
|
||||||
|
resp := MakeRequest(t, req, http.StatusOK)
|
||||||
|
|
||||||
|
repos := DecodeJSON(t, resp, []api.Repository{})
|
||||||
|
if assert.Len(t, repos, 1) {
|
||||||
|
assert.Equal(t, "user5/repo4", repos[0].FullName)
|
||||||
|
}
|
||||||
|
|
||||||
|
req = NewRequest(t, "GET", "/api/v1/users/user2/starred").
|
||||||
|
AddTokenAuth(token)
|
||||||
|
resp = MakeRequest(t, req, http.StatusOK)
|
||||||
|
repos = DecodeJSON(t, resp, []api.Repository{})
|
||||||
|
require.Len(t, repos, 1)
|
||||||
|
assert.Equal(t, "user5/repo4", repos[0].FullName)
|
||||||
|
}
|
||||||
|
|||||||
@@ -94,3 +94,28 @@ func TestAPIWatch(t *testing.T) {
|
|||||||
MakeRequest(t, req, http.StatusNoContent)
|
MakeRequest(t, req, http.StatusNoContent)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIWatchPublicOnly(t *testing.T) {
|
||||||
|
defer tests.PrepareTestEnv(t)()
|
||||||
|
|
||||||
|
session := loginUser(t, "user1")
|
||||||
|
writeRepoToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeReadUser)
|
||||||
|
publicOnlyToken := getTokenForLoggedInUser(t, session, auth_model.AccessTokenScopePublicOnly, auth_model.AccessTokenScopeReadUser, auth_model.AccessTokenScopeReadRepository)
|
||||||
|
|
||||||
|
MakeRequest(t, NewRequest(t, "PUT", "/api/v1/repos/user2/repo1/subscription").AddTokenAuth(writeRepoToken), http.StatusOK)
|
||||||
|
MakeRequest(t, NewRequest(t, "PUT", "/api/v1/repos/user2/repo2/subscription").AddTokenAuth(writeRepoToken), http.StatusOK)
|
||||||
|
|
||||||
|
resp := MakeRequest(t, NewRequest(t, "GET", "/api/v1/user/subscriptions").AddTokenAuth(publicOnlyToken), http.StatusOK)
|
||||||
|
repos := DecodeJSON(t, resp, []api.Repository{})
|
||||||
|
for _, r := range repos {
|
||||||
|
assert.False(t, r.Private, "private repo %s leaked via /user/subscriptions", r.FullName)
|
||||||
|
}
|
||||||
|
assert.NotContains(t, repoNames(repos), "user2/repo2")
|
||||||
|
|
||||||
|
resp = MakeRequest(t, NewRequest(t, "GET", "/api/v1/users/user1/subscriptions").AddTokenAuth(publicOnlyToken), http.StatusOK)
|
||||||
|
repos = DecodeJSON(t, resp, []api.Repository{})
|
||||||
|
for _, r := range repos {
|
||||||
|
assert.False(t, r.Private, "private repo %s leaked via /users/{username}/subscriptions", r.FullName)
|
||||||
|
}
|
||||||
|
assert.NotContains(t, repoNames(repos), "user2/repo2")
|
||||||
|
}
|
||||||
|
|||||||
@@ -141,21 +141,29 @@ func TestPullCreate_EmptyChangesWithDifferentCommits(t *testing.T) {
|
|||||||
func TestPullCreate_EmptyChangesWithSameCommits(t *testing.T) {
|
func TestPullCreate_EmptyChangesWithSameCommits(t *testing.T) {
|
||||||
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
onGiteaRun(t, func(t *testing.T, u *url.URL) {
|
||||||
session := loginUser(t, "user1")
|
session := loginUser(t, "user1")
|
||||||
testRepoFork(t, session, "user2", "repo1", "user1", "repo1", "")
|
|
||||||
testCreateBranch(t, session, "user1", "repo1", "branch/master", "status1", http.StatusSeeOther)
|
|
||||||
url := path.Join("user1", "repo1", "compare", "master...status1")
|
|
||||||
req := NewRequestWithValues(t, "POST", url,
|
|
||||||
map[string]string{
|
|
||||||
"title": "pull request from status1",
|
|
||||||
},
|
|
||||||
)
|
|
||||||
session.MakeRequest(t, req, http.StatusOK)
|
|
||||||
req = NewRequest(t, "GET", "/user1/repo1/pulls/1")
|
|
||||||
resp := session.MakeRequest(t, req, http.StatusOK)
|
|
||||||
doc := NewHTMLParser(t, resp.Body)
|
|
||||||
|
|
||||||
|
testCreateBranch(t, session, "user2", "repo1", "branch/master", "empty-pr-branch", http.StatusSeeOther)
|
||||||
|
resp := testPullCreateDirectly(t, session, createPullRequestOptions{
|
||||||
|
BaseRepoOwner: "user2",
|
||||||
|
BaseRepoName: "repo1",
|
||||||
|
BaseBranch: "master",
|
||||||
|
HeadBranch: "empty-pr-branch",
|
||||||
|
Title: "empty pr test",
|
||||||
|
})
|
||||||
|
prURL := test.RedirectURL(resp)
|
||||||
|
|
||||||
|
// check the "merge box" text
|
||||||
|
req := NewRequest(t, "GET", prURL)
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
doc := NewHTMLParser(t, resp.Body)
|
||||||
text := strings.TrimSpace(doc.doc.Find(".merge-section").Text())
|
text := strings.TrimSpace(doc.doc.Find(".merge-section").Text())
|
||||||
assert.Contains(t, text, "This branch is already included in the target branch. There is nothing to merge.")
|
assert.Contains(t, text, "This branch is already included in the target branch. There is nothing to merge.")
|
||||||
|
|
||||||
|
// check the "files" tab content
|
||||||
|
req = NewRequest(t, "GET", prURL+"/files")
|
||||||
|
resp = session.MakeRequest(t, req, http.StatusOK)
|
||||||
|
doc = NewHTMLParser(t, resp.Body)
|
||||||
|
assert.Equal(t, "Diff Content Not Available", strings.TrimSpace(doc.Find("#diff-container").Text()))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user