Skip to content

Commit

Permalink
Merge branch 'main' into fix-markdown
Browse files Browse the repository at this point in the history
  • Loading branch information
wxiaoguang authored Nov 18, 2024
2 parents fed44af + 4f879a0 commit 4ea582a
Show file tree
Hide file tree
Showing 11 changed files with 276 additions and 52 deletions.
15 changes: 0 additions & 15 deletions models/repo/fork.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,21 +54,6 @@ func GetUserFork(ctx context.Context, repoID, userID int64) (*Repository, error)
return &forkedRepo, nil
}

// GetForks returns all the forks of the repository
func GetForks(ctx context.Context, repo *Repository, listOptions db.ListOptions) ([]*Repository, error) {
sess := db.GetEngine(ctx)

var forks []*Repository
if listOptions.Page == 0 {
forks = make([]*Repository, 0, repo.NumForks)
} else {
forks = make([]*Repository, 0, listOptions.PageSize)
sess = db.SetSessionPagination(sess, &listOptions)
}

return forks, sess.Find(&forks, &Repository{ForkID: repo.ID})
}

// IncrementRepoForkNum increment repository fork number
func IncrementRepoForkNum(ctx context.Context, repoID int64) error {
_, err := db.GetEngine(ctx).Exec("UPDATE `repository` SET num_forks=num_forks+1 WHERE id=?", repoID)
Expand Down
26 changes: 18 additions & 8 deletions models/repo/repo_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,19 +98,14 @@ func (repos RepositoryList) IDs() []int64 {
return repoIDs
}

// LoadAttributes loads the attributes for the given RepositoryList
func (repos RepositoryList) LoadAttributes(ctx context.Context) error {
func (repos RepositoryList) LoadOwners(ctx context.Context) error {
if len(repos) == 0 {
return nil
}

userIDs := container.FilterSlice(repos, func(repo *Repository) (int64, bool) {
return repo.OwnerID, true
})
repoIDs := make([]int64, len(repos))
for i := range repos {
repoIDs[i] = repos[i].ID
}

// Load owners.
users := make(map[int64]*user_model.User, len(userIDs))
Expand All @@ -123,12 +118,19 @@ func (repos RepositoryList) LoadAttributes(ctx context.Context) error {
for i := range repos {
repos[i].Owner = users[repos[i].OwnerID]
}
return nil
}

func (repos RepositoryList) LoadLanguageStats(ctx context.Context) error {
if len(repos) == 0 {
return nil
}

// Load primary language.
stats := make(LanguageStatList, 0, len(repos))
if err := db.GetEngine(ctx).
Where("`is_primary` = ? AND `language` != ?", true, "other").
In("`repo_id`", repoIDs).
In("`repo_id`", repos.IDs()).
Find(&stats); err != nil {
return fmt.Errorf("find primary languages: %w", err)
}
Expand All @@ -141,10 +143,18 @@ func (repos RepositoryList) LoadAttributes(ctx context.Context) error {
}
}
}

return nil
}

// LoadAttributes loads the attributes for the given RepositoryList
func (repos RepositoryList) LoadAttributes(ctx context.Context) error {
if err := repos.LoadOwners(ctx); err != nil {
return err
}

return repos.LoadLanguageStats(ctx)
}

// SearchRepoOptions holds the search options
type SearchRepoOptions struct {
db.ListOptions
Expand Down
15 changes: 12 additions & 3 deletions routers/api/v1/repo/fork.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,20 @@ func ListForks(ctx *context.APIContext) {
// "404":
// "$ref": "#/responses/notFound"

forks, err := repo_model.GetForks(ctx, ctx.Repo.Repository, utils.GetListOptions(ctx))
forks, total, err := repo_service.FindForks(ctx, ctx.Repo.Repository, ctx.Doer, utils.GetListOptions(ctx))
if err != nil {
ctx.Error(http.StatusInternalServerError, "GetForks", err)
ctx.Error(http.StatusInternalServerError, "FindForks", err)
return
}
if err := repo_model.RepositoryList(forks).LoadOwners(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadOwners", err)
return
}
if err := repo_model.RepositoryList(forks).LoadUnits(ctx); err != nil {
ctx.Error(http.StatusInternalServerError, "LoadUnits", err)
return
}

apiForks := make([]*api.Repository, len(forks))
for i, fork := range forks {
permission, err := access_model.GetUserRepoPermission(ctx, fork, ctx.Doer)
Expand All @@ -70,7 +79,7 @@ func ListForks(ctx *context.APIContext) {
apiForks[i] = convert.ToRepo(ctx, fork, permission)
}

ctx.SetTotalCountHeader(int64(ctx.Repo.Repository.NumForks))
ctx.SetTotalCountHeader(total)
ctx.JSON(http.StatusOK, apiForks)
}

Expand Down
22 changes: 11 additions & 11 deletions routers/web/auth/oauth2_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,12 @@ func (err errCallback) Error() string {
}

type userInfoResponse struct {
Sub string `json:"sub"`
Name string `json:"name"`
Username string `json:"preferred_username"`
Email string `json:"email"`
Picture string `json:"picture"`
Groups []string `json:"groups"`
Sub string `json:"sub"`
Name string `json:"name"`
PreferredUsername string `json:"preferred_username"`
Email string `json:"email"`
Picture string `json:"picture"`
Groups []string `json:"groups"`
}

// InfoOAuth manages request for userinfo endpoint
Expand All @@ -97,11 +97,11 @@ func InfoOAuth(ctx *context.Context) {
}

response := &userInfoResponse{
Sub: fmt.Sprint(ctx.Doer.ID),
Name: ctx.Doer.FullName,
Username: ctx.Doer.Name,
Email: ctx.Doer.Email,
Picture: ctx.Doer.AvatarLink(ctx),
Sub: fmt.Sprint(ctx.Doer.ID),
Name: ctx.Doer.FullName,
PreferredUsername: ctx.Doer.Name,
Email: ctx.Doer.Email,
Picture: ctx.Doer.AvatarLink(ctx),
}

groups, err := oauth2_provider.GetOAuthGroupsForUser(ctx, ctx.Doer)
Expand Down
23 changes: 11 additions & 12 deletions routers/web/repo/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -1151,26 +1151,25 @@ func Forks(ctx *context.Context) {
if page <= 0 {
page = 1
}
pageSize := setting.ItemsPerPage

pager := context.NewPagination(ctx.Repo.Repository.NumForks, setting.ItemsPerPage, page, 5)
ctx.Data["Page"] = pager

forks, err := repo_model.GetForks(ctx, ctx.Repo.Repository, db.ListOptions{
Page: pager.Paginater.Current(),
PageSize: setting.ItemsPerPage,
forks, total, err := repo_service.FindForks(ctx, ctx.Repo.Repository, ctx.Doer, db.ListOptions{
Page: page,
PageSize: pageSize,
})
if err != nil {
ctx.ServerError("GetForks", err)
ctx.ServerError("FindForks", err)
return
}

for _, fork := range forks {
if err = fork.LoadOwner(ctx); err != nil {
ctx.ServerError("LoadOwner", err)
return
}
if err := repo_model.RepositoryList(forks).LoadOwners(ctx); err != nil {
ctx.ServerError("LoadAttributes", err)
return
}

pager := context.NewPagination(int(total), pageSize, page, 5)
ctx.Data["Page"] = pager

ctx.Data["Forks"] = forks

ctx.HTML(http.StatusOK, tplForks)
Expand Down
10 changes: 10 additions & 0 deletions services/auth/basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package auth

import (
"errors"
"net/http"
"strings"

Expand Down Expand Up @@ -141,6 +142,15 @@ func (b *Basic) Verify(req *http.Request, w http.ResponseWriter, store DataStore
}

if skipper, ok := source.Cfg.(LocalTwoFASkipper); !ok || !skipper.IsSkipLocalTwoFA() {
// Check if the user has webAuthn registration
hasWebAuthn, err := auth_model.HasWebAuthnRegistrationsByUID(req.Context(), u.ID)
if err != nil {
return nil, err
}
if hasWebAuthn {
return nil, errors.New("Basic authorization is not allowed while webAuthn enrolled")
}

if err := validateTOTP(req, u); err != nil {
return nil, err
}
Expand Down
24 changes: 24 additions & 0 deletions services/repository/fork.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/models/db"
git_model "code.gitea.io/gitea/models/git"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/models/unit"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
Expand All @@ -20,6 +21,8 @@ import (
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
notify_service "code.gitea.io/gitea/services/notify"

"xorm.io/builder"
)

// ErrForkAlreadyExist represents a "ForkAlreadyExist" kind of error.
Expand Down Expand Up @@ -247,3 +250,24 @@ func ConvertForkToNormalRepository(ctx context.Context, repo *repo_model.Reposit

return err
}

type findForksOptions struct {
db.ListOptions
RepoID int64
Doer *user_model.User
}

func (opts findForksOptions) ToConds() builder.Cond {
return builder.Eq{"fork_id": opts.RepoID}.And(
repo_model.AccessibleRepositoryCondition(opts.Doer, unit.TypeInvalid),
)
}

// FindForks returns all the forks of the repository
func FindForks(ctx context.Context, repo *repo_model.Repository, doer *user_model.User, listOptions db.ListOptions) ([]*repo_model.Repository, int64, error) {
return db.FindAndCount[repo_model.Repository](ctx, findForksOptions{
ListOptions: listOptions,
RepoID: repo.ID,
Doer: doer,
})
}
8 changes: 5 additions & 3 deletions templates/repo/forks.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@
<h2 class="ui dividing header">
{{ctx.Locale.Tr "repo.forks"}}
</h2>
<div class="flex-list">
{{range .Forks}}
<div class="tw-flex tw-items-center tw-py-2">
<span class="tw-mr-1">{{ctx.AvatarUtils.Avatar .Owner}}</span>
<a href="{{.Owner.HomeLink}}">{{.Owner.Name}}</a> / <a href="{{.Link}}">{{.Name}}</a>
<div class="flex-item tw-border-0 repo-fork-item">
<span>{{ctx.AvatarUtils.Avatar .Owner}}</span>
<span><a href="{{.Owner.HomeLink}}">{{.Owner.Name}}</a> / <a href="{{.Link}}">{{.Name}}</a></span>
</div>
{{end}}
</div>
</div>

{{template "base/paginate" .}}
Expand Down
80 changes: 80 additions & 0 deletions tests/integration/api_fork_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,92 @@ import (
"net/http"
"testing"

"code.gitea.io/gitea/models"
auth_model "code.gitea.io/gitea/models/auth"
"code.gitea.io/gitea/models/db"
org_model "code.gitea.io/gitea/models/organization"
"code.gitea.io/gitea/models/unittest"
user_model "code.gitea.io/gitea/models/user"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/tests"

"github.com/stretchr/testify/assert"
)

func TestCreateForkNoLogin(t *testing.T) {
defer tests.PrepareTestEnv(t)()
req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/forks", &api.CreateForkOption{})
MakeRequest(t, req, http.StatusUnauthorized)
}

func TestAPIForkListLimitedAndPrivateRepos(t *testing.T) {
defer tests.PrepareTestEnv(t)()

user1Sess := loginUser(t, "user1")
user1 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user1"})

// fork into a limited org
limitedOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 22})
assert.EqualValues(t, api.VisibleTypeLimited, limitedOrg.Visibility)

ownerTeam1, err := org_model.OrgFromUser(limitedOrg).GetOwnerTeam(db.DefaultContext)
assert.NoError(t, err)
assert.NoError(t, models.AddTeamMember(db.DefaultContext, ownerTeam1, user1))
user1Token := getTokenForLoggedInUser(t, user1Sess, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteOrganization)
req := NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/forks", &api.CreateForkOption{
Organization: &limitedOrg.Name,
}).AddTokenAuth(user1Token)
MakeRequest(t, req, http.StatusAccepted)

// fork into a private org
user4Sess := loginUser(t, "user4")
user4 := unittest.AssertExistsAndLoadBean(t, &user_model.User{Name: "user4"})
privateOrg := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 23})
assert.EqualValues(t, api.VisibleTypePrivate, privateOrg.Visibility)

ownerTeam2, err := org_model.OrgFromUser(privateOrg).GetOwnerTeam(db.DefaultContext)
assert.NoError(t, err)
assert.NoError(t, models.AddTeamMember(db.DefaultContext, ownerTeam2, user4))
user4Token := getTokenForLoggedInUser(t, user4Sess, auth_model.AccessTokenScopeWriteRepository, auth_model.AccessTokenScopeWriteOrganization)
req = NewRequestWithJSON(t, "POST", "/api/v1/repos/user2/repo1/forks", &api.CreateForkOption{
Organization: &privateOrg.Name,
}).AddTokenAuth(user4Token)
MakeRequest(t, req, http.StatusAccepted)

t.Run("Anonymous", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()

req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/forks")
resp := MakeRequest(t, req, http.StatusOK)

var forks []*api.Repository
DecodeJSON(t, resp, &forks)

assert.Empty(t, forks)
assert.EqualValues(t, "0", resp.Header().Get("X-Total-Count"))
})

t.Run("Logged in", func(t *testing.T) {
defer tests.PrintCurrentTest(t)()

req := NewRequest(t, "GET", "/api/v1/repos/user2/repo1/forks").AddTokenAuth(user1Token)
resp := MakeRequest(t, req, http.StatusOK)

var forks []*api.Repository
DecodeJSON(t, resp, &forks)

assert.Len(t, forks, 1)
assert.EqualValues(t, "1", resp.Header().Get("X-Total-Count"))

assert.NoError(t, models.AddTeamMember(db.DefaultContext, ownerTeam2, user1))

req = NewRequest(t, "GET", "/api/v1/repos/user2/repo1/forks").AddTokenAuth(user1Token)
resp = MakeRequest(t, req, http.StatusOK)

forks = []*api.Repository{}
DecodeJSON(t, resp, &forks)

assert.Len(t, forks, 2)
assert.EqualValues(t, "2", resp.Header().Get("X-Total-Count"))
})
}
Loading

0 comments on commit 4ea582a

Please sign in to comment.