Compare commits

...

13 Commits

Author SHA1 Message Date
Lunny Xiao
4d876ab1c8 Changelog for v1.10.6 (#10699)
* Changelog for v1.10.6

* Add warnning

* Apply suggestions from code review

Co-Authored-By: John Olheiser <john.olheiser@gmail.com>

Co-authored-by: John Olheiser <john.olheiser@gmail.com>
2020-03-10 17:09:54 +00:00
Lunny Xiao
f58715d903 cross compile using go 1.13.x (#10684) (#10696)
Co-authored-by: techknowlogick <techknowlogick@gitea.io>
2020-03-10 16:25:59 +01:00
Lunny Xiao
81072af8ce add changelog for v1.10.5 (#10628) 2020-03-06 06:34:28 +00:00
guillep2k
1d7a855d66 Fix migration bug on v96.go (#10572) (#10574)
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
2020-03-02 10:32:38 -06:00
zeripath
3c8842d58c v1.10.4 Changelog (#10294)
* v1.10.4 Changelog

* Add backport identifier for #10261

* Update CHANGELOG.md entry for #9884

* Workaround docker-library/postgres#658

* Update .drone.yml
2020-02-16 22:45:38 +02:00
Lunny Xiao
e786f098d5 Fix reply on code review (#10261)
* Fix branch page pull request title and link error (#10092)

* Fix branch page pull request title and link error

* Fix ui

* Fix reply on code review (#10227)

Co-authored-by: zeripath <art27@cantab.net>

* Revert unrelated change

* Fix lint

Co-authored-by: zeripath <art27@cantab.net>
Co-authored-by: Lauris BH <lauris@nix.lv>
2020-02-14 13:53:36 +02:00
Lunny Xiao
22dec1cea6 Fix branch page pull request title and link error (#10092) (#10098)
* Fix branch page pull request title and link error (#10092)
* Fix tmpl
2020-02-01 17:37:52 +00:00
Lunny Xiao
0c5e2e2e4c Fix milestone API state parameter unhandled (#10049) (#10053)
* Fix milestone API state parameter unhandled (#10049)

* Fix milestone API state parameter unhandled

* Fix test

* Fix test
2020-01-30 11:49:02 +08:00
Lunny Xiao
158b716387 Fix wiki raw view on sub path (#10002) (#10041)
* Fix wiki raw view on sub path

* Add test for subpath wiki raw file

* Fix bug

Co-authored-by: zeripath <art27@cantab.net>
2020-01-28 15:10:40 +00:00
John Olheiser
e611dbbe86 Fix RocketChat (#9925)
Signed-off-by: jolheiser <john.olheiser@gmail.com>
2020-01-22 10:07:11 +02:00
dioss-Machiel
68bca621cd Prevent empty LDAP search from deactivating all users (#9879) (#9890)
* Backport of #9879 (Add option to prevent LDAP from deactivating everything on empty search)

* go fmtted

Co-authored-by: Antoine GIRARD <sapk@users.noreply.github.com>
Co-authored-by: zeripath <art27@cantab.net>
2020-01-20 15:02:35 -05:00
Lunny Xiao
c4e0f717e7 fix bug about wrong dependencies permissions check and other wr… (#9884)
* fix bug about wrong dependencies permissions check and other wrong permissions check

* improve code
2020-01-20 16:45:42 +01:00
zeripath
e3e024876e Ensure that 2fa is checked on reset-password (#9857) (#9877)
* Ensure that 2fa is checked on reset-password

* Apply suggestions from code review

Co-Authored-By: Lauris BH <lauris@nix.lv>

* Properly manage scratch_code regeneration

Co-authored-by: Lauris BH <lauris@nix.lv>

Co-authored-by: Lauris BH <lauris@nix.lv>
2020-01-19 18:37:28 -05:00
38 changed files with 370 additions and 120 deletions

View File

@@ -30,6 +30,7 @@ services:
image: postgres:9.5
environment:
POSTGRES_DB: test
POSTGRES_PASSWORD: postgres
- name: mssql
pull: default
@@ -388,7 +389,7 @@ steps:
- name: static
pull: always
image: techknowlogick/xgo:latest
image: techknowlogick/xgo:go-1.13.x
commands:
- export PATH=$PATH:$GOPATH/bin
- make generate
@@ -490,7 +491,7 @@ steps:
- name: static
pull: always
image: techknowlogick/xgo:latest
image: techknowlogick/xgo:go-1.13.x
commands:
- export PATH=$PATH:$GOPATH/bin
- make generate

View File

@@ -4,6 +4,30 @@ 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
been added to each release, please refer to the [blog](https://blog.gitea.io).
## [1.10.6](https://github.com/go-gitea/gitea/releases/tag/v1.10.6) - 2020-03-10
This is a re-tag version of v1.10.5 and also explicitly built with Go 1.13.
WARNING: v1.10.5 is incorrectly tagged targeting 1.12-dev and should **not** be used.
## [1.10.5](https://github.com/go-gitea/gitea/releases/tag/v1.10.5) - 2020-03-06
* BUGFIXES
* Fix release attachments being deleted while upgrading (#10572) (#10574)
## [1.10.4](https://github.com/go-gitea/gitea/releases/tag/v1.10.4) - 2020-02-16
* FEATURE
* Prevent empty LDAP search from deactivating all users (#9879) (#9890)
* BUGFIXES
* Fix reply on code review (#10261) (#10227)
* Fix branch page pull request title and link error (#10092) (#10098)
* Fix milestone API state parameter unhandled (#10049) (#10053)
* Fix wiki raw view on sub path (#10002) (#10041)
* Fix RocketChat Webhook (#9908) (#9921) (#9925)
* Fix bug about wrong dependencies permissions check and other wrong permissions check (#9884) (Partial backport #9842)
* Ensure that 2fa is checked on reset-password (#9857) (#9877)
## [1.10.3](https://github.com/go-gitea/gitea/releases/tag/v1.10.3) - 2020-01-17
* SECURITY
* Hide credentials when submitting migration (#9102) (#9704)

View File

@@ -61,6 +61,10 @@ var (
Name: "admin-filter",
Usage: "An LDAP filter specifying if a user should be given administrator privileges.",
},
cli.BoolFlag{
Name: "allow-deactivate-all",
Usage: "Allow empty search results to deactivate all users.",
},
cli.StringFlag{
Name: "username-attribute",
Usage: "The attribute of the users LDAP record containing the user name.",
@@ -231,6 +235,9 @@ func parseLdapConfig(c *cli.Context, config *models.LDAPConfig) error {
if c.IsSet("admin-filter") {
config.Source.AdminFilter = c.String("admin-filter")
}
if c.IsSet("allow-deactivate-all") {
config.Source.AllowDeactivateAll = c.Bool("allow-deactivate-all")
}
return nil
}

View File

@@ -0,0 +1,47 @@
// Copyright 2020 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 integrations
import (
"fmt"
"net/http"
"testing"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/structs"
"github.com/stretchr/testify/assert"
)
func TestAPIIssuesMilestone(t *testing.T) {
prepareTestEnv(t)
milestone := models.AssertExistsAndLoadBean(t, &models.Milestone{ID: 1}).(*models.Milestone)
repo := models.AssertExistsAndLoadBean(t, &models.Repository{ID: milestone.RepoID}).(*models.Repository)
owner := models.AssertExistsAndLoadBean(t, &models.User{ID: repo.OwnerID}).(*models.User)
assert.Equal(t, int64(1), int64(milestone.NumIssues))
assert.Equal(t, structs.StateOpen, milestone.State())
session := loginUser(t, owner.Name)
token := getTokenForLoggedInUser(t, session)
// update values of issue
milestoneState := "closed"
urlStr := fmt.Sprintf("/api/v1/repos/%s/%s/milestones/%d?token=%s", owner.Name, repo.Name, milestone.ID, token)
req := NewRequestWithJSON(t, "PATCH", urlStr, structs.EditMilestoneOption{
State: &milestoneState,
})
resp := session.MakeRequest(t, req, http.StatusOK)
var apiMilestone structs.Milestone
DecodeJSON(t, resp, &apiMilestone)
assert.EqualValues(t, "closed", apiMilestone.State)
req = NewRequest(t, "GET", urlStr)
resp = session.MakeRequest(t, req, http.StatusOK)
var apiMilestone2 structs.Milestone
DecodeJSON(t, resp, &apiMilestone2)
assert.EqualValues(t, "closed", apiMilestone2.State)
}

View File

@@ -1 +1 @@
0cf15c3f66ec8384480ed9c3cf87c9e97fbb0ec3
423313fbd38093bb10d0c8387db9105409c6f196

View File

@@ -253,12 +253,33 @@ func updateMilestone(e Engine, m *Milestone) error {
}
// UpdateMilestone updates information of given milestone.
func UpdateMilestone(m *Milestone) error {
if err := updateMilestone(x, m); err != nil {
func UpdateMilestone(m *Milestone, oldIsClosed bool) error {
sess := x.NewSession()
defer sess.Close()
if err := sess.Begin(); err != nil {
return err
}
return updateMilestoneCompleteness(x, m.ID)
if m.IsClosed && !oldIsClosed {
m.ClosedDateUnix = timeutil.TimeStampNow()
}
if err := updateMilestone(sess, m); err != nil {
return err
}
if err := updateMilestoneCompleteness(sess, m.ID); err != nil {
return err
}
// if IsClosed changed, update milestone numbers of repository
if oldIsClosed != m.IsClosed {
if err := updateRepoMilestoneNum(sess, m.RepoID); err != nil {
return err
}
}
return sess.Commit()
}
func updateMilestoneCompleteness(e Engine, milestoneID int64) error {

View File

@@ -160,8 +160,9 @@ func TestUpdateMilestone(t *testing.T) {
milestone := AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone)
milestone.Name = "newMilestoneName"
milestone.Content = "newMilestoneContent"
assert.NoError(t, UpdateMilestone(milestone))
AssertExistsAndLoadBean(t, milestone)
assert.NoError(t, UpdateMilestone(milestone, milestone.IsClosed))
milestone = AssertExistsAndLoadBean(t, &Milestone{ID: 1}).(*Milestone)
assert.EqualValues(t, "newMilestoneName", milestone.Name)
CheckConsistencyFor(t, &Milestone{})
}

View File

@@ -26,23 +26,38 @@ func deleteOrphanedAttachments(x *xorm.Engine) error {
sess := x.NewSession()
defer sess.Close()
err := sess.BufferSize(setting.Database.IterateBufferSize).
Where("`issue_id` = 0 and (`release_id` = 0 or `release_id` not in (select `id` from `release`))").Cols("uuid").
Iterate(new(Attachment),
func(idx int, bean interface{}) error {
attachment := bean.(*Attachment)
if err := os.RemoveAll(models.AttachmentLocalPath(attachment.UUID)); err != nil {
return err
}
_, err := sess.ID(attachment.ID).NoAutoCondition().Delete(attachment)
return err
})
if err != nil {
return err
var limit = setting.Database.IterateBufferSize
if limit <= 0 {
limit = 50
}
return sess.Commit()
for {
attachements := make([]Attachment, 0, limit)
if err := sess.Where("`issue_id` = 0 and (`release_id` = 0 or `release_id` not in (select `id` from `release`))").
Cols("id, uuid").Limit(limit).
Asc("id").
Find(&attachements); err != nil {
return err
}
if len(attachements) == 0 {
return nil
}
var ids = make([]int64, 0, limit)
for _, attachment := range attachements {
ids = append(ids, attachment.ID)
}
if _, err := sess.In("id", ids).Delete(new(Attachment)); err != nil {
return err
}
for _, attachment := range attachements {
if err := os.RemoveAll(models.AttachmentLocalPath(attachment.UUID)); err != nil {
return err
}
}
if len(attachements) < limit {
return nil
}
}
}

View File

@@ -1715,6 +1715,15 @@ func SyncExternalUsers() {
continue
}
if len(sr) == 0 {
if !s.LDAP().AllowDeactivateAll {
log.Error("LDAP search found no entries but did not report an error. Refusing to deactivate all users")
continue
} else {
log.Warn("LDAP search found no entries but did not report an error. All users will be deactivated as per settings")
}
}
for _, su := range sr {
if len(su.Username) == 0 {
continue

View File

@@ -283,8 +283,10 @@ func getSlackPushPayload(p *api.PushPayload, slack *SlackMeta) (*SlackPayload, e
Username: slack.Username,
IconURL: slack.IconURL,
Attachments: []SlackAttachment{{
Color: slack.Color,
Text: attachmentText,
Color: slack.Color,
Title: p.Repo.HTMLURL,
TitleLink: p.Repo.HTMLURL,
Text: attachmentText,
}},
}, nil
}
@@ -380,12 +382,11 @@ func getSlackPullRequestApprovalPayload(p *api.PullRequestPayload, slack *SlackM
func getSlackRepositoryPayload(p *api.RepositoryPayload, slack *SlackMeta) (*SlackPayload, error) {
senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
var text, title, attachmentText string
var text string
switch p.Action {
case api.HookRepoCreated:
text = fmt.Sprintf("[%s] Repository created by %s", p.Repository.FullName, senderLink)
title = p.Repository.HTMLURL
case api.HookRepoDeleted:
text = fmt.Sprintf("[%s] Repository deleted by %s", p.Repository.FullName, senderLink)
}
@@ -395,12 +396,6 @@ func getSlackRepositoryPayload(p *api.RepositoryPayload, slack *SlackMeta) (*Sla
Text: text,
Username: slack.Username,
IconURL: slack.IconURL,
Attachments: []SlackAttachment{{
Color: slack.Color,
Title: title,
TitleLink: title,
Text: attachmentText,
}},
}, nil
}

View File

@@ -30,6 +30,7 @@ type AuthenticationForm struct {
SearchPageSize int
Filter string
AdminFilter string
AllowDeactivateAll bool
IsActive bool
IsSyncEnabled bool
SMTPAuth string

View File

@@ -47,6 +47,7 @@ type Source struct {
Filter string // Query filter to validate entry
AdminFilter string // Query filter to check if user is admin
Enabled bool // if this source is disabled
AllowDeactivateAll bool // Allow an empty search response to deactivate all users from this source
}
// SearchResult : user data

View File

@@ -91,12 +91,12 @@ func (r *Repository) CanUseTimetracker(issue *models.Issue, user *models.User) b
// 2. Is the user a contributor, admin, poster or assignee and do the repository policies require this?
isAssigned, _ := models.IsUserAssignedToIssue(issue, user)
return r.Repository.IsTimetrackerEnabled() && (!r.Repository.AllowOnlyContributorsToTrackTime() ||
r.Permission.CanWrite(models.UnitTypeIssues) || issue.IsPoster(user.ID) || isAssigned)
r.Permission.CanWriteIssuesOrPulls(issue.IsPull) || issue.IsPoster(user.ID) || isAssigned)
}
// CanCreateIssueDependencies returns whether or not a user can create dependencies.
func (r *Repository) CanCreateIssueDependencies(user *models.User) bool {
return r.Permission.CanWrite(models.UnitTypeIssues) && r.Repository.IsDependenciesEnabled()
func (r *Repository) CanCreateIssueDependencies(user *models.User, isPull bool) bool {
return r.Repository.IsDependenciesEnabled() && r.Permission.CanWriteIssuesOrPulls(isPull)
}
// GetCommitsCount returns cached commit count for current view

View File

@@ -1700,6 +1700,7 @@ auths.attribute_surname = Surname Attribute
auths.attribute_mail = Email Attribute
auths.attribute_ssh_public_key = Public SSH Key Attribute
auths.attributes_in_bind = Fetch Attributes in Bind DN Context
auths.allow_deactivate_all = Allow an empty search result to deactivate all users
auths.use_paged_search = Use Paged Search
auths.search_page_size = Page Size
auths.filter = User Filter

View File

@@ -1,6 +1,6 @@
/* globals wipPrefixes, issuesTribute, emojiTribute */
/* exported timeAddManual, toggleStopwatch, cancelStopwatch, initHeatmap */
/* exported toggleDeadlineForm, setDeadline, deleteDependencyModal, cancelCodeComment, onOAuthLoginClick */
/* exported toggleDeadlineForm, setDeadline, deleteDependencyModal, submitReply, cancelCodeComment, onOAuthLoginClick */
'use strict';
function htmlEncode(text) {
@@ -3134,10 +3134,11 @@ function deleteDependencyModal(id, type) {
function initIssueList() {
const repolink = $('#repolink').val();
const tp = $('#type').val();
$('#new-dependency-drop-list')
.dropdown({
apiSettings: {
url: suburl + '/api/v1/repos/' + repolink + '/issues?q={query}',
url: suburl + '/api/v1/repos/' + repolink + '/issues?q={query}&type='+tp,
onResponse: function(response) {
const filteredResponse = {'success': true, 'results': []};
const currIssueId = $('#new-dependency-drop-list').data('issue-id');
@@ -3170,6 +3171,14 @@ function cancelCodeComment(btn) {
form.closest('.comment-code-cloud').remove()
}
}
function submitReply(btn) {
const form = $(btn).closest('form');
if (form.length > 0 && form.hasClass('comment-form')) {
form.submit();
}
}
function onOAuthLoginClick() {
const oauthLoader = $('#oauth2-login-loader');
const oauthNav = $('#oauth2-login-navigator');

View File

@@ -115,6 +115,7 @@ func parseLDAPConfig(form auth.AuthenticationForm) *models.LDAPConfig {
SearchPageSize: pageSize,
Filter: form.Filter,
AdminFilter: form.AdminFilter,
AllowDeactivateAll: form.AllowDeactivateAll,
Enabled: true,
},
}

View File

@@ -57,6 +57,10 @@ func ListIssues(ctx *context.APIContext) {
// in: query
// description: search string
// type: string
// - name: type
// in: query
// description: filter by type (issues / pulls) if set
// type: string
// responses:
// "200":
// "$ref": "#/responses/IssueList"
@@ -91,6 +95,16 @@ func ListIssues(ctx *context.APIContext) {
}
}
var isPull util.OptionalBool
switch ctx.Query("type") {
case "pulls":
isPull = util.OptionalBoolTrue
case "issues":
isPull = util.OptionalBoolFalse
default:
isPull = util.OptionalBoolNone
}
// Only fetch the issues if we either don't have a keyword or the search returned issues
// This would otherwise return all issues if no issues were found by the search.
if len(keyword) == 0 || len(issueIDs) > 0 || len(labelIDs) > 0 {
@@ -101,6 +115,7 @@ func ListIssues(ctx *context.APIContext) {
IsClosed: isClosed,
IssueIDs: issueIDs,
LabelIDs: labelIDs,
IsPull: isPull,
})
}
@@ -292,6 +307,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
return
}
issue.Repo = ctx.Repo.Repository
canWrite := ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
err = issue.LoadAttributes()
if err != nil {
@@ -299,7 +315,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
return
}
if !issue.IsPoster(ctx.User.ID) && !ctx.Repo.CanWrite(models.UnitTypeIssues) {
if !issue.IsPoster(ctx.User.ID) && !canWrite {
ctx.Status(403)
return
}
@@ -312,7 +328,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
}
// Update the deadline
if form.Deadline != nil && ctx.Repo.CanWrite(models.UnitTypeIssues) {
if form.Deadline != nil && canWrite {
deadlineUnix := timeutil.TimeStamp(form.Deadline.Unix())
if err := models.UpdateIssueDeadline(issue, deadlineUnix, ctx.User); err != nil {
ctx.Error(500, "UpdateIssueDeadline", err)
@@ -329,7 +345,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
// Pass one or more user logins to replace the set of assignees on this Issue.
// Send an empty array ([]) to clear all assignees from the Issue.
if ctx.Repo.CanWrite(models.UnitTypeIssues) && (form.Assignees != nil || form.Assignee != nil) {
if canWrite && (form.Assignees != nil || form.Assignee != nil) {
oneAssignee := ""
if form.Assignee != nil {
oneAssignee = *form.Assignee
@@ -342,7 +358,7 @@ func EditIssue(ctx *context.APIContext, form api.EditIssueOption) {
}
}
if ctx.Repo.CanWrite(models.UnitTypeIssues) && form.Milestone != nil &&
if canWrite && form.Milestone != nil &&
issue.MilestoneID != *form.Milestone {
oldMilestoneID := issue.MilestoneID
issue.MilestoneID = *form.Milestone
@@ -430,7 +446,7 @@ func UpdateIssueDeadline(ctx *context.APIContext, form api.EditDeadlineOption) {
return
}
if !ctx.Repo.CanWrite(models.UnitTypeIssues) {
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
ctx.Status(403)
return
}
@@ -497,7 +513,7 @@ func StartIssueStopwatch(ctx *context.APIContext) {
return
}
if !ctx.Repo.CanWrite(models.UnitTypeIssues) {
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
ctx.Status(403)
return
}
@@ -566,7 +582,7 @@ func StopIssueStopwatch(ctx *context.APIContext) {
return
}
if !ctx.Repo.CanWrite(models.UnitTypeIssues) {
if !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) {
ctx.Status(403)
return
}

View File

@@ -185,7 +185,7 @@ func CreateIssueComment(ctx *context.APIContext, form api.CreateIssueCommentOpti
return
}
if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin {
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.User.IsAdmin {
ctx.Error(403, "CreateIssueComment", errors.New(ctx.Tr("repo.issues.comment_on_locked")))
return
}

View File

@@ -189,7 +189,12 @@ func EditMilestone(ctx *context.APIContext, form api.EditMilestoneOption) {
milestone.DeadlineUnix = timeutil.TimeStamp(form.Deadline.Unix())
}
if err := models.UpdateMilestone(milestone); err != nil {
var oldIsClosed = milestone.IsClosed
if form.State != nil {
milestone.IsClosed = *form.State == string(api.StateClosed)
}
if err := models.UpdateMilestone(milestone, oldIsClosed); err != nil {
ctx.ServerError("UpdateMilestone", err)
return
}

View File

@@ -221,6 +221,7 @@ func loadBranches(ctx *context.Context) []*Branch {
} else {
repoIDToRepo[pr.BaseRepoID] = pr.BaseRepo
}
pr.Issue.Repo = pr.BaseRepo
if pr.HasMerged {
baseGitRepo, ok := repoIDToGitRepo[pr.BaseRepoID]
@@ -243,7 +244,6 @@ func loadBranches(ctx *context.Context) []*Branch {
mergeMovedOn = true
}
}
}
branches[i] = &Branch{

View File

@@ -407,7 +407,7 @@ func CompareDiff(ctx *context.Context) {
if !nothingToCompare {
// Setup information for new form.
RetrieveRepoMetas(ctx, ctx.Repo.Repository)
RetrieveRepoMetas(ctx, ctx.Repo.Repository, true)
if ctx.Written() {
return
}

View File

@@ -67,7 +67,7 @@ func MustAllowUserComment(ctx *context.Context) {
return
}
if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin {
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.User.IsAdmin {
ctx.Flash.Error(ctx.Tr("repo.issues.comment_on_locked"))
ctx.Redirect(issue.HTMLURL())
return
@@ -346,8 +346,8 @@ func RetrieveRepoMilestonesAndAssignees(ctx *context.Context, repo *models.Repos
}
// RetrieveRepoMetas find all the meta information of a repository
func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository) []*models.Label {
if !ctx.Repo.CanWrite(models.UnitTypeIssues) {
func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository, isPull bool) []*models.Label {
if !ctx.Repo.CanWriteIssuesOrPulls(isPull) {
return nil
}
@@ -371,7 +371,7 @@ func RetrieveRepoMetas(ctx *context.Context, repo *models.Repository) []*models.
ctx.Data["Branches"] = brs
// Contains true if the user can create issue dependencies
ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User)
ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User, isPull)
return labels
}
@@ -441,7 +441,7 @@ func NewIssue(ctx *context.Context) {
setTemplateIfExists(ctx, issueTemplateKey, IssueTemplateCandidates)
renderAttachmentSettings(ctx)
RetrieveRepoMetas(ctx, ctx.Repo.Repository)
RetrieveRepoMetas(ctx, ctx.Repo.Repository, false)
if ctx.Written() {
return
}
@@ -456,7 +456,7 @@ func ValidateRepoMetas(ctx *context.Context, form auth.CreateIssueForm, isPull b
err error
)
labels := RetrieveRepoMetas(ctx, ctx.Repo.Repository)
labels := RetrieveRepoMetas(ctx, ctx.Repo.Repository, isPull)
if ctx.Written() {
return nil, nil, 0
}
@@ -776,8 +776,16 @@ func ViewIssue(ctx *context.Context) {
}
}
if issue.IsPull && !ctx.Repo.CanRead(models.UnitTypeIssues) {
ctx.Data["IssueType"] = "pulls"
} else if !issue.IsPull && !ctx.Repo.CanRead(models.UnitTypePullRequests) {
ctx.Data["IssueType"] = "issues"
} else {
ctx.Data["IssueType"] = "all"
}
// Check if the user can use the dependencies
ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User)
ctx.Data["CanCreateIssueDependencies"] = ctx.Repo.CanCreateIssueDependencies(ctx.User, issue.IsPull)
// Render comments and and fetch participants.
participants[0] = issue.Poster
@@ -963,7 +971,6 @@ func ViewIssue(ctx *context.Context) {
ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.User.ID)
ctx.Data["IsIssueWriter"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
ctx.Data["IsRepoAdmin"] = ctx.IsSigned && (ctx.Repo.IsAdmin() || ctx.User.IsAdmin)
ctx.Data["IsRepoIssuesWriter"] = ctx.IsSigned && (ctx.Repo.CanWrite(models.UnitTypeIssues) || ctx.User.IsAdmin)
ctx.Data["LockReasons"] = setting.Repository.Issue.LockReasons
ctx.HTML(200, tplIssueView)
}
@@ -1208,7 +1215,7 @@ func NewComment(ctx *context.Context, form auth.CreateCommentForm) {
ctx.Error(403)
}
if issue.IsLocked && !ctx.Repo.CanWrite(models.UnitTypeIssues) && !ctx.User.IsAdmin {
if issue.IsLocked && !ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) && !ctx.User.IsAdmin {
ctx.Flash.Error(ctx.Tr("repo.issues.comment_on_locked"))
ctx.Redirect(issue.HTMLURL(), http.StatusSeeOther)
return

View File

@@ -14,14 +14,6 @@ import (
// AddDependency adds new dependencies
func AddDependency(ctx *context.Context) {
// Check if the Repo is allowed to have dependencies
if !ctx.Repo.CanCreateIssueDependencies(ctx.User) {
ctx.Error(http.StatusForbidden, "CanCreateIssueDependencies")
return
}
depID := ctx.QueryInt64("newDependency")
issueIndex := ctx.ParamsInt64("index")
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, issueIndex)
if err != nil {
@@ -29,6 +21,14 @@ func AddDependency(ctx *context.Context) {
return
}
// Check if the Repo is allowed to have dependencies
if !ctx.Repo.CanCreateIssueDependencies(ctx.User, issue.IsPull) {
ctx.Error(http.StatusForbidden, "CanCreateIssueDependencies")
return
}
depID := ctx.QueryInt64("newDependency")
// Redirect
defer ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issueIndex), http.StatusSeeOther)
@@ -68,14 +68,6 @@ func AddDependency(ctx *context.Context) {
// RemoveDependency removes the dependency
func RemoveDependency(ctx *context.Context) {
// Check if the Repo is allowed to have dependencies
if !ctx.Repo.CanCreateIssueDependencies(ctx.User) {
ctx.Error(http.StatusForbidden, "CanCreateIssueDependencies")
return
}
depID := ctx.QueryInt64("removeDependencyID")
issueIndex := ctx.ParamsInt64("index")
issue, err := models.GetIssueByIndex(ctx.Repo.Repository.ID, issueIndex)
if err != nil {
@@ -83,8 +75,13 @@ func RemoveDependency(ctx *context.Context) {
return
}
// Redirect
ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issueIndex), http.StatusSeeOther)
// Check if the Repo is allowed to have dependencies
if !ctx.Repo.CanCreateIssueDependencies(ctx.User, issue.IsPull) {
ctx.Error(http.StatusForbidden, "CanCreateIssueDependencies")
return
}
depID := ctx.QueryInt64("removeDependencyID")
// Dependency Type
depTypeStr := ctx.Req.PostForm.Get("dependencyType")
@@ -116,4 +113,7 @@ func RemoveDependency(ctx *context.Context) {
ctx.ServerError("RemoveIssueDependency", err)
return
}
// Redirect
ctx.Redirect(fmt.Sprintf("%s/issues/%d", ctx.Repo.RepoLink, issueIndex), http.StatusSeeOther)
}

View File

@@ -192,7 +192,7 @@ func EditMilestonePost(ctx *context.Context, form auth.CreateMilestoneForm) {
m.Name = form.Title
m.Content = form.Content
m.DeadlineUnix = timeutil.TimeStamp(deadline.Unix())
if err = models.UpdateMilestone(m); err != nil {
if err = models.UpdateMilestone(m, m.IsClosed); err != nil {
ctx.ServerError("UpdateMilestone", err)
return
}

View File

@@ -65,27 +65,20 @@ type PageMeta struct {
// findEntryForFile finds the tree entry for a target filepath.
func findEntryForFile(commit *git.Commit, target string) (*git.TreeEntry, error) {
entries, err := commit.ListEntries()
if err != nil {
entry, err := commit.GetTreeEntryByPath(target)
if err != nil && !git.IsErrNotExist(err) {
return nil, err
}
// The longest name should be checked first
for _, entry := range entries {
if entry.IsRegular() && entry.Name() == target {
return entry, nil
}
if entry != nil {
return entry, nil
}
// Then the unescaped, shortest alternative
var unescapedTarget string
if unescapedTarget, err = url.QueryUnescape(target); err != nil {
return nil, err
}
for _, entry := range entries {
if entry.IsRegular() && entry.Name() == unescapedTarget {
return entry, nil
}
}
return nil, nil
return commit.GetTreeEntryByPath(unescapedTarget)
}
func findWikiRepoCommit(ctx *context.Context) (*git.Repository, *git.Commit, error) {
@@ -122,10 +115,9 @@ func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte {
// wikiContentsByName returns the contents of a wiki page, along with a boolean
// indicating whether the page exists. Writes to ctx if an error occurs.
func wikiContentsByName(ctx *context.Context, commit *git.Commit, wikiName string) ([]byte, *git.TreeEntry, string, bool) {
var entry *git.TreeEntry
var err error
pageFilename := models.WikiNameToFilename(wikiName)
if entry, err = findEntryForFile(commit, pageFilename); err != nil {
entry, err := findEntryForFile(commit, pageFilename)
if err != nil && !git.IsErrNotExist(err) {
ctx.ServerError("findEntryForFile", err)
return nil, nil, "", false
} else if entry == nil {
@@ -517,7 +509,7 @@ func WikiRaw(ctx *context.Context) {
if commit != nil {
// Try to find a file with that name
entry, err = findEntryForFile(commit, providedPath)
if err != nil {
if err != nil && !git.IsErrNotExist(err) {
ctx.ServerError("findFile", err)
return
}
@@ -530,7 +522,7 @@ func WikiRaw(ctx *context.Context) {
wikiPath := models.WikiNameToFilename(providedPath)
entry, err = findEntryForFile(commit, wikiPath)
if err != nil {
if err != nil && !git.IsErrNotExist(err) {
ctx.ServerError("findFile", err)
return
}

View File

@@ -191,6 +191,7 @@ func TestDeleteWikiPagePost(t *testing.T) {
func TestWikiRaw(t *testing.T) {
for filepath, filetype := range map[string]string{
"jpeg.jpg": "image/jpeg",
"images/jpeg.jpg": "image/jpeg",
"Page With Spaced Name": "text/plain; charset=utf-8",
"Page-With-Spaced-Name": "text/plain; charset=utf-8",
"Page With Spaced Name.md": "text/plain; charset=utf-8",

View File

@@ -532,18 +532,12 @@ func RegisterRoutes(m *macaron.Macaron) {
reqRepoReleaseReader := context.RequireRepoReader(models.UnitTypeReleases)
reqRepoWikiWriter := context.RequireRepoWriter(models.UnitTypeWiki)
reqRepoIssueReader := context.RequireRepoReader(models.UnitTypeIssues)
reqRepoIssueWriter := context.RequireRepoWriter(models.UnitTypeIssues)
reqRepoPullsWriter := context.RequireRepoWriter(models.UnitTypePullRequests)
reqRepoPullsReader := context.RequireRepoReader(models.UnitTypePullRequests)
reqRepoIssuesOrPullsWriter := context.RequireRepoWriterOr(models.UnitTypeIssues, models.UnitTypePullRequests)
reqRepoIssuesOrPullsReader := context.RequireRepoReaderOr(models.UnitTypeIssues, models.UnitTypePullRequests)
reqRepoIssueWriter := func(ctx *context.Context) {
if !ctx.Repo.CanWrite(models.UnitTypeIssues) {
ctx.Error(403)
return
}
}
// ***** START: Organization *****
m.Group("/org", func() {
m.Group("", func() {

View File

@@ -1282,7 +1282,7 @@ func ForgotPasswdPost(ctx *context.Context) {
ctx.HTML(200, tplForgotPassword)
}
func commonResetPassword(ctx *context.Context) *models.User {
func commonResetPassword(ctx *context.Context) (*models.User, *models.TwoFactor) {
code := ctx.Query("code")
ctx.Data["Title"] = ctx.Tr("auth.reset_password")
@@ -1294,14 +1294,25 @@ func commonResetPassword(ctx *context.Context) *models.User {
if len(code) == 0 {
ctx.Flash.Error(ctx.Tr("auth.invalid_code"))
return nil
return nil, nil
}
// Fail early, don't frustrate the user
u := models.VerifyUserActiveCode(code)
if u == nil {
ctx.Flash.Error(ctx.Tr("auth.invalid_code"))
return nil
return nil, nil
}
twofa, err := models.GetTwoFactorByUID(u.ID)
if err != nil {
if !models.IsErrTwoFactorNotEnrolled(err) {
ctx.Error(http.StatusInternalServerError, "CommonResetPassword", err.Error())
return nil, nil
}
} else {
ctx.Data["has_two_factor"] = true
ctx.Data["scratch_code"] = ctx.QueryBool("scratch_code")
}
// Show the user that they are affecting the account that they intended to
@@ -1309,10 +1320,10 @@ func commonResetPassword(ctx *context.Context) *models.User {
if nil != ctx.User && u.ID != ctx.User.ID {
ctx.Flash.Error(ctx.Tr("auth.reset_password_wrong_user", ctx.User.Email, u.Email))
return nil
return nil, nil
}
return u
return u, twofa
}
// ResetPasswd render the account recovery page
@@ -1320,13 +1331,19 @@ func ResetPasswd(ctx *context.Context) {
ctx.Data["IsResetForm"] = true
commonResetPassword(ctx)
if ctx.Written() {
return
}
ctx.HTML(200, tplResetPassword)
}
// ResetPasswdPost response from account recovery request
func ResetPasswdPost(ctx *context.Context) {
u := commonResetPassword(ctx)
u, twofa := commonResetPassword(ctx)
if ctx.Written() {
return
}
if u == nil {
// Flash error has been set
@@ -1348,6 +1365,39 @@ func ResetPasswdPost(ctx *context.Context) {
return
}
// Handle two-factor
regenerateScratchToken := false
if twofa != nil {
if ctx.QueryBool("scratch_code") {
if !twofa.VerifyScratchToken(ctx.Query("token")) {
ctx.Data["IsResetForm"] = true
ctx.Data["Err_Token"] = true
ctx.RenderWithErr(ctx.Tr("auth.twofa_scratch_token_incorrect"), tplResetPassword, nil)
return
}
regenerateScratchToken = true
} else {
passcode := ctx.Query("passcode")
ok, err := twofa.ValidateTOTP(passcode)
if err != nil {
ctx.Error(http.StatusInternalServerError, "ValidateTOTP", err.Error())
return
}
if !ok || twofa.LastUsedPasscode == passcode {
ctx.Data["IsResetForm"] = true
ctx.Data["Err_Passcode"] = true
ctx.RenderWithErr(ctx.Tr("auth.twofa_passcode_incorrect"), tplResetPassword, nil)
return
}
twofa.LastUsedPasscode = passcode
if err = models.UpdateTwoFactor(twofa); err != nil {
ctx.ServerError("ResetPasswdPost: UpdateTwoFactor", err)
return
}
}
}
var err error
if u.Rands, err = models.GetUserSalt(); err != nil {
ctx.ServerError("UpdateUser", err)
@@ -1357,7 +1407,6 @@ func ResetPasswdPost(ctx *context.Context) {
ctx.ServerError("UpdateUser", err)
return
}
u.HashPassword(passwd)
u.MustChangePassword = false
if err := models.UpdateUserCols(u, "must_change_password", "passwd", "rands", "salt"); err != nil {
@@ -1366,9 +1415,27 @@ func ResetPasswdPost(ctx *context.Context) {
}
log.Trace("User password reset: %s", u.Name)
ctx.Data["IsResetFailed"] = true
remember := len(ctx.Query("remember")) != 0
if regenerateScratchToken {
// Invalidate the scratch token.
_, err = twofa.GenerateScratchToken()
if err != nil {
ctx.ServerError("UserSignIn", err)
return
}
if err = models.UpdateTwoFactor(twofa); err != nil {
ctx.ServerError("UserSignIn", err)
return
}
handleSignInFull(ctx, u, remember, false)
ctx.Flash.Info(ctx.Tr("auth.twofa_scratch_used"))
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
return
}
handleSignInFull(ctx, u, remember, true)
}

View File

@@ -112,6 +112,12 @@
</div>
</div>
{{end}}
<div class="inline field">
<div class="ui checkbox">
<label for="allow_deactivate_all"><strong>{{.i18n.Tr "admin.auths.allow_deactivate_all"}}</strong></label>
<input id="allow_deactivate_all" name="allow_deactivate_all" type="checkbox" {{if $cfg.AllowDeactivateAll}}checked{{end}}>
</div>
</div>
{{end}}
<!-- SMTP -->

View File

@@ -73,7 +73,7 @@
</div>
{{end}}
</td>
<td class="two wide right aligned">
<td class="three wide right aligned">
{{if not .LatestPullRequest}}
{{if and (not .IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
<a href="{{$.RepoLink}}/compare/{{$.DefaultBranch | EscapePound}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{$.Owner.Name}}:{{end}}{{.Name | EscapePound}}">
@@ -87,13 +87,13 @@
</a>
{{end}}
{{else}}
<a href="{{$.RepoLink}}/pulls/{{.LatestPullRequest.Issue.Index}}">#{{.LatestPullRequest.Issue.Index}}</a>
<a href="{{.LatestPullRequest.Issue.HTMLURL}}">{{if ne .LatestPullRequest.BaseRepoID .LatestPullRequest.HeadRepoID}}{{.LatestPullRequest.BaseRepo.FullName}}{{end}}#{{.LatestPullRequest.Issue.Index}}</a>
{{if .LatestPullRequest.HasMerged}}
<a href="{{$.RepoLink}}/pulls/{{.LatestPullRequest.Issue.Index}}" class="ui purple small label"><i class="octicon octicon-git-pull-request"></i> {{$.i18n.Tr "repo.pulls.merged"}}</a>
<a href="{{.LatestPullRequest.Issue.HTMLURL}}" class="ui purple mini label"><i class="octicon octicon-git-pull-request"></i> {{$.i18n.Tr "repo.pulls.merged"}}</a>
{{else if .LatestPullRequest.Issue.IsClosed}}
<a href="{{$.RepoLink}}/pulls/{{.LatestPullRequest.Issue.Index}}" class="ui red small label"><i class="octicon octicon-issue-closed"></i> {{$.i18n.Tr "repo.issues.closed_title"}}</a>
<a href="{{.LatestPullRequest.Issue.HTMLURL}}" class="ui red mini label"><i class="octicon octicon-issue-closed"></i> {{$.i18n.Tr "repo.issues.closed_title"}}</a>
{{else}}
<a href="{{$.RepoLink}}/pulls/{{.LatestPullRequest.Issue.Index}}" class="ui green small label"><i class="octicon octicon-issue-opened"></i> {{$.i18n.Tr "repo.issues.open_title"}}</a>
<a href="{{.LatestPullRequest.Issue.HTMLURL}}" class="ui green mini label"><i class="octicon octicon-issue-opened"></i> {{$.i18n.Tr "repo.issues.open_title"}}</a>
{{end}}
{{end}}
</td>

View File

@@ -26,7 +26,8 @@
<span class="markdown-info"><i class="octicon octicon-markdown"></i> {{$.root.i18n.Tr "repo.diff.comment.markdown_info"}}</span>
<div class="ui right floated">
{{if $.reply}}
<button name="reply" value="{{$.reply}}" class="ui submit green tiny button btn-reply">{{$.root.i18n.Tr "repo.diff.comment.reply"}}</button>
<input type="hidden" name="reply" value="{{$.reply}}">
<button class="ui submit green tiny button btn-reply" onclick="submitReply(this);">{{$.root.i18n.Tr "repo.diff.comment.reply"}}</button>
{{else}}
{{if $.root.CurrentReview}}
<button name="is_review" value="true" type="submit"

View File

@@ -78,7 +78,7 @@
{{ template "repo/issue/view_content/pull". }}
{{end}}
{{if .IsSigned}}
{{ if and (or .IsRepoAdmin .IsRepoIssuesWriter (or (not .Issue.IsLocked))) (not .Repository.IsArchived) }}
{{ if and (or .IsRepoAdmin .IsIssuesWriter (or (not .Issue.IsLocked))) (not .Repository.IsArchived) }}
<div class="comment form">
<a class="avatar" href="{{.SignedUser.HomeLink}}">
<img src="{{.SignedUser.RelAvatarLink}}">

View File

@@ -426,6 +426,7 @@
<input type="hidden" id="repolink" value="{{$.RepoRelPath}}">
<!-- I know, there is probably a better way to do this -->
<input type="hidden" id="issueIndex" value="{{.Issue.Index}}"/>
<input type="hidden" id="type" value="{{.IssueType}}">
<div class="ui basic modal remove-dependency">
<div class="ui icon header">

View File

@@ -2777,6 +2777,12 @@
"description": "search string",
"name": "q",
"in": "query"
},
{
"type": "string",
"description": "filter by type (issues / pulls) if set",
"name": "type",
"in": "query"
}
],
"responses": {

View File

@@ -18,7 +18,7 @@
{{end}}
{{if .IsResetForm}}
<div class="required inline field {{if .Err_Password}}error{{end}}">
<label for="password">{{.i18n.Tr "password"}}</label>
<label for="password">{{.i18n.Tr "settings.new_password"}}</label>
<input id="password" name="password" type="password" value="{{.password}}" autocomplete="off" autofocus required>
</div>
{{if not .user_signed_in}}
@@ -30,10 +30,31 @@
</div>
</div>
{{end}}
{{if .has_two_factor}}
<h4 class="ui dividing header">
{{.i18n.Tr "twofa"}}
</h4>
<div class="ui warning visible message">{{.i18n.Tr "settings.twofa_is_enrolled" | Str2html }}</div>
{{if .scratch_code}}
<div class="required inline field {{if .Err_Token}}error{{end}}">
<label for="token">{{.i18n.Tr "auth.scratch_code"}}</label>
<input id="token" name="token" type="text" autocomplete="off" autofocus required>
</div>
<input type="hidden" name="scratch_code" value="true">
{{else}}
<div class="required inline field {{if .Err_Passcode}}error{{end}}">
<label for="passcode">{{.i18n.Tr "passcode"}}</label>
<input id="passcode" name="passcode" type="number" autocomplete="off" autofocus required>
</div>
{{end}}
{{end}}
<div class="ui divider"></div>
<div class="inline field">
<label></label>
<button class="ui blue button">{{.i18n.Tr "auth.reset_password_helper"}}</button>
{{if and .has_two_factor (not .scratch_code)}}
<a href="{{.Link}}?code={{.Code}}&amp;scratch_code=true">{{.i18n.Tr "auth.use_scratch_code" | Str2html}}</a>
{{end}}
</div>
{{else}}
<p class="center">{{.i18n.Tr "auth.invalid_code"}}</p>