Compare commits

..

18 Commits

Author SHA1 Message Date
techknowlogick
38d8b8cf49 1.5.1 Changelog (#4851)
As title
2018-09-03 08:53:34 +08:00
SagePtr
0358a40625 Fix missing release title in webhook (#4783) (#4800) 2018-08-26 15:07:44 -04:00
techknowlogick
99ce0bfcd7 Don't disclose emails of all users when sending out emails (#4784)
Backport (#4664)
2018-08-24 14:37:30 -04:00
Lanre Adelowo
3fbcdd9e25 Make sure to reset commit count in the cache on mirror syncing (#4720) (#4770)
* Make sure to reset commit count in the cache on mirror syncing

* reset count of commits in all branches
2018-08-23 22:23:21 +08:00
Lanre Adelowo
e9def84bf2 Fixed bug where team with admin privelege type doesn't get any unit attached to the team (#4719) (#4759) 2018-08-21 15:29:25 -04:00
Lauris BH
066515429f Improve URL validation for external wiki and external issues (#4710) (#4740)
* Improve URL validation for external wiki  and external issues

* Do not allow also localhost address for external URLs
2018-08-17 20:21:20 -04:00
SagePtr
12c04a85f2 Fix failure on creating pull request with assignees (#4419) (#4727) 2018-08-16 13:46:06 -04:00
SagePtr
a345023d0a Fix incorrect caption of webhook setting (#4701) (#4718) 2018-08-15 19:06:56 +03:00
SagePtr
052aa54b2b Make cookies HttpOnly and obey COOKIE_SECURE flag (#4707) 2018-08-14 16:19:20 -04:00
SagePtr
cbe8a1f0e6 Hide org/create menu item in Dashboard if user has no rights (#4678) (#4686) 2018-08-13 14:22:15 +03:00
techknowlogick
cfe6941905 1.5.0 changelog 2018-08-10 13:16:53 -04:00
Lunny Xiao
eb8c611b1d Site admin could create repos even MAX_CREATION_LIMIT=0 (#4645) (#4650)
* site admin could create repos even MAX_CREATION_LIMIT=0

* Optimize if structure
2018-08-09 06:31:57 +03:00
techknowlogick
b1eaeeb0cd Backport Remove link to GitHub issues in 404 template #4639 (#4644) 2018-08-08 16:20:05 -04:00
SagePtr
15a403bf97 Push whitelist now doesn't apply to branch deletion (#4601) (#4640) 2018-08-08 11:19:13 +03:00
Lunny Xiao
099028681e fix bugs when too many IN variables (#4594) (#4597) 2018-08-02 14:48:39 -04:00
Dingjun
940e30bcd4 fix panic issue on update avatar email (#4580) (#4590)
fix #4580

back port PR for release/v1.5,  refer to #4581
2018-08-01 21:34:57 +08:00
SagePtr
5a7830e0e8 Fix incorrect MergeWhitelistTeamIDs check in CanUserMerge function (#4519) (#4526) 2018-07-27 22:11:53 +03:00
SagePtr
dae065ea68 Fix out-of-transaction query in removeOrgUser (#4521) (#4524) 2018-07-27 08:57:49 -04:00
23 changed files with 483 additions and 152 deletions

View File

@@ -4,9 +4,25 @@ This changelog goes through all 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.io). been added to each release, please refer to the [blog](https://blog.gitea.io).
## [1.5.0-RC2](https://github.com/go-gitea/gitea/releases/tag/v1.5.0-rc2) - 2018-07-21 ## [1.5.1](https://github.com/go-gitea/gitea/releases/tag/v1.5.1) - 2018-09-03
* SECURITY
* Don't disclose emails of all users when sending out emails (#4784)
* Improve URL validation for external wiki and external issues (#4710) (#4740)
* Make cookies HttpOnly and obey COOKIE_SECURE flag (#4706) (#4707)
* BUGFIXES
* Fix missing release title in webhook (#4783) (#4800)
* Make sure to reset commit count in the cache on mirror syncing (#4770)
* Fixed bug where team with admin privelege type doesn't get any unit (#4759)
* Fix failure on creating pull request with assignees (#4583) (#4727)
* Hide org/create menu item in Dashboard if user has no rights (#4678) (#4686)
* TRANSLATION
* Fix incorrect caption of webhook setting (#4701) (#4718)
## [1.5.0](https://github.com/go-gitea/gitea/releases/tag/v1.5.0) - 2018-08-10
* SECURITY * SECURITY
* Check that repositories can only be migrated to own user or organizations (#4366) (#4370) * Check that repositories can only be migrated to own user or organizations (#4366) (#4370)
* Limit uploaded avatar image-size to 4096px x 3072px by default (#4353)
* Do not allow to reuse TOTP passcode (#3878)
* BUGFIXES * BUGFIXES
* Fix column droping for MSSQL that need new transaction for that (#4440) (#4484) * Fix column droping for MSSQL that need new transaction for that (#4440) (#4484)
* Redirect to correct page after using scratch token (#4458) (#4472) * Redirect to correct page after using scratch token (#4458) (#4472)
@@ -15,11 +31,12 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
* Add default merge options when adding new repository (#4369) (#4373) * Add default merge options when adding new repository (#4369) (#4373)
* Fix repository last updated time update when delete a user who watched the repo (#4363) (#4371) * Fix repository last updated time update when delete a user who watched the repo (#4363) (#4371)
* Fix html entity escaping in branch deletion message (#4471) (#4485) * Fix html entity escaping in branch deletion message (#4471) (#4485)
* Fix out-of-transaction query in removeOrgUser (#4521) (#4524)
## [1.5.0-RC1](https://github.com/go-gitea/gitea/releases/tag/v1.5.0-rc1) - 2018-07-04 * Fix incorrect MergeWhitelistTeamIDs check in CanUserMerge function (#4519)
* SECURITY * Fix panic issue on update avatar email (#4580) (#4590)
* Limit uploaded avatar image-size to 4096px x 3072px by default (#4353) * Fix bugs when too many IN variables (#4594) (#4597)
* Do not allow to reuse TOTP passcode (#3878) * Push whitelist now doesn't apply to branch deletion (#4601) (#4640)
* Site admin could create repos even MAX_CREATION_LIMIT=0 (#4645) (#4650)
* FEATURE * FEATURE
* Add cli commands to regen hooks & keys (#3979) * Add cli commands to regen hooks & keys (#3979)
* Add support for FIDO U2F (#3971) * Add support for FIDO U2F (#3971)

View File

@@ -74,7 +74,7 @@ func (protectBranch *ProtectedBranch) CanUserMerge(userID int64) bool {
return true return true
} }
if len(protectBranch.WhitelistTeamIDs) == 0 { if len(protectBranch.MergeWhitelistTeamIDs) == 0 {
return false return false
} }
@@ -184,6 +184,24 @@ func (repo *Repository) IsProtectedBranch(branchName string, doer *User) (bool,
BranchName: branchName, BranchName: branchName,
} }
has, err := x.Exist(protectedBranch)
if err != nil {
return true, err
}
return has, nil
}
// IsProtectedBranchForPush checks if branch is protected for push
func (repo *Repository) IsProtectedBranchForPush(branchName string, doer *User) (bool, error) {
if doer == nil {
return true, nil
}
protectedBranch := &ProtectedBranch{
RepoID: repo.ID,
BranchName: branchName,
}
has, err := x.Get(protectedBranch) has, err := x.Get(protectedBranch)
if err != nil { if err != nil {
return true, err return true, err

View File

@@ -950,7 +950,7 @@ func newIssue(e *xorm.Session, doer *User, opts NewIssueOptions) (err error) {
// Insert the assignees // Insert the assignees
for _, assigneeID := range opts.AssigneeIDs { for _, assigneeID := range opts.AssigneeIDs {
err = opts.Issue.changeAssignee(e, doer, assigneeID) err = opts.Issue.changeAssignee(e, doer, assigneeID, true)
if err != nil { if err != nil {
return err return err
} }

View File

@@ -134,14 +134,14 @@ func (issue *Issue) ChangeAssignee(doer *User, assigneeID int64) (err error) {
return err return err
} }
if err := issue.changeAssignee(sess, doer, assigneeID); err != nil { if err := issue.changeAssignee(sess, doer, assigneeID, false); err != nil {
return err return err
} }
return sess.Commit() return sess.Commit()
} }
func (issue *Issue) changeAssignee(sess *xorm.Session, doer *User, assigneeID int64) (err error) { func (issue *Issue) changeAssignee(sess *xorm.Session, doer *User, assigneeID int64, isCreate bool) (err error) {
// Update the assignee // Update the assignee
removed, err := updateIssueAssignee(sess, issue, assigneeID) removed, err := updateIssueAssignee(sess, issue, assigneeID)
@@ -161,6 +161,10 @@ func (issue *Issue) changeAssignee(sess *xorm.Session, doer *User, assigneeID in
mode, _ := accessLevel(sess, doer.ID, issue.Repo) mode, _ := accessLevel(sess, doer.ID, issue.Repo)
if issue.IsPull { if issue.IsPull {
// if pull request is in the middle of creation - don't call webhook
if isCreate {
return nil
}
if err = issue.loadPullRequest(sess); err != nil { if err = issue.loadPullRequest(sess); err != nil {
return fmt.Errorf("loadPullRequest: %v", err) return fmt.Errorf("loadPullRequest: %v", err)
} }

View File

@@ -9,6 +9,11 @@ import "fmt"
// IssueList defines a list of issues // IssueList defines a list of issues
type IssueList []*Issue type IssueList []*Issue
const (
// default variables number on IN () in SQL
defaultMaxInSize = 50
)
func (issues IssueList) getRepoIDs() []int64 { func (issues IssueList) getRepoIDs() []int64 {
repoIDs := make(map[int64]struct{}, len(issues)) repoIDs := make(map[int64]struct{}, len(issues))
for _, issue := range issues { for _, issue := range issues {
@@ -26,12 +31,21 @@ func (issues IssueList) loadRepositories(e Engine) ([]*Repository, error) {
repoIDs := issues.getRepoIDs() repoIDs := issues.getRepoIDs()
repoMaps := make(map[int64]*Repository, len(repoIDs)) repoMaps := make(map[int64]*Repository, len(repoIDs))
var left = len(repoIDs)
for left > 0 {
var limit = defaultMaxInSize
if left < limit {
limit = left
}
err := e. err := e.
In("id", repoIDs). In("id", repoIDs[:limit]).
Find(&repoMaps) Find(&repoMaps)
if err != nil { if err != nil {
return nil, fmt.Errorf("find repository: %v", err) return nil, fmt.Errorf("find repository: %v", err)
} }
left = left - limit
repoIDs = repoIDs[limit:]
}
for _, issue := range issues { for _, issue := range issues {
issue.Repo = repoMaps[issue.RepoID] issue.Repo = repoMaps[issue.RepoID]
@@ -61,12 +75,21 @@ func (issues IssueList) loadPosters(e Engine) error {
posterIDs := issues.getPosterIDs() posterIDs := issues.getPosterIDs()
posterMaps := make(map[int64]*User, len(posterIDs)) posterMaps := make(map[int64]*User, len(posterIDs))
var left = len(posterIDs)
for left > 0 {
var limit = defaultMaxInSize
if left < limit {
limit = left
}
err := e. err := e.
In("id", posterIDs). In("id", posterIDs[:limit]).
Find(&posterMaps) Find(&posterMaps)
if err != nil { if err != nil {
return err return err
} }
left = left - limit
posterIDs = posterIDs[limit:]
}
for _, issue := range issues { for _, issue := range issues {
if issue.PosterID <= 0 { if issue.PosterID <= 0 {
@@ -99,24 +122,35 @@ func (issues IssueList) loadLabels(e Engine) error {
} }
var issueLabels = make(map[int64][]*Label, len(issues)*3) var issueLabels = make(map[int64][]*Label, len(issues)*3)
var issueIDs = issues.getIssueIDs()
var left = len(issueIDs)
for left > 0 {
var limit = defaultMaxInSize
if left < limit {
limit = left
}
rows, err := e.Table("label"). rows, err := e.Table("label").
Join("LEFT", "issue_label", "issue_label.label_id = label.id"). Join("LEFT", "issue_label", "issue_label.label_id = label.id").
In("issue_label.issue_id", issues.getIssueIDs()). In("issue_label.issue_id", issueIDs[:limit]).
Asc("label.name"). Asc("label.name").
Rows(new(LabelIssue)) Rows(new(LabelIssue))
if err != nil { if err != nil {
return err return err
} }
defer rows.Close()
for rows.Next() { for rows.Next() {
var labelIssue LabelIssue var labelIssue LabelIssue
err = rows.Scan(&labelIssue) err = rows.Scan(&labelIssue)
if err != nil { if err != nil {
rows.Close()
return err return err
} }
issueLabels[labelIssue.IssueLabel.IssueID] = append(issueLabels[labelIssue.IssueLabel.IssueID], labelIssue.Label) issueLabels[labelIssue.IssueLabel.IssueID] = append(issueLabels[labelIssue.IssueLabel.IssueID], labelIssue.Label)
} }
rows.Close()
left = left - limit
issueIDs = issueIDs[limit:]
}
for _, issue := range issues { for _, issue := range issues {
issue.Labels = issueLabels[issue.ID] issue.Labels = issueLabels[issue.ID]
@@ -141,12 +175,21 @@ func (issues IssueList) loadMilestones(e Engine) error {
} }
milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs)) milestoneMaps := make(map[int64]*Milestone, len(milestoneIDs))
var left = len(milestoneIDs)
for left > 0 {
var limit = defaultMaxInSize
if left < limit {
limit = left
}
err := e. err := e.
In("id", milestoneIDs). In("id", milestoneIDs[:limit]).
Find(&milestoneMaps) Find(&milestoneMaps)
if err != nil { if err != nil {
return err return err
} }
left = left - limit
milestoneIDs = milestoneIDs[limit:]
}
for _, issue := range issues { for _, issue := range issues {
issue.Milestone = milestoneMaps[issue.MilestoneID] issue.Milestone = milestoneMaps[issue.MilestoneID]
@@ -165,24 +208,36 @@ func (issues IssueList) loadAssignees(e Engine) error {
} }
var assignees = make(map[int64][]*User, len(issues)) var assignees = make(map[int64][]*User, len(issues))
var issueIDs = issues.getIssueIDs()
var left = len(issueIDs)
for left > 0 {
var limit = defaultMaxInSize
if left < limit {
limit = left
}
rows, err := e.Table("issue_assignees"). rows, err := e.Table("issue_assignees").
Join("INNER", "`user`", "`user`.id = `issue_assignees`.assignee_id"). Join("INNER", "`user`", "`user`.id = `issue_assignees`.assignee_id").
In("`issue_assignees`.issue_id", issues.getIssueIDs()). In("`issue_assignees`.issue_id", issueIDs[:limit]).
Rows(new(AssigneeIssue)) Rows(new(AssigneeIssue))
if err != nil { if err != nil {
return err return err
} }
defer rows.Close()
for rows.Next() { for rows.Next() {
var assigneeIssue AssigneeIssue var assigneeIssue AssigneeIssue
err = rows.Scan(&assigneeIssue) err = rows.Scan(&assigneeIssue)
if err != nil { if err != nil {
rows.Close()
return err return err
} }
assignees[assigneeIssue.IssueAssignee.IssueID] = append(assignees[assigneeIssue.IssueAssignee.IssueID], assigneeIssue.Assignee) assignees[assigneeIssue.IssueAssignee.IssueID] = append(assignees[assigneeIssue.IssueAssignee.IssueID], assigneeIssue.Assignee)
} }
rows.Close()
left = left - limit
issueIDs = issueIDs[limit:]
}
for _, issue := range issues { for _, issue := range issues {
issue.Assignees = assignees[issue.ID] issue.Assignees = assignees[issue.ID]
@@ -207,23 +262,34 @@ func (issues IssueList) loadPullRequests(e Engine) error {
} }
pullRequestMaps := make(map[int64]*PullRequest, len(issuesIDs)) pullRequestMaps := make(map[int64]*PullRequest, len(issuesIDs))
var left = len(issuesIDs)
for left > 0 {
var limit = defaultMaxInSize
if left < limit {
limit = left
}
rows, err := e. rows, err := e.
In("issue_id", issuesIDs). In("issue_id", issuesIDs[:limit]).
Rows(new(PullRequest)) Rows(new(PullRequest))
if err != nil { if err != nil {
return err return err
} }
defer rows.Close()
for rows.Next() { for rows.Next() {
var pr PullRequest var pr PullRequest
err = rows.Scan(&pr) err = rows.Scan(&pr)
if err != nil { if err != nil {
rows.Close()
return err return err
} }
pullRequestMaps[pr.IssueID] = &pr pullRequestMaps[pr.IssueID] = &pr
} }
rows.Close()
left = left - limit
issuesIDs = issuesIDs[limit:]
}
for _, issue := range issues { for _, issue := range issues {
issue.PullRequest = pullRequestMaps[issue.ID] issue.PullRequest = pullRequestMaps[issue.ID]
} }
@@ -236,24 +302,36 @@ func (issues IssueList) loadAttachments(e Engine) (err error) {
} }
var attachments = make(map[int64][]*Attachment, len(issues)) var attachments = make(map[int64][]*Attachment, len(issues))
var issuesIDs = issues.getIssueIDs()
var left = len(issuesIDs)
for left > 0 {
var limit = defaultMaxInSize
if left < limit {
limit = left
}
rows, err := e.Table("attachment"). rows, err := e.Table("attachment").
Join("INNER", "issue", "issue.id = attachment.issue_id"). Join("INNER", "issue", "issue.id = attachment.issue_id").
In("issue.id", issues.getIssueIDs()). In("issue.id", issuesIDs[:limit]).
Rows(new(Attachment)) Rows(new(Attachment))
if err != nil { if err != nil {
return err return err
} }
defer rows.Close()
for rows.Next() { for rows.Next() {
var attachment Attachment var attachment Attachment
err = rows.Scan(&attachment) err = rows.Scan(&attachment)
if err != nil { if err != nil {
rows.Close()
return err return err
} }
attachments[attachment.IssueID] = append(attachments[attachment.IssueID], &attachment) attachments[attachment.IssueID] = append(attachments[attachment.IssueID], &attachment)
} }
rows.Close()
left = left - limit
issuesIDs = issuesIDs[limit:]
}
for _, issue := range issues { for _, issue := range issues {
issue.Attachments = attachments[issue.ID] issue.Attachments = attachments[issue.ID]
} }
@@ -266,23 +344,34 @@ func (issues IssueList) loadComments(e Engine) (err error) {
} }
var comments = make(map[int64][]*Comment, len(issues)) var comments = make(map[int64][]*Comment, len(issues))
var issuesIDs = issues.getIssueIDs()
var left = len(issuesIDs)
for left > 0 {
var limit = defaultMaxInSize
if left < limit {
limit = left
}
rows, err := e.Table("comment"). rows, err := e.Table("comment").
Join("INNER", "issue", "issue.id = comment.issue_id"). Join("INNER", "issue", "issue.id = comment.issue_id").
In("issue.id", issues.getIssueIDs()). In("issue.id", issuesIDs[:limit]).
Rows(new(Comment)) Rows(new(Comment))
if err != nil { if err != nil {
return err return err
} }
defer rows.Close()
for rows.Next() { for rows.Next() {
var comment Comment var comment Comment
err = rows.Scan(&comment) err = rows.Scan(&comment)
if err != nil { if err != nil {
rows.Close()
return err return err
} }
comments[comment.IssueID] = append(comments[comment.IssueID], &comment) comments[comment.IssueID] = append(comments[comment.IssueID], &comment)
} }
rows.Close()
left = left - limit
issuesIDs = issuesIDs[limit:]
}
for _, issue := range issues { for _, issue := range issues {
issue.Comments = comments[issue.ID] issue.Comments = comments[issue.ID]
@@ -307,26 +396,36 @@ func (issues IssueList) loadTotalTrackedTimes(e Engine) (err error) {
} }
} }
var left = len(ids)
for left > 0 {
var limit = defaultMaxInSize
if left < limit {
limit = left
}
// select issue_id, sum(time) from tracked_time where issue_id in (<issue ids in current page>) group by issue_id // select issue_id, sum(time) from tracked_time where issue_id in (<issue ids in current page>) group by issue_id
rows, err := e.Table("tracked_time"). rows, err := e.Table("tracked_time").
Select("issue_id, sum(time) as time"). Select("issue_id, sum(time) as time").
In("issue_id", ids). In("issue_id", ids[:limit]).
GroupBy("issue_id"). GroupBy("issue_id").
Rows(new(totalTimesByIssue)) Rows(new(totalTimesByIssue))
if err != nil { if err != nil {
return err return err
} }
defer rows.Close()
for rows.Next() { for rows.Next() {
var totalTime totalTimesByIssue var totalTime totalTimesByIssue
err = rows.Scan(&totalTime) err = rows.Scan(&totalTime)
if err != nil { if err != nil {
rows.Close()
return err return err
} }
trackedTimes[totalTime.IssueID] = totalTime.Time trackedTimes[totalTime.IssueID] = totalTime.Time
} }
rows.Close()
left = left - limit
ids = ids[limit:]
}
for _, issue := range issues { for _, issue := range issues {
issue.TotalTrackedTime = trackedTimes[issue.ID] issue.TotalTrackedTime = trackedTimes[issue.ID]

View File

@@ -1,4 +1,5 @@
// Copyright 2016 The Gogs Authors. All rights reserved. // Copyright 2016 The Gogs Authors. All rights reserved.
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@@ -87,7 +88,9 @@ func mailIssueCommentToParticipants(e Engine, issue *Issue, doer *User, content
names = append(names, participants[i].Name) names = append(names, participants[i].Name)
} }
SendIssueCommentMail(issue, doer, content, comment, tos) for _, to := range tos {
SendIssueCommentMail(issue, doer, content, comment, []string{to})
}
// Mail mentioned people and exclude watchers. // Mail mentioned people and exclude watchers.
names = append(names, doer.Name) names = append(names, doer.Name)
@@ -99,7 +102,12 @@ func mailIssueCommentToParticipants(e Engine, issue *Issue, doer *User, content
tos = append(tos, mentions[i]) tos = append(tos, mentions[i])
} }
SendIssueMentionMail(issue, doer, content, comment, getUserEmailsByNames(e, tos))
emails := getUserEmailsByNames(e, tos)
for _, to := range emails {
SendIssueMentionMail(issue, doer, content, comment, []string{to})
}
return nil return nil
} }

View File

@@ -454,7 +454,7 @@ func AddOrgUser(orgID, uid int64) error {
func removeOrgUser(sess *xorm.Session, orgID, userID int64) error { func removeOrgUser(sess *xorm.Session, orgID, userID int64) error {
ou := new(OrgUser) ou := new(OrgUser)
has, err := x. has, err := sess.
Where("uid=?", userID). Where("uid=?", userID).
And("org_id=?", orgID). And("org_id=?", orgID).
Get(ou) Get(ou)

View File

@@ -88,6 +88,7 @@ func (r *Release) APIFormat() *api.Release {
ID: r.ID, ID: r.ID,
TagName: r.TagName, TagName: r.TagName,
Target: r.Target, Target: r.Target,
Title: r.Title,
Note: r.Note, Note: r.Note,
URL: r.APIURL(), URL: r.APIURL(),
TarURL: r.TarURL(), TarURL: r.TarURL(),

View File

@@ -1407,7 +1407,7 @@ func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err err
// CreateRepository creates a repository for the user/organization u. // CreateRepository creates a repository for the user/organization u.
func CreateRepository(doer, u *User, opts CreateRepoOptions) (_ *Repository, err error) { func CreateRepository(doer, u *User, opts CreateRepoOptions) (_ *Repository, err error) {
if !u.CanCreateRepo() { if !doer.IsAdmin && !u.CanCreateRepo() {
return nil, ErrReachLimitOfRepo{u.MaxRepoCreation} return nil, ErrReachLimitOfRepo{u.MaxRepoCreation}
} }

View File

@@ -9,6 +9,7 @@ import (
"time" "time"
"code.gitea.io/git" "code.gitea.io/git"
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/process" "code.gitea.io/gitea/modules/process"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@@ -180,6 +181,16 @@ func (m *Mirror) runSync() bool {
} }
} }
branches, err := m.Repo.GetBranches()
if err != nil {
log.Error(4, "GetBranches: %v", err)
return false
}
for i := range branches {
cache.Remove(m.Repo.GetCommitsCountCacheKey(branches[i].Name, true))
}
m.UpdatedUnix = util.TimeStampNow() m.UpdatedUnix = util.TimeStampNow()
return true return true
} }

View File

@@ -85,9 +85,9 @@ func (r *Repository) CanCreateBranch() bool {
} }
// CanCommitToBranch returns true if repository is editable and user has proper access level // CanCommitToBranch returns true if repository is editable and user has proper access level
// and branch is not protected // and branch is not protected for push
func (r *Repository) CanCommitToBranch(doer *models.User) (bool, error) { func (r *Repository) CanCommitToBranch(doer *models.User) (bool, error) {
protectedBranch, err := r.Repository.IsProtectedBranch(r.BranchName, doer) protectedBranch, err := r.Repository.IsProtectedBranchForPush(r.BranchName, doer)
if err != nil { if err != nil {
return false, err return false, err
} }

View File

@@ -6,7 +6,6 @@ package validation
import ( import (
"fmt" "fmt"
"net/url"
"regexp" "regexp"
"strings" "strings"
@@ -70,14 +69,10 @@ func addValidURLBindingRule() {
}, },
IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) { IsValid: func(errs binding.Errors, name string, val interface{}) (bool, binding.Errors) {
str := fmt.Sprintf("%v", val) str := fmt.Sprintf("%v", val)
if len(str) != 0 { if len(str) != 0 && !IsValidURL(str) {
if u, err := url.ParseRequestURI(str); err != nil ||
(u.Scheme != "http" && u.Scheme != "https") ||
!validPort(portOnly(u.Host)) {
errs.Add([]string{name}, binding.ERR_URL, "Url") errs.Add([]string{name}, binding.ERR_URL, "Url")
return false, errs return false, errs
} }
}
return true, errs return true, errs
}, },

View File

@@ -0,0 +1,77 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package validation
import (
"net"
"net/url"
"strings"
"code.gitea.io/gitea/modules/setting"
)
var loopbackIPBlocks []*net.IPNet
func init() {
for _, cidr := range []string{
"127.0.0.0/8", // IPv4 loopback
"::1/128", // IPv6 loopback
} {
if _, block, err := net.ParseCIDR(cidr); err == nil {
loopbackIPBlocks = append(loopbackIPBlocks, block)
}
}
}
func isLoopbackIP(ip string) bool {
pip := net.ParseIP(ip)
if pip == nil {
return false
}
for _, block := range loopbackIPBlocks {
if block.Contains(pip) {
return true
}
}
return false
}
// IsValidURL checks if URL is valid
func IsValidURL(uri string) bool {
if u, err := url.ParseRequestURI(uri); err != nil ||
(u.Scheme != "http" && u.Scheme != "https") ||
!validPort(portOnly(u.Host)) {
return false
}
return true
}
// IsAPIURL checks if URL is current Gitea instance API URL
func IsAPIURL(uri string) bool {
return strings.HasPrefix(strings.ToLower(uri), strings.ToLower(setting.AppURL+"api"))
}
// IsValidExternalURL checks if URL is valid external URL
func IsValidExternalURL(uri string) bool {
if !IsValidURL(uri) || IsAPIURL(uri) {
return false
}
u, err := url.ParseRequestURI(uri)
if err != nil {
return false
}
// Currently check only if not loopback IP is provided to keep compatibility
if isLoopbackIP(u.Hostname()) || strings.ToLower(u.Hostname()) == "localhost" {
return false
}
// TODO: Later it should be added to allow local network IP addreses
// only if allowed by special setting
return true
}

View File

@@ -0,0 +1,90 @@
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package validation
import (
"testing"
"github.com/stretchr/testify/assert"
"code.gitea.io/gitea/modules/setting"
)
func Test_IsValidURL(t *testing.T) {
cases := []struct {
description string
url string
valid bool
}{
{
description: "Empty URL",
url: "",
valid: false,
},
{
description: "Loobpack IPv4 URL",
url: "http://127.0.1.1:5678/",
valid: true,
},
{
description: "Loobpack IPv6 URL",
url: "https://[::1]/",
valid: true,
},
{
description: "Missing semicolon after schema",
url: "http//meh/",
valid: false,
},
}
for _, testCase := range cases {
t.Run(testCase.description, func(t *testing.T) {
assert.Equal(t, testCase.valid, IsValidURL(testCase.url))
})
}
}
func Test_IsValidExternalURL(t *testing.T) {
setting.AppURL = "https://try.gitea.io/"
cases := []struct {
description string
url string
valid bool
}{
{
description: "Current instance URL",
url: "https://try.gitea.io/test",
valid: true,
},
{
description: "Loobpack IPv4 URL",
url: "http://127.0.1.1:5678/",
valid: false,
},
{
description: "Current instance API URL",
url: "https://try.gitea.io/api/v1/user/follow",
valid: false,
},
{
description: "Local network URL",
url: "http://192.168.1.2/api/v1/user/follow",
valid: true,
},
{
description: "Local URL",
url: "http://LOCALHOST:1234/whatever",
valid: false,
},
}
for _, testCase := range cases {
t.Run(testCase.description, func(t *testing.T) {
assert.Equal(t, testCase.valid, IsValidExternalURL(testCase.url))
})
}
}

View File

@@ -943,6 +943,7 @@ settings.external_tracker_url = External Issue Tracker URL
settings.external_tracker_url_error = The external issue tracker URL is not a valid URL. settings.external_tracker_url_error = The external issue tracker URL is not a valid URL.
settings.external_tracker_url_desc = Visitors are redirected to the external issue tracker URL when clicking on the issues tab. settings.external_tracker_url_desc = Visitors are redirected to the external issue tracker URL when clicking on the issues tab.
settings.tracker_url_format = External Issue Tracker URL Format settings.tracker_url_format = External Issue Tracker URL Format
settings.tracker_url_format_error = The external issue tracker URL format is not a valid URL.
settings.tracker_issue_style = External Issue Tracker Number Format settings.tracker_issue_style = External Issue Tracker Number Format
settings.tracker_issue_style.numeric = Numeric settings.tracker_issue_style.numeric = Numeric
settings.tracker_issue_style.alphanumeric = Alphanumeric settings.tracker_issue_style.alphanumeric = Alphanumeric
@@ -1042,8 +1043,8 @@ settings.event_push = Push
settings.event_push_desc = Git push to a repository. settings.event_push_desc = Git push to a repository.
settings.event_repository = Repository settings.event_repository = Repository
settings.event_repository_desc = Repository created or deleted. settings.event_repository_desc = Repository created or deleted.
settings.active = Include Event Details settings.active = Active
settings.active_helper = Add information about the triggering event to requests. settings.active_helper = Information about triggered events will be sent to this webhook URL.
settings.add_hook_success = The webhook has been added. settings.add_hook_success = The webhook has been added.
settings.update_webhook = Update Webhook settings.update_webhook = Update Webhook
settings.update_hook_success = The webhook has been updated. settings.update_hook_success = The webhook has been updated.

View File

@@ -181,7 +181,8 @@ func NewTeamPost(ctx *context.Context, form auth.CreateTeamForm) {
Description: form.Description, Description: form.Description,
Authorize: models.ParseAccessMode(form.Permission), Authorize: models.ParseAccessMode(form.Permission),
} }
if t.Authorize < models.AccessModeAdmin {
if t.Authorize < models.AccessModeOwner {
var units = make([]*models.TeamUnit, 0, len(form.Units)) var units = make([]*models.TeamUnit, 0, len(form.Units))
for _, tp := range form.Units { for _, tp := range form.Units {
units = append(units, &models.TeamUnit{ units = append(units, &models.TeamUnit{
@@ -270,7 +271,7 @@ func EditTeamPost(ctx *context.Context, form auth.CreateTeamForm) {
} }
} }
t.Description = form.Description t.Description = form.Description
if t.Authorize < models.AccessModeAdmin { if t.Authorize < models.AccessModeOwner {
var units = make([]models.TeamUnit, 0, len(form.Units)) var units = make([]models.TeamUnit, 0, len(form.Units))
for _, tp := range form.Units { for _, tp := range form.Units {
units = append(units, models.TeamUnit{ units = append(units, models.TeamUnit{

View File

@@ -1,4 +1,5 @@
// Copyright 2014 The Gogs Authors. All rights reserved. // Copyright 2014 The Gogs Authors. All rights reserved.
// Copyright 2018 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style // Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
@@ -17,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/validation"
"code.gitea.io/gitea/routers/utils" "code.gitea.io/gitea/routers/utils"
) )
@@ -157,7 +159,7 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
if form.EnableWiki { if form.EnableWiki {
if form.EnableExternalWiki { if form.EnableExternalWiki {
if !strings.HasPrefix(form.ExternalWikiURL, "http://") && !strings.HasPrefix(form.ExternalWikiURL, "https://") { if !validation.IsValidExternalURL(form.ExternalWikiURL) {
ctx.Flash.Error(ctx.Tr("repo.settings.external_wiki_url_error")) ctx.Flash.Error(ctx.Tr("repo.settings.external_wiki_url_error"))
ctx.Redirect(repo.Link() + "/settings") ctx.Redirect(repo.Link() + "/settings")
return return
@@ -181,11 +183,16 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
if form.EnableIssues { if form.EnableIssues {
if form.EnableExternalTracker { if form.EnableExternalTracker {
if !strings.HasPrefix(form.ExternalTrackerURL, "http://") && !strings.HasPrefix(form.ExternalTrackerURL, "https://") { if !validation.IsValidExternalURL(form.ExternalTrackerURL) {
ctx.Flash.Error(ctx.Tr("repo.settings.external_tracker_url_error")) ctx.Flash.Error(ctx.Tr("repo.settings.external_tracker_url_error"))
ctx.Redirect(repo.Link() + "/settings") ctx.Redirect(repo.Link() + "/settings")
return return
} }
if len(form.TrackerURLFormat) != 0 && !validation.IsValidExternalURL(form.TrackerURLFormat) {
ctx.Flash.Error(ctx.Tr("repo.settings.tracker_url_format_error"))
ctx.Redirect(repo.Link() + "/settings")
return
}
units = append(units, models.RepoUnit{ units = append(units, models.RepoUnit{
RepoID: repo.ID, RepoID: repo.ID,
Type: models.UnitTypeExternalTracker, Type: models.UnitTypeExternalTracker,

View File

@@ -120,6 +120,7 @@ func NewMacaron() *macaron.Macaron {
Cookie: setting.CSRFCookieName, Cookie: setting.CSRFCookieName,
SetCookie: true, SetCookie: true,
Secure: setting.SessionConfig.Secure, Secure: setting.SessionConfig.Secure,
CookieHttpOnly: true,
Header: "X-Csrf-Token", Header: "X-Csrf-Token",
CookiePath: setting.AppSubURL, CookiePath: setting.AppSubURL,
})) }))

View File

@@ -56,8 +56,8 @@ func AutoSignIn(ctx *context.Context) (bool, error) {
defer func() { defer func() {
if !isSucceed { if !isSucceed {
log.Trace("auto-login cookie cleared: %s", uname) log.Trace("auto-login cookie cleared: %s", uname)
ctx.SetCookie(setting.CookieUserName, "", -1, setting.AppSubURL) ctx.SetCookie(setting.CookieUserName, "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
ctx.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubURL) ctx.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
} }
}() }()
@@ -77,7 +77,7 @@ func AutoSignIn(ctx *context.Context) (bool, error) {
isSucceed = true isSucceed = true
ctx.Session.Set("uid", u.ID) ctx.Session.Set("uid", u.ID)
ctx.Session.Set("uname", u.Name) ctx.Session.Set("uname", u.Name)
ctx.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL) ctx.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
return true, nil return true, nil
} }
@@ -91,13 +91,13 @@ func checkAutoLogin(ctx *context.Context) bool {
redirectTo := ctx.Query("redirect_to") redirectTo := ctx.Query("redirect_to")
if len(redirectTo) > 0 { if len(redirectTo) > 0 {
ctx.SetCookie("redirect_to", redirectTo, 0, setting.AppSubURL) ctx.SetCookie("redirect_to", redirectTo, 0, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
} else { } else {
redirectTo, _ = url.QueryUnescape(ctx.GetCookie("redirect_to")) redirectTo, _ = url.QueryUnescape(ctx.GetCookie("redirect_to"))
} }
if isSucceed { if isSucceed {
ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL) ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
ctx.RedirectToFirst(redirectTo, setting.AppSubURL+string(setting.LandingPageURL)) ctx.RedirectToFirst(redirectTo, setting.AppSubURL+string(setting.LandingPageURL))
return true return true
} }
@@ -438,9 +438,9 @@ func handleSignIn(ctx *context.Context, u *models.User, remember bool) {
func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyRedirect bool) string { func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyRedirect bool) string {
if remember { if remember {
days := 86400 * setting.LogInRememberDays days := 86400 * setting.LogInRememberDays
ctx.SetCookie(setting.CookieUserName, u.Name, days, setting.AppSubURL) ctx.SetCookie(setting.CookieUserName, u.Name, days, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
ctx.SetSuperSecureCookie(base.EncodeMD5(u.Rands+u.Passwd), ctx.SetSuperSecureCookie(base.EncodeMD5(u.Rands+u.Passwd),
setting.CookieRememberName, u.Name, days, setting.AppSubURL) setting.CookieRememberName, u.Name, days, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
} }
ctx.Session.Delete("openid_verified_uri") ctx.Session.Delete("openid_verified_uri")
@@ -464,10 +464,10 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR
} }
} }
ctx.SetCookie("lang", u.Language, nil, setting.AppSubURL) ctx.SetCookie("lang", u.Language, nil, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
// Clear whatever CSRF has right now, force to generate a new one // Clear whatever CSRF has right now, force to generate a new one
ctx.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL) ctx.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
// Register last login // Register last login
u.SetLastLogin() u.SetLastLogin()
@@ -477,7 +477,7 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR
} }
if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 && !util.IsExternalURL(redirectTo) { if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 && !util.IsExternalURL(redirectTo) {
ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL) ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
if obeyRedirect { if obeyRedirect {
ctx.RedirectToFirst(redirectTo) ctx.RedirectToFirst(redirectTo)
} }
@@ -558,7 +558,7 @@ func handleOAuth2SignIn(u *models.User, gothUser goth.User, ctx *context.Context
ctx.Session.Set("uname", u.Name) ctx.Session.Set("uname", u.Name)
// Clear whatever CSRF has right now, force to generate a new one // Clear whatever CSRF has right now, force to generate a new one
ctx.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL) ctx.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
// Register last login // Register last login
u.SetLastLogin() u.SetLastLogin()
@@ -568,7 +568,7 @@ func handleOAuth2SignIn(u *models.User, gothUser goth.User, ctx *context.Context
} }
if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 { if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 {
ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL) ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
ctx.RedirectToFirst(redirectTo) ctx.RedirectToFirst(redirectTo)
return return
} }
@@ -844,10 +844,10 @@ func SignOut(ctx *context.Context) {
ctx.Session.Delete("socialId") ctx.Session.Delete("socialId")
ctx.Session.Delete("socialName") ctx.Session.Delete("socialName")
ctx.Session.Delete("socialEmail") ctx.Session.Delete("socialEmail")
ctx.SetCookie(setting.CookieUserName, "", -1, setting.AppSubURL) ctx.SetCookie(setting.CookieUserName, "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
ctx.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubURL) ctx.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
ctx.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL) ctx.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
ctx.SetCookie("lang", "", -1, setting.AppSubURL) // Setting the lang cookie will trigger the middleware to reset the language ot previous state. ctx.SetCookie("lang", "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true) // Setting the lang cookie will trigger the middleware to reset the language ot previous state.
ctx.Redirect(setting.AppSubURL + "/") ctx.Redirect(setting.AppSubURL + "/")
} }

View File

@@ -44,13 +44,13 @@ func SignInOpenID(ctx *context.Context) {
redirectTo := ctx.Query("redirect_to") redirectTo := ctx.Query("redirect_to")
if len(redirectTo) > 0 { if len(redirectTo) > 0 {
ctx.SetCookie("redirect_to", redirectTo, 0, setting.AppSubURL) ctx.SetCookie("redirect_to", redirectTo, 0, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
} else { } else {
redirectTo, _ = url.QueryUnescape(ctx.GetCookie("redirect_to")) redirectTo, _ = url.QueryUnescape(ctx.GetCookie("redirect_to"))
} }
if isSucceed { if isSucceed {
ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL) ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
ctx.RedirectToFirst(redirectTo) ctx.RedirectToFirst(redirectTo)
return return
} }

View File

@@ -103,7 +103,7 @@ func ProfilePost(ctx *context.Context, form auth.UpdateProfileForm) {
} }
// Update the language to the one we just set // Update the language to the one we just set
ctx.SetCookie("lang", ctx.User.Language, nil, setting.AppSubURL) ctx.SetCookie("lang", ctx.User.Language, nil, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
log.Trace("User settings updated: %s", ctx.User.Name) log.Trace("User settings updated: %s", ctx.User.Name)
ctx.Flash.Success(i18n.Tr(ctx.User.Language, "settings.update_profile_success")) ctx.Flash.Success(i18n.Tr(ctx.User.Language, "settings.update_profile_success"))
@@ -119,7 +119,7 @@ func UpdateAvatarSetting(ctx *context.Context, form auth.AvatarForm, ctxUser *mo
ctxUser.AvatarEmail = form.Gravatar ctxUser.AvatarEmail = form.Gravatar
} }
if form.Avatar.Filename != "" { if form.Avatar != nil && form.Avatar.Filename != "" {
fr, err := form.Avatar.Open() fr, err := form.Avatar.Open()
if err != nil { if err != nil {
return fmt.Errorf("Avatar.Open: %v", err) return fmt.Errorf("Avatar.Open: %v", err)

View File

@@ -4,6 +4,5 @@
<div class="ui divider"></div> <div class="ui divider"></div>
<br> <br>
{{if .ShowFooterVersion}}<p>Application Version: {{AppVer}}</p>{{end}} {{if .ShowFooterVersion}}<p>Application Version: {{AppVer}}</p>{{end}}
<p>If you think this is an error, please open an issue on <a href="https://github.com/go-gitea/gitea/issues/new">GitHub</a>.</p>
</div> </div>
{{template "base/footer" .}} {{template "base/footer" .}}

View File

@@ -23,9 +23,11 @@
</a> </a>
{{end}} {{end}}
</div> </div>
{{if .SignedUser.CanCreateOrganization}}
<a class="item" href="{{AppSubUrl}}/org/create"> <a class="item" href="{{AppSubUrl}}/org/create">
<i class="octicon octicon-plus"></i>&nbsp;&nbsp;&nbsp;{{.i18n.Tr "new_org"}} <i class="octicon octicon-plus"></i>&nbsp;&nbsp;&nbsp;{{.i18n.Tr "new_org"}}
</a> </a>
{{end}}
</div> </div>
</div> </div>
</div> </div>