mirror of
https://github.com/go-gitea/gitea.git
synced 2025-11-17 23:03:55 +09:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5462fdcbbd | ||
|
|
161e550200 | ||
|
|
95af6096fb | ||
|
|
801f4b9e7a | ||
|
|
c0c3a533a0 | ||
|
|
ed646078e1 | ||
|
|
dc0ea133e1 | ||
|
|
a854846f06 | ||
|
|
b52e8de7de | ||
|
|
1b62916393 | ||
|
|
1d57c309ef | ||
|
|
cf97e65b66 | ||
|
|
42a46cff35 | ||
|
|
2cb3db2d20 | ||
|
|
04e480d477 | ||
|
|
de9a96c4de | ||
|
|
878434146f | ||
|
|
d78be7ddf9 |
21
CHANGELOG.md
21
CHANGELOG.md
@@ -4,6 +4,27 @@ 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.11.6](https://github.com/go-gitea/gitea/releases/tag/v1.11.6) - 2020-05-30
|
||||||
|
|
||||||
|
* SECURITY
|
||||||
|
* Fix missing authorization check on pull for public repos of private/limited org (#11656) (#11683)
|
||||||
|
* Use session for retrieving org teams (#11438) (#11439)
|
||||||
|
* BUGFIXES
|
||||||
|
* Return json on 500 error from API (#11574) (#11660)
|
||||||
|
* Fix wrong milestone in webhook message (#11596) (#11612)
|
||||||
|
* Prevent (caught) panic on login (#11590) (#11598)
|
||||||
|
* Fix commit page js error (#11527)
|
||||||
|
* Use media links for img in post-process (#10515) (#11504)
|
||||||
|
* Ensure public repositories in private organizations are visible and fix admin organizations list (#11465) (#11475)
|
||||||
|
* Set correct Content-Type value for Gogs/Gitea webhooks (#9504) (#10456) (#11461)
|
||||||
|
* Allow all members of private orgs to see public repos (#11442) (#11459)
|
||||||
|
* Whenever the ctx.Session is updated, release it to save it before sending the redirect (#11456) (#11457)
|
||||||
|
* Forcibly clean and destroy the session on logout (#11447) (#11451)
|
||||||
|
* Fix /api/v1/orgs/* endpoints by changing parameter to :org from :orgname (#11381)
|
||||||
|
* Add tracked time fix to doctor (part of #11111) (#11138)
|
||||||
|
* Fix webpack chunk loading with STATIC_URL_PREFIX (#11526) (#11544)
|
||||||
|
* Remove unnecessary parentheses in wiki/revision.tmpl to allow 1.11 to build on go1.14 (#11481)
|
||||||
|
|
||||||
## [1.11.5](https://github.com/go-gitea/gitea/releases/tag/v1.11.5) - 2020-05-09
|
## [1.11.5](https://github.com/go-gitea/gitea/releases/tag/v1.11.5) - 2020-05-09
|
||||||
|
|
||||||
* BUGFIXES
|
* BUGFIXES
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ var checklist = []check{
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Check Database Version",
|
title: "Check Database Version",
|
||||||
name: "check-db",
|
name: "check-db-version",
|
||||||
isDefault: true,
|
isDefault: true,
|
||||||
f: runDoctorCheckDBVersion,
|
f: runDoctorCheckDBVersion,
|
||||||
abortIfFailed: true,
|
abortIfFailed: true,
|
||||||
@@ -113,6 +113,12 @@ var checklist = []check{
|
|||||||
isDefault: false,
|
isDefault: false,
|
||||||
f: runDoctorPRMergeBase,
|
f: runDoctorPRMergeBase,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "Check consistency of database",
|
||||||
|
name: "check-db-consistency",
|
||||||
|
isDefault: true,
|
||||||
|
f: runDoctorCheckDBConsistency,
|
||||||
|
},
|
||||||
// more checks please append here
|
// more checks please append here
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,3 +500,45 @@ func runDoctorScriptType(ctx *cli.Context) ([]string, error) {
|
|||||||
}
|
}
|
||||||
return []string{fmt.Sprintf("ScriptType %s is on the current PATH at %s", setting.ScriptType, path)}, nil
|
return []string{fmt.Sprintf("ScriptType %s is on the current PATH at %s", setting.ScriptType, path)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runDoctorCheckDBConsistency(ctx *cli.Context) ([]string, error) {
|
||||||
|
// make sure DB version is uptodate
|
||||||
|
if err := models.NewEngine(context.Background(), migrations.EnsureUpToDate); err != nil {
|
||||||
|
return nil, fmt.Errorf("model version on the database does not match the current Gitea version. Model consistency will not be checked until the database is upgraded")
|
||||||
|
}
|
||||||
|
_, committer, err := models.TxDBContext()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sess := committer.(models.Engine)
|
||||||
|
defer committer.Close()
|
||||||
|
var results []string
|
||||||
|
|
||||||
|
//find tracked times without existing issues/pulls
|
||||||
|
count, err := sess.Table("tracked_time").
|
||||||
|
Join("LEFT", "issue", "tracked_time.issue_id=issue.id").
|
||||||
|
Where("issue.id is NULL").
|
||||||
|
Count("id")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if count > 0 {
|
||||||
|
if ctx.Bool("fix") {
|
||||||
|
if _, err = sess.In("id", builder.Select("tracked_time.id").
|
||||||
|
From("tracked_time").
|
||||||
|
Join("LEFT", "issue", "tracked_time.issue_id=issue.id").
|
||||||
|
Where(builder.IsNull{"issue.id"})).
|
||||||
|
Delete(models.TrackedTime{}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
results = append(results, fmt.Sprintf("%d tracked times without existing issue deleted", count))
|
||||||
|
} else {
|
||||||
|
results = append(results, fmt.Sprintf("%d tracked times without existing issue", count))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.Bool("fix") {
|
||||||
|
return results, committer.Commit()
|
||||||
|
}
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ func (issue *Issue) loadReactions(e Engine) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (issue *Issue) loadMilestone(e Engine) (err error) {
|
func (issue *Issue) loadMilestone(e Engine) (err error) {
|
||||||
if issue.Milestone == nil && issue.MilestoneID > 0 {
|
if (issue.Milestone == nil || issue.Milestone.ID != issue.MilestoneID) && issue.MilestoneID > 0 {
|
||||||
issue.Milestone, err = getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID)
|
issue.Milestone, err = getMilestoneByRepoID(e, issue.RepoID, issue.MilestoneID)
|
||||||
if err != nil && !IsErrMilestoneNotExist(err) {
|
if err != nil && !IsErrMilestoneNotExist(err) {
|
||||||
return fmt.Errorf("getMilestoneByRepoID [repo_id: %d, milestone_id: %d]: %v", issue.RepoID, issue.MilestoneID, err)
|
return fmt.Errorf("getMilestoneByRepoID [repo_id: %d, milestone_id: %d]: %v", issue.RepoID, issue.MilestoneID, err)
|
||||||
|
|||||||
@@ -1608,7 +1608,7 @@ func TransferOwnership(doer *User, newOwnerName string, repo *Repository) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
if newOwner.IsOrganization() {
|
if newOwner.IsOrganization() {
|
||||||
if err := newOwner.GetTeams(); err != nil {
|
if err := newOwner.getTeams(sess); err != nil {
|
||||||
return fmt.Errorf("GetTeams: %v", err)
|
return fmt.Errorf("GetTeams: %v", err)
|
||||||
}
|
}
|
||||||
for _, t := range newOwner.Teams {
|
for _, t := range newOwner.Teams {
|
||||||
|
|||||||
@@ -214,14 +214,35 @@ func SearchRepositoryCondition(opts *SearchRepoOptions) builder.Cond {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if opts.Collaborate != util.OptionalBoolFalse {
|
if opts.Collaborate != util.OptionalBoolFalse {
|
||||||
|
// A Collaboration is:
|
||||||
collaborateCond := builder.And(
|
collaborateCond := builder.And(
|
||||||
|
// 1. Repository we don't own
|
||||||
|
builder.Neq{"owner_id": opts.OwnerID},
|
||||||
|
// 2. But we can see because of:
|
||||||
builder.Or(
|
builder.Or(
|
||||||
builder.Expr("repository.id IN (SELECT repo_id FROM `access` WHERE access.user_id = ?)", opts.OwnerID),
|
// A. We have access
|
||||||
builder.In("id", builder.Select("`team_repo`.repo_id").
|
builder.In("`repository`.id",
|
||||||
|
builder.Select("`access`.repo_id").
|
||||||
|
From("access").
|
||||||
|
Where(builder.Eq{"`access`.user_id": opts.OwnerID})),
|
||||||
|
// B. We are in a team for
|
||||||
|
builder.In("`repository`.id", builder.Select("`team_repo`.repo_id").
|
||||||
From("team_repo").
|
From("team_repo").
|
||||||
Where(builder.Eq{"`team_user`.uid": opts.OwnerID}).
|
Where(builder.Eq{"`team_user`.uid": opts.OwnerID}).
|
||||||
Join("INNER", "team_user", "`team_user`.team_id = `team_repo`.team_id"))),
|
Join("INNER", "team_user", "`team_user`.team_id = `team_repo`.team_id")),
|
||||||
builder.Neq{"owner_id": opts.OwnerID})
|
// C. Public repositories in private organizations that we are member of
|
||||||
|
builder.And(
|
||||||
|
builder.Eq{"`repository`.is_private": false},
|
||||||
|
builder.In("`repository`.owner_id",
|
||||||
|
builder.Select("`org_user`.org_id").
|
||||||
|
From("org_user").
|
||||||
|
Join("INNER", "`user`", "`user`.id = `org_user`.org_id").
|
||||||
|
Where(builder.Eq{
|
||||||
|
"`org_user`.uid": opts.OwnerID,
|
||||||
|
"`user`.type": UserTypeOrganization,
|
||||||
|
"`user`.visibility": structs.VisibleTypePrivate,
|
||||||
|
})))),
|
||||||
|
)
|
||||||
if !opts.Private {
|
if !opts.Private {
|
||||||
collaborateCond = collaborateCond.And(builder.Expr("owner_id NOT IN (SELECT org_id FROM org_user WHERE org_user.uid = ? AND org_user.is_public = ?)", opts.OwnerID, false))
|
collaborateCond = collaborateCond.And(builder.Expr("owner_id NOT IN (SELECT org_id FROM org_user WHERE org_user.uid = ? AND org_user.is_public = ?)", opts.OwnerID, false))
|
||||||
}
|
}
|
||||||
@@ -340,41 +361,39 @@ func SearchRepositoryByCondition(opts *SearchRepoOptions, cond builder.Cond) (Re
|
|||||||
// accessibleRepositoryCondition takes a user a returns a condition for checking if a repository is accessible
|
// accessibleRepositoryCondition takes a user a returns a condition for checking if a repository is accessible
|
||||||
func accessibleRepositoryCondition(userID int64) builder.Cond {
|
func accessibleRepositoryCondition(userID int64) builder.Cond {
|
||||||
if userID <= 0 {
|
if userID <= 0 {
|
||||||
|
// Public repositories that are not in private or limited organizations
|
||||||
return builder.And(
|
return builder.And(
|
||||||
builder.Eq{"`repository`.is_private": false},
|
builder.Eq{"`repository`.is_private": false},
|
||||||
builder.Or(
|
builder.NotIn("`repository`.owner_id",
|
||||||
// A. Aren't in organisations __OR__
|
builder.Select("id").From("`user`").Where(builder.Eq{"type": UserTypeOrganization}).And(builder.Neq{"visibility": structs.VisibleTypePublic})))
|
||||||
builder.NotIn("`repository`.owner_id", builder.Select("id").From("`user`").Where(builder.Eq{"type": UserTypeOrganization})),
|
|
||||||
// B. Is a public organisation.
|
|
||||||
builder.In("`repository`.owner_id", builder.Select("id").From("`user`").Where(builder.Eq{"visibility": structs.VisibleTypePublic}))),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.Or(
|
return builder.Or(
|
||||||
// 1. Be able to see all non-private repositories that either:
|
// 1. All public repositories that are not in private organizations
|
||||||
builder.And(
|
builder.And(
|
||||||
builder.Eq{"`repository`.is_private": false},
|
builder.Eq{"`repository`.is_private": false},
|
||||||
builder.Or(
|
builder.NotIn("`repository`.owner_id",
|
||||||
// A. Aren't in organisations __OR__
|
builder.Select("id").From("`user`").Where(builder.Eq{"type": UserTypeOrganization}).And(builder.Eq{"visibility": structs.VisibleTypePrivate}))),
|
||||||
builder.NotIn("`repository`.owner_id", builder.Select("id").From("`user`").Where(builder.Eq{"type": UserTypeOrganization})),
|
// 2. Be able to see all repositories that we own
|
||||||
// B. Isn't a private organisation. (Limited is OK because we're logged in)
|
builder.Eq{"`repository`.owner_id": userID},
|
||||||
builder.NotIn("`repository`.owner_id", builder.Select("id").From("`user`").Where(builder.Eq{"visibility": structs.VisibleTypePrivate}))),
|
// 3. Be able to see all repositories that we have access to
|
||||||
),
|
builder.In("`repository`.id", builder.Select("repo_id").
|
||||||
// 2. Be able to see all repositories that we have access to
|
From("`access`").
|
||||||
builder.Or(
|
Where(builder.And(
|
||||||
builder.In("`repository`.id", builder.Select("repo_id").
|
builder.Eq{"user_id": userID},
|
||||||
From("`access`").
|
builder.Gt{"mode": int(AccessModeNone)}))),
|
||||||
Where(builder.And(
|
// 4. Be able to see all repositories that we are in a team
|
||||||
builder.Eq{"user_id": userID},
|
|
||||||
builder.Gt{"mode": int(AccessModeNone)}))),
|
|
||||||
builder.In("`repository`.id", builder.Select("id").
|
|
||||||
From("`repository`").
|
|
||||||
Where(builder.Eq{"owner_id": userID}))),
|
|
||||||
// 3. Be able to see all repositories that we are in a team
|
|
||||||
builder.In("`repository`.id", builder.Select("`team_repo`.repo_id").
|
builder.In("`repository`.id", builder.Select("`team_repo`.repo_id").
|
||||||
From("team_repo").
|
From("team_repo").
|
||||||
Where(builder.Eq{"`team_user`.uid": userID}).
|
Where(builder.Eq{"`team_user`.uid": userID}).
|
||||||
Join("INNER", "team_user", "`team_user`.team_id = `team_repo`.team_id")))
|
Join("INNER", "team_user", "`team_user`.team_id = `team_repo`.team_id")),
|
||||||
|
// 5. Be able to see all public repos in private organizations that we are an org_user of
|
||||||
|
builder.And(builder.Eq{"`repository`.is_private": false},
|
||||||
|
builder.In("`repository`.owner_id",
|
||||||
|
builder.Select("`org_user`.org_id").
|
||||||
|
From("org_user").
|
||||||
|
Where(builder.Eq{"`org_user`.uid": userID}))),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchRepositoryByName takes keyword and part of repository name to search,
|
// SearchRepositoryByName takes keyword and part of repository name to search,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ package context
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -64,18 +65,18 @@ type APINotFound struct{}
|
|||||||
// swagger:response redirect
|
// swagger:response redirect
|
||||||
type APIRedirect struct{}
|
type APIRedirect struct{}
|
||||||
|
|
||||||
// Error responses error message to client with given message.
|
// Error responds with an error message to client with given obj as the message.
|
||||||
// If status is 500, also it prints error to log.
|
// If status is 500, also it prints error to log.
|
||||||
func (ctx *APIContext) Error(status int, title string, obj interface{}) {
|
func (ctx *APIContext) Error(status int, title string, obj interface{}) {
|
||||||
var message string
|
var message string
|
||||||
if err, ok := obj.(error); ok {
|
if err, ok := obj.(error); ok {
|
||||||
message = err.Error()
|
message = err.Error()
|
||||||
} else {
|
} else {
|
||||||
message = obj.(string)
|
message = fmt.Sprintf("%s", obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
if status == 500 {
|
if status == http.StatusInternalServerError {
|
||||||
log.Error("%s: %s", title, message)
|
log.ErrorWithSkip(1, "%s: %s", title, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.JSON(status, APIError{
|
ctx.JSON(status, APIError{
|
||||||
@@ -84,6 +85,22 @@ func (ctx *APIContext) Error(status int, title string, obj interface{}) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InternalServerError responds with an error message to the client with the error as a message
|
||||||
|
// and the file and line of the caller.
|
||||||
|
func (ctx *APIContext) InternalServerError(err error) {
|
||||||
|
log.ErrorWithSkip(1, "InternalServerError: %v", err)
|
||||||
|
|
||||||
|
var message string
|
||||||
|
if macaron.Env != macaron.PROD {
|
||||||
|
message = err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.JSON(http.StatusInternalServerError, APIError{
|
||||||
|
Message: message,
|
||||||
|
URL: setting.API.SwaggerURL,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func genAPILinks(curURL *url.URL, total, pageSize, curPage int) []string {
|
func genAPILinks(curURL *url.URL, total, pageSize, curPage int) []string {
|
||||||
page := NewPagination(total, pageSize, curPage, 0)
|
page := NewPagination(total, pageSize, curPage, 0)
|
||||||
paginater := page.Paginater
|
paginater := page.Paginater
|
||||||
|
|||||||
@@ -290,7 +290,7 @@ func (ctx *postProcessCtx) postProcess(rawHTML []byte) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
ctx.visitNode(node)
|
ctx.visitNode(node, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create buffer in which the data will be placed again. We know that the
|
// Create buffer in which the data will be placed again. We know that the
|
||||||
@@ -313,7 +313,7 @@ func (ctx *postProcessCtx) postProcess(rawHTML []byte) ([]byte, error) {
|
|||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx *postProcessCtx) visitNode(node *html.Node) {
|
func (ctx *postProcessCtx) visitNode(node *html.Node, visitText bool) {
|
||||||
// Add user-content- to IDs if they don't already have them
|
// Add user-content- to IDs if they don't already have them
|
||||||
for idx, attr := range node.Attr {
|
for idx, attr := range node.Attr {
|
||||||
if attr.Key == "id" && !(strings.HasPrefix(attr.Val, "user-content-") || blackfridayExtRegex.MatchString(attr.Val)) {
|
if attr.Key == "id" && !(strings.HasPrefix(attr.Val, "user-content-") || blackfridayExtRegex.MatchString(attr.Val)) {
|
||||||
@@ -323,13 +323,37 @@ func (ctx *postProcessCtx) visitNode(node *html.Node) {
|
|||||||
// We ignore code, pre and already generated links.
|
// We ignore code, pre and already generated links.
|
||||||
switch node.Type {
|
switch node.Type {
|
||||||
case html.TextNode:
|
case html.TextNode:
|
||||||
ctx.textNode(node)
|
if visitText {
|
||||||
|
ctx.textNode(node)
|
||||||
|
}
|
||||||
case html.ElementNode:
|
case html.ElementNode:
|
||||||
if node.Data == "a" || node.Data == "code" || node.Data == "pre" {
|
if node.Data == "img" {
|
||||||
|
attrs := node.Attr
|
||||||
|
for idx, attr := range attrs {
|
||||||
|
if attr.Key != "src" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
link := []byte(attr.Val)
|
||||||
|
if len(link) > 0 && !IsLink(link) {
|
||||||
|
prefix := ctx.urlPrefix
|
||||||
|
if ctx.isWikiMarkdown {
|
||||||
|
prefix = util.URLJoin(prefix, "wiki", "raw")
|
||||||
|
}
|
||||||
|
prefix = strings.Replace(prefix, "/src/", "/media/", 1)
|
||||||
|
|
||||||
|
lnk := string(link)
|
||||||
|
lnk = util.URLJoin(prefix, lnk)
|
||||||
|
link = []byte(lnk)
|
||||||
|
}
|
||||||
|
node.Attr[idx].Val = string(link)
|
||||||
|
}
|
||||||
|
} else if node.Data == "a" {
|
||||||
|
visitText = false
|
||||||
|
} else if node.Data == "code" || node.Data == "pre" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for n := node.FirstChild; n != nil; n = n.NextSibling {
|
for n := node.FirstChild; n != nil; n = n.NextSibling {
|
||||||
ctx.visitNode(n)
|
ctx.visitNode(n, visitText)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ignore everything else
|
// ignore everything else
|
||||||
|
|||||||
@@ -107,10 +107,11 @@ func init() {
|
|||||||
|
|
||||||
// VirtualStore represents a virtual session store implementation.
|
// VirtualStore represents a virtual session store implementation.
|
||||||
type VirtualStore struct {
|
type VirtualStore struct {
|
||||||
p *VirtualSessionProvider
|
p *VirtualSessionProvider
|
||||||
sid string
|
sid string
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
data map[interface{}]interface{}
|
data map[interface{}]interface{}
|
||||||
|
released bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewVirtualStore creates and returns a virtual session store.
|
// NewVirtualStore creates and returns a virtual session store.
|
||||||
@@ -164,7 +165,7 @@ func (s *VirtualStore) Release() error {
|
|||||||
// Now ensure that we don't exist!
|
// Now ensure that we don't exist!
|
||||||
realProvider := s.p.provider
|
realProvider := s.p.provider
|
||||||
|
|
||||||
if realProvider.Exist(s.sid) {
|
if !s.released && realProvider.Exist(s.sid) {
|
||||||
// This is an error!
|
// This is an error!
|
||||||
return fmt.Errorf("new sid '%s' already exists", s.sid)
|
return fmt.Errorf("new sid '%s' already exists", s.sid)
|
||||||
}
|
}
|
||||||
@@ -172,12 +173,19 @@ func (s *VirtualStore) Release() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := realStore.Flush(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
for key, value := range s.data {
|
for key, value := range s.data {
|
||||||
if err := realStore.Set(key, value); err != nil {
|
if err := realStore.Set(key, value); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return realStore.Release()
|
err = realStore.Release()
|
||||||
|
if err == nil {
|
||||||
|
s.released = true
|
||||||
|
}
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -382,7 +382,7 @@ func orgAssignment(args ...bool) macaron.Handler {
|
|||||||
|
|
||||||
var err error
|
var err error
|
||||||
if assignOrg {
|
if assignOrg {
|
||||||
ctx.Org.Organization, err = models.GetOrgByName(ctx.Params(":orgname"))
|
ctx.Org.Organization, err = models.GetOrgByName(ctx.Params(":org"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if models.IsErrOrgNotExist(err) {
|
if models.IsErrOrgNotExist(err) {
|
||||||
ctx.NotFound()
|
ctx.NotFound()
|
||||||
@@ -808,7 +808,7 @@ func RegisterRoutes(m *macaron.Macaron) {
|
|||||||
m.Get("/user/orgs", reqToken(), org.ListMyOrgs)
|
m.Get("/user/orgs", reqToken(), org.ListMyOrgs)
|
||||||
m.Get("/users/:username/orgs", org.ListUserOrgs)
|
m.Get("/users/:username/orgs", org.ListUserOrgs)
|
||||||
m.Post("/orgs", reqToken(), bind(api.CreateOrgOption{}), org.Create)
|
m.Post("/orgs", reqToken(), bind(api.CreateOrgOption{}), org.Create)
|
||||||
m.Group("/orgs/:orgname", func() {
|
m.Group("/orgs/:org", func() {
|
||||||
m.Get("/repos", user.ListOrgRepos)
|
m.Get("/repos", user.ListOrgRepos)
|
||||||
m.Combo("").Get(org.Get).
|
m.Combo("").Get(org.Get).
|
||||||
Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit).
|
Patch(reqToken(), reqOrgOwnership(), bind(api.EditOrgOption{}), org.Edit).
|
||||||
@@ -850,7 +850,7 @@ func RegisterRoutes(m *macaron.Macaron) {
|
|||||||
})
|
})
|
||||||
m.Group("/repos", func() {
|
m.Group("/repos", func() {
|
||||||
m.Get("", org.GetTeamRepos)
|
m.Get("", org.GetTeamRepos)
|
||||||
m.Combo("/:orgname/:reponame").
|
m.Combo("/:org/:reponame").
|
||||||
Put(org.AddTeamRepository).
|
Put(org.AddTeamRepository).
|
||||||
Delete(org.RemoveTeamRepository)
|
Delete(org.RemoveTeamRepository)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -384,6 +384,11 @@ func InstallPost(ctx *context.Context, form auth.InstallForm) {
|
|||||||
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
|
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = ctx.Session.Release(); err != nil {
|
||||||
|
ctx.RenderWithErr(ctx.Tr("install.save_config_failed", err), tplInstall, &form)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("First-time run install finished!")
|
log.Info("First-time run install finished!")
|
||||||
|
|||||||
@@ -201,6 +201,8 @@ func FileHistory(ctx *context.Context) {
|
|||||||
func Diff(ctx *context.Context) {
|
func Diff(ctx *context.Context) {
|
||||||
ctx.Data["PageIsDiff"] = true
|
ctx.Data["PageIsDiff"] = true
|
||||||
ctx.Data["RequireHighlightJS"] = true
|
ctx.Data["RequireHighlightJS"] = true
|
||||||
|
ctx.Data["RequireSimpleMDE"] = true
|
||||||
|
ctx.Data["RequireTribute"] = true
|
||||||
|
|
||||||
userName := ctx.Repo.Owner.Name
|
userName := ctx.Repo.Owner.Name
|
||||||
repoName := ctx.Repo.Repository.Name
|
repoName := ctx.Repo.Repository.Name
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import (
|
|||||||
"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"
|
||||||
|
"code.gitea.io/gitea/modules/structs"
|
||||||
"code.gitea.io/gitea/modules/timeutil"
|
"code.gitea.io/gitea/modules/timeutil"
|
||||||
repo_service "code.gitea.io/gitea/services/repository"
|
repo_service "code.gitea.io/gitea/services/repository"
|
||||||
)
|
)
|
||||||
@@ -135,6 +136,16 @@ func HTTP(ctx *context.Context) {
|
|||||||
environ []string
|
environ []string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// don't allow anonymous pulls if organization is not public
|
||||||
|
if isPublicPull {
|
||||||
|
if err := repo.GetOwner(); err != nil {
|
||||||
|
ctx.ServerError("GetOwner", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
askAuth = askAuth || (repo.Owner.Visibility != structs.VisibleTypePublic)
|
||||||
|
}
|
||||||
|
|
||||||
// check access
|
// check access
|
||||||
if askAuth {
|
if askAuth {
|
||||||
authUsername = ctx.Req.Header.Get(setting.ReverseProxyAuthUser)
|
authUsername = ctx.Req.Header.Get(setting.ReverseProxyAuthUser)
|
||||||
|
|||||||
@@ -81,14 +81,18 @@ func AutoSignIn(ctx *context.Context) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isSucceed = true
|
isSucceed = true
|
||||||
err = ctx.Session.Set("uid", u.ID)
|
|
||||||
if err != nil {
|
// Set session IDs
|
||||||
|
if err := ctx.Session.Set("uid", u.ID); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
err = ctx.Session.Set("uname", u.Name)
|
if err := ctx.Session.Set("uname", u.Name); err != nil {
|
||||||
if err != nil {
|
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
if err := ctx.Session.Release(); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
ctx.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true)
|
ctx.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true)
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
@@ -203,14 +207,16 @@ func SignInPost(ctx *context.Context, form auth.SignInForm) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// User needs to use 2FA, save data and redirect to 2FA page.
|
// User needs to use 2FA, save data and redirect to 2FA page.
|
||||||
err = ctx.Session.Set("twofaUid", u.ID)
|
if err := ctx.Session.Set("twofaUid", u.ID); err != nil {
|
||||||
if err != nil {
|
ctx.ServerError("UserSignIn: Unable to set twofaUid in session", err)
|
||||||
ctx.ServerError("UserSignIn", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = ctx.Session.Set("twofaRemember", form.Remember)
|
if err := ctx.Session.Set("twofaRemember", form.Remember); err != nil {
|
||||||
if err != nil {
|
ctx.ServerError("UserSignIn: Unable to set twofaRemember in session", err)
|
||||||
ctx.ServerError("UserSignIn", err)
|
return
|
||||||
|
}
|
||||||
|
if err := ctx.Session.Release(); err != nil {
|
||||||
|
ctx.ServerError("UserSignIn: Unable to save session", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,10 +413,14 @@ func U2FChallenge(ctx *context.Context) {
|
|||||||
ctx.ServerError("u2f.NewChallenge", err)
|
ctx.ServerError("u2f.NewChallenge", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err = ctx.Session.Set("u2fChallenge", challenge); err != nil {
|
if err := ctx.Session.Set("u2fChallenge", challenge); err != nil {
|
||||||
ctx.ServerError("UserSignIn", err)
|
ctx.ServerError("UserSignIn: unable to set u2fChallenge in session", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if err := ctx.Session.Release(); err != nil {
|
||||||
|
ctx.ServerError("UserSignIn: unable to store session", err)
|
||||||
|
}
|
||||||
|
|
||||||
ctx.JSON(200, challenge.SignRequest(regs.ToRegistrations()))
|
ctx.JSON(200, challenge.SignRequest(regs.ToRegistrations()))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -494,13 +504,14 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR
|
|||||||
_ = ctx.Session.Delete("twofaRemember")
|
_ = ctx.Session.Delete("twofaRemember")
|
||||||
_ = ctx.Session.Delete("u2fChallenge")
|
_ = ctx.Session.Delete("u2fChallenge")
|
||||||
_ = ctx.Session.Delete("linkAccount")
|
_ = ctx.Session.Delete("linkAccount")
|
||||||
err := ctx.Session.Set("uid", u.ID)
|
if err := ctx.Session.Set("uid", u.ID); err != nil {
|
||||||
if err != nil {
|
log.Error("Error setting uid %d in session: %v", u.ID, err)
|
||||||
log.Error(fmt.Sprintf("Error setting session: %v", err))
|
|
||||||
}
|
}
|
||||||
err = ctx.Session.Set("uname", u.Name)
|
if err := ctx.Session.Set("uname", u.Name); err != nil {
|
||||||
if err != nil {
|
log.Error("Error setting uname %s session: %v", u.Name, err)
|
||||||
log.Error(fmt.Sprintf("Error setting session: %v", err))
|
}
|
||||||
|
if err := ctx.Session.Release(); err != nil {
|
||||||
|
log.Error("Unable to store session: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Language setting of the user overwrites the one previously set
|
// Language setting of the user overwrites the one previously set
|
||||||
@@ -593,9 +604,11 @@ func handleOAuth2SignIn(u *models.User, gothUser goth.User, ctx *context.Context
|
|||||||
|
|
||||||
if u == nil {
|
if u == nil {
|
||||||
// no existing user is found, request attach or new account
|
// no existing user is found, request attach or new account
|
||||||
err = ctx.Session.Set("linkAccountGothUser", gothUser)
|
if err := ctx.Session.Set("linkAccountGothUser", gothUser); err != nil {
|
||||||
if err != nil {
|
log.Error("Error setting linkAccountGothUser in session: %v", err)
|
||||||
log.Error(fmt.Sprintf("Error setting session: %v", err))
|
}
|
||||||
|
if err := ctx.Session.Release(); err != nil {
|
||||||
|
log.Error("Error storing session: %v", err)
|
||||||
}
|
}
|
||||||
ctx.Redirect(setting.AppSubURL + "/user/link_account")
|
ctx.Redirect(setting.AppSubURL + "/user/link_account")
|
||||||
return
|
return
|
||||||
@@ -610,13 +623,14 @@ func handleOAuth2SignIn(u *models.User, gothUser goth.User, ctx *context.Context
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ctx.Session.Set("uid", u.ID)
|
if err := ctx.Session.Set("uid", u.ID); err != nil {
|
||||||
if err != nil {
|
log.Error("Error setting uid in session: %v", err)
|
||||||
log.Error(fmt.Sprintf("Error setting session: %v", err))
|
|
||||||
}
|
}
|
||||||
err = ctx.Session.Set("uname", u.Name)
|
if err := ctx.Session.Set("uname", u.Name); err != nil {
|
||||||
if err != nil {
|
log.Error("Error setting uname in session: %v", err)
|
||||||
log.Error(fmt.Sprintf("Error setting session: %v", err))
|
}
|
||||||
|
if err := ctx.Session.Release(); err != nil {
|
||||||
|
log.Error("Error storing session: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear whatever CSRF has right now, force to generate a new one
|
// Clear whatever CSRF has right now, force to generate a new one
|
||||||
@@ -645,13 +659,14 @@ func handleOAuth2SignIn(u *models.User, gothUser goth.User, ctx *context.Context
|
|||||||
}
|
}
|
||||||
|
|
||||||
// User needs to use 2FA, save data and redirect to 2FA page.
|
// User needs to use 2FA, save data and redirect to 2FA page.
|
||||||
err = ctx.Session.Set("twofaUid", u.ID)
|
if err := ctx.Session.Set("twofaUid", u.ID); err != nil {
|
||||||
if err != nil {
|
log.Error("Error setting twofaUid in session: %v", err)
|
||||||
log.Error(fmt.Sprintf("Error setting session: %v", err))
|
|
||||||
}
|
}
|
||||||
err = ctx.Session.Set("twofaRemember", false)
|
if err := ctx.Session.Set("twofaRemember", false); err != nil {
|
||||||
if err != nil {
|
log.Error("Error setting twofaRemember in session: %v", err)
|
||||||
log.Error(fmt.Sprintf("Error setting session: %v", err))
|
}
|
||||||
|
if err := ctx.Session.Release(); err != nil {
|
||||||
|
log.Error("Error storing session: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If U2F is enrolled -> Redirect to U2F instead
|
// If U2F is enrolled -> Redirect to U2F instead
|
||||||
@@ -816,17 +831,17 @@ func LinkAccountPostSignIn(ctx *context.Context, signInForm auth.SignInForm) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// User needs to use 2FA, save data and redirect to 2FA page.
|
// User needs to use 2FA, save data and redirect to 2FA page.
|
||||||
err = ctx.Session.Set("twofaUid", u.ID)
|
if err := ctx.Session.Set("twofaUid", u.ID); err != nil {
|
||||||
if err != nil {
|
log.Error("Error setting twofaUid in session: %v", err)
|
||||||
log.Error(fmt.Sprintf("Error setting session: %v", err))
|
|
||||||
}
|
}
|
||||||
err = ctx.Session.Set("twofaRemember", signInForm.Remember)
|
if err := ctx.Session.Set("twofaRemember", signInForm.Remember); err != nil {
|
||||||
if err != nil {
|
log.Error("Error setting twofaRemember in session: %v", err)
|
||||||
log.Error(fmt.Sprintf("Error setting session: %v", err))
|
|
||||||
}
|
}
|
||||||
err = ctx.Session.Set("linkAccount", true)
|
if err := ctx.Session.Set("linkAccount", true); err != nil {
|
||||||
if err != nil {
|
log.Error("Error setting linkAccount in session: %v", err)
|
||||||
log.Error(fmt.Sprintf("Error setting session: %v", err))
|
}
|
||||||
|
if err := ctx.Session.Release(); err != nil {
|
||||||
|
log.Error("Error storing session: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If U2F is enrolled -> Redirect to U2F instead
|
// If U2F is enrolled -> Redirect to U2F instead
|
||||||
@@ -988,11 +1003,8 @@ func LinkAccountPostRegister(ctx *context.Context, cpt *captcha.Captcha, form au
|
|||||||
}
|
}
|
||||||
|
|
||||||
func handleSignOut(ctx *context.Context) {
|
func handleSignOut(ctx *context.Context) {
|
||||||
_ = ctx.Session.Delete("uid")
|
_ = ctx.Session.Flush()
|
||||||
_ = ctx.Session.Delete("uname")
|
_ = ctx.Session.Destroy(ctx.Context)
|
||||||
_ = ctx.Session.Delete("socialId")
|
|
||||||
_ = ctx.Session.Delete("socialName")
|
|
||||||
_ = ctx.Session.Delete("socialEmail")
|
|
||||||
ctx.SetCookie(setting.CookieUserName, "", -1, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true)
|
ctx.SetCookie(setting.CookieUserName, "", -1, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true)
|
||||||
ctx.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true)
|
ctx.SetCookie(setting.CookieRememberName, "", -1, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true)
|
||||||
ctx.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true)
|
ctx.SetCookie(setting.CSRFCookieName, "", -1, setting.AppSubURL, setting.SessionConfig.Domain, setting.SessionConfig.Secure, true)
|
||||||
@@ -1187,14 +1199,16 @@ func Activate(ctx *context.Context) {
|
|||||||
|
|
||||||
log.Trace("User activated: %s", user.Name)
|
log.Trace("User activated: %s", user.Name)
|
||||||
|
|
||||||
err = ctx.Session.Set("uid", user.ID)
|
if err := ctx.Session.Set("uid", user.ID); err != nil {
|
||||||
if err != nil {
|
log.Error(fmt.Sprintf("Error setting uid in session: %v", err))
|
||||||
log.Error(fmt.Sprintf("Error setting session: %v", err))
|
|
||||||
}
|
}
|
||||||
err = ctx.Session.Set("uname", user.Name)
|
if err := ctx.Session.Set("uname", user.Name); err != nil {
|
||||||
if err != nil {
|
log.Error(fmt.Sprintf("Error setting uname in session: %v", err))
|
||||||
log.Error(fmt.Sprintf("Error setting session: %v", err))
|
|
||||||
}
|
}
|
||||||
|
if err := ctx.Session.Release(); err != nil {
|
||||||
|
log.Error("Error storing session: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
ctx.Flash.Success(ctx.Tr("auth.account_activated"))
|
ctx.Flash.Success(ctx.Tr("auth.account_activated"))
|
||||||
ctx.Redirect(setting.AppSubURL + "/")
|
ctx.Redirect(setting.AppSubURL + "/")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -128,9 +128,12 @@ func SignInOpenIDPost(ctx *context.Context, form auth.SignInOpenIDForm) {
|
|||||||
url += "&openid.sreg.optional=nickname%2Cemail"
|
url += "&openid.sreg.optional=nickname%2Cemail"
|
||||||
|
|
||||||
log.Trace("Form-passed openid-remember: %t", form.Remember)
|
log.Trace("Form-passed openid-remember: %t", form.Remember)
|
||||||
err = ctx.Session.Set("openid_signin_remember", form.Remember)
|
|
||||||
if err != nil {
|
if err := ctx.Session.Set("openid_signin_remember", form.Remember); err != nil {
|
||||||
log.Error("SignInOpenIDPost: Could not set session: %v", err.Error())
|
log.Error("SignInOpenIDPost: Could not set openid_signin_remember in session: %v", err)
|
||||||
|
}
|
||||||
|
if err := ctx.Session.Release(); err != nil {
|
||||||
|
log.Error("SignInOpenIDPost: Unable to save changes to the session: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Redirect(url)
|
ctx.Redirect(url)
|
||||||
@@ -227,23 +230,22 @@ func signInOpenIDVerify(ctx *context.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ctx.Session.Set("openid_verified_uri", id)
|
if err := ctx.Session.Set("openid_verified_uri", id); err != nil {
|
||||||
if err != nil {
|
log.Error("signInOpenIDVerify: Could not set openid_verified_uri in session: %v", err)
|
||||||
log.Error("signInOpenIDVerify: Could not set session: %v", err.Error())
|
|
||||||
}
|
}
|
||||||
|
if err := ctx.Session.Set("openid_determined_email", email); err != nil {
|
||||||
err = ctx.Session.Set("openid_determined_email", email)
|
log.Error("signInOpenIDVerify: Could not set openid_determined_email in session: %v", err)
|
||||||
if err != nil {
|
|
||||||
log.Error("signInOpenIDVerify: Could not set session: %v", err.Error())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if u != nil {
|
if u != nil {
|
||||||
nickname = u.LowerName
|
nickname = u.LowerName
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ctx.Session.Set("openid_determined_username", nickname)
|
if err := ctx.Session.Set("openid_determined_username", nickname); err != nil {
|
||||||
if err != nil {
|
log.Error("signInOpenIDVerify: Could not set openid_determined_username in session: %v", err)
|
||||||
log.Error("signInOpenIDVerify: Could not set session: %v", err.Error())
|
}
|
||||||
|
if err := ctx.Session.Release(); err != nil {
|
||||||
|
log.Error("signInOpenIDVerify: Unable to save changes to the session: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if u != nil || !setting.Service.EnableOpenIDSignUp {
|
if u != nil || !setting.Service.EnableOpenIDSignUp {
|
||||||
|
|||||||
@@ -229,6 +229,11 @@ func AuthorizeOAuth(ctx *context.Context, form auth.AuthorizationForm) {
|
|||||||
}, form.RedirectURI)
|
}, form.RedirectURI)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Here we're just going to try to release the session early
|
||||||
|
if err := ctx.Session.Release(); err != nil {
|
||||||
|
// we'll tolerate errors here as they *should* get saved elsewhere
|
||||||
|
log.Error("Unable to save changes to the session: %v", err)
|
||||||
|
}
|
||||||
case "":
|
case "":
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
@@ -287,6 +292,11 @@ func AuthorizeOAuth(ctx *context.Context, form auth.AuthorizationForm) {
|
|||||||
log.Error(err.Error())
|
log.Error(err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Here we're just going to try to release the session early
|
||||||
|
if err := ctx.Session.Release(); err != nil {
|
||||||
|
// we'll tolerate errors here as they *should* get saved elsewhere
|
||||||
|
log.Error("Unable to save changes to the session: %v", err)
|
||||||
|
}
|
||||||
ctx.HTML(200, tplGrantAccess)
|
ctx.HTML(200, tplGrantAccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
"code.gitea.io/gitea/modules/auth"
|
"code.gitea.io/gitea/modules/auth"
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/pquerna/otp"
|
"github.com/pquerna/otp"
|
||||||
@@ -28,18 +29,22 @@ func RegenerateScratchTwoFactor(ctx *context.Context) {
|
|||||||
|
|
||||||
t, err := models.GetTwoFactorByUID(ctx.User.ID)
|
t, err := models.GetTwoFactorByUID(ctx.User.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("SettingsTwoFactor", err)
|
if models.IsErrTwoFactorNotEnrolled(err) {
|
||||||
|
ctx.Flash.Error(ctx.Tr("setting.twofa_not_enrolled"))
|
||||||
|
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||||
|
}
|
||||||
|
ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := t.GenerateScratchToken()
|
token, err := t.GenerateScratchToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("SettingsTwoFactor", err)
|
ctx.ServerError("SettingsTwoFactor: Failed to GenerateScratchToken", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = models.UpdateTwoFactor(t); err != nil {
|
if err = models.UpdateTwoFactor(t); err != nil {
|
||||||
ctx.ServerError("SettingsTwoFactor", err)
|
ctx.ServerError("SettingsTwoFactor: Failed to UpdateTwoFactor", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,12 +59,21 @@ func DisableTwoFactor(ctx *context.Context) {
|
|||||||
|
|
||||||
t, err := models.GetTwoFactorByUID(ctx.User.ID)
|
t, err := models.GetTwoFactorByUID(ctx.User.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("SettingsTwoFactor", err)
|
if models.IsErrTwoFactorNotEnrolled(err) {
|
||||||
|
ctx.Flash.Error(ctx.Tr("setting.twofa_not_enrolled"))
|
||||||
|
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||||
|
}
|
||||||
|
ctx.ServerError("SettingsTwoFactor: Failed to GetTwoFactorByUID", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = models.DeleteTwoFactorByID(t.ID, ctx.User.ID); err != nil {
|
if err = models.DeleteTwoFactorByID(t.ID, ctx.User.ID); err != nil {
|
||||||
ctx.ServerError("SettingsTwoFactor", err)
|
if models.IsErrTwoFactorNotEnrolled(err) {
|
||||||
|
// There is a potential DB race here - we must have been disabled by another request in the intervening period
|
||||||
|
ctx.Flash.Success(ctx.Tr("settings.twofa_disabled"))
|
||||||
|
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||||
|
}
|
||||||
|
ctx.ServerError("SettingsTwoFactor: Failed to DeleteTwoFactorByID", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,7 +88,7 @@ func twofaGenerateSecretAndQr(ctx *context.Context) bool {
|
|||||||
if uri != nil {
|
if uri != nil {
|
||||||
otpKey, err = otp.NewKeyFromURL(uri.(string))
|
otpKey, err = otp.NewKeyFromURL(uri.(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("SettingsTwoFactor: NewKeyFromURL: ", err)
|
ctx.ServerError("SettingsTwoFactor: Failed NewKeyFromURL: ", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -87,7 +101,7 @@ func twofaGenerateSecretAndQr(ctx *context.Context) bool {
|
|||||||
AccountName: ctx.User.Name,
|
AccountName: ctx.User.Name,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("SettingsTwoFactor", err)
|
ctx.ServerError("SettingsTwoFactor: totpGenerate Failed", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -95,27 +109,33 @@ func twofaGenerateSecretAndQr(ctx *context.Context) bool {
|
|||||||
ctx.Data["TwofaSecret"] = otpKey.Secret()
|
ctx.Data["TwofaSecret"] = otpKey.Secret()
|
||||||
img, err := otpKey.Image(320, 240)
|
img, err := otpKey.Image(320, 240)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("SettingsTwoFactor", err)
|
ctx.ServerError("SettingsTwoFactor: otpKey image generation failed", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
var imgBytes bytes.Buffer
|
var imgBytes bytes.Buffer
|
||||||
if err = png.Encode(&imgBytes, img); err != nil {
|
if err = png.Encode(&imgBytes, img); err != nil {
|
||||||
ctx.ServerError("SettingsTwoFactor", err)
|
ctx.ServerError("SettingsTwoFactor: otpKey png encoding failed", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Data["QrUri"] = template.URL("data:image/png;base64," + base64.StdEncoding.EncodeToString(imgBytes.Bytes()))
|
ctx.Data["QrUri"] = template.URL("data:image/png;base64," + base64.StdEncoding.EncodeToString(imgBytes.Bytes()))
|
||||||
err = ctx.Session.Set("twofaSecret", otpKey.Secret())
|
|
||||||
if err != nil {
|
if err := ctx.Session.Set("twofaSecret", otpKey.Secret()); err != nil {
|
||||||
ctx.ServerError("SettingsTwoFactor", err)
|
ctx.ServerError("SettingsTwoFactor: Failed to set session for twofaSecret", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
err = ctx.Session.Set("twofaUri", otpKey.String())
|
|
||||||
if err != nil {
|
if err := ctx.Session.Set("twofaUri", otpKey.String()); err != nil {
|
||||||
ctx.ServerError("SettingsTwoFactor", err)
|
ctx.ServerError("SettingsTwoFactor: Failed to set session for twofaUri", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Here we're just going to try to release the session early
|
||||||
|
if err := ctx.Session.Release(); err != nil {
|
||||||
|
// we'll tolerate errors here as they *should* get saved elsewhere
|
||||||
|
log.Error("Unable to save changes to the session: %v", err)
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,12 +146,14 @@ func EnrollTwoFactor(ctx *context.Context) {
|
|||||||
|
|
||||||
t, err := models.GetTwoFactorByUID(ctx.User.ID)
|
t, err := models.GetTwoFactorByUID(ctx.User.ID)
|
||||||
if t != nil {
|
if t != nil {
|
||||||
// already enrolled
|
// already enrolled - we should redirect back!
|
||||||
ctx.ServerError("SettingsTwoFactor", err)
|
log.Warn("Trying to re-enroll %-v in twofa when already enrolled", ctx.User)
|
||||||
|
ctx.Flash.Error(ctx.Tr("setting.twofa_is_enrolled"))
|
||||||
|
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil && !models.IsErrTwoFactorNotEnrolled(err) {
|
if err != nil && !models.IsErrTwoFactorNotEnrolled(err) {
|
||||||
ctx.ServerError("SettingsTwoFactor", err)
|
ctx.ServerError("SettingsTwoFactor: GetTwoFactorByUID", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,11 +172,12 @@ func EnrollTwoFactorPost(ctx *context.Context, form auth.TwoFactorAuthForm) {
|
|||||||
t, err := models.GetTwoFactorByUID(ctx.User.ID)
|
t, err := models.GetTwoFactorByUID(ctx.User.ID)
|
||||||
if t != nil {
|
if t != nil {
|
||||||
// already enrolled
|
// already enrolled
|
||||||
ctx.ServerError("SettingsTwoFactor", err)
|
ctx.Flash.Error(ctx.Tr("setting.twofa_is_enrolled"))
|
||||||
|
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err != nil && !models.IsErrTwoFactorNotEnrolled(err) {
|
if err != nil && !models.IsErrTwoFactorNotEnrolled(err) {
|
||||||
ctx.ServerError("SettingsTwoFactor", err)
|
ctx.ServerError("SettingsTwoFactor: Failed to check if already enrolled with GetTwoFactorByUID", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,30 +204,37 @@ func EnrollTwoFactorPost(ctx *context.Context, form auth.TwoFactorAuthForm) {
|
|||||||
}
|
}
|
||||||
err = t.SetSecret(secret)
|
err = t.SetSecret(secret)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("SettingsTwoFactor", err)
|
ctx.ServerError("SettingsTwoFactor: Failed to set secret", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
token, err := t.GenerateScratchToken()
|
token, err := t.GenerateScratchToken()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ctx.ServerError("SettingsTwoFactor", err)
|
ctx.ServerError("SettingsTwoFactor: Failed to generate scratch token", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now we have to delete the secrets - because if we fail to insert then it's highly likely that they have already been used
|
||||||
|
// If we can detect the unique constraint failure below we can move this to after the NewTwoFactor
|
||||||
|
if err := ctx.Session.Delete("twofaSecret"); err != nil {
|
||||||
|
// tolerate this failure - it's more important to continue
|
||||||
|
log.Error("Unable to delete twofaSecret from the session: Error: %v", err)
|
||||||
|
}
|
||||||
|
if err := ctx.Session.Delete("twofaUri"); err != nil {
|
||||||
|
// tolerate this failure - it's more important to continue
|
||||||
|
log.Error("Unable to delete twofaUri from the session: Error: %v", err)
|
||||||
|
}
|
||||||
|
if err := ctx.Session.Release(); err != nil {
|
||||||
|
// tolerate this failure - it's more important to continue
|
||||||
|
log.Error("Unable to save changes to the session: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
if err = models.NewTwoFactor(t); err != nil {
|
if err = models.NewTwoFactor(t); err != nil {
|
||||||
ctx.ServerError("SettingsTwoFactor", err)
|
// FIXME: We need to handle a unique constraint fail here it's entirely possible that another request has beaten us.
|
||||||
|
// If there is a unique constraint fail we should just tolerate the error
|
||||||
|
ctx.ServerError("SettingsTwoFactor: Failed to save two factor", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ctx.Session.Delete("twofaSecret")
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("SettingsTwoFactor", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = ctx.Session.Delete("twofaUri")
|
|
||||||
if err != nil {
|
|
||||||
ctx.ServerError("SettingsTwoFactor", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ctx.Flash.Success(ctx.Tr("settings.twofa_enrolled", token))
|
ctx.Flash.Success(ctx.Tr("settings.twofa_enrolled", token))
|
||||||
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
ctx.Redirect(setting.AppSubURL + "/user/settings/security")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"code.gitea.io/gitea/models"
|
"code.gitea.io/gitea/models"
|
||||||
"code.gitea.io/gitea/modules/auth"
|
"code.gitea.io/gitea/modules/auth"
|
||||||
"code.gitea.io/gitea/modules/context"
|
"code.gitea.io/gitea/modules/context"
|
||||||
|
"code.gitea.io/gitea/modules/log"
|
||||||
"code.gitea.io/gitea/modules/setting"
|
"code.gitea.io/gitea/modules/setting"
|
||||||
|
|
||||||
"github.com/tstranex/u2f"
|
"github.com/tstranex/u2f"
|
||||||
@@ -26,9 +27,8 @@ func U2FRegister(ctx *context.Context, form auth.U2FRegistrationForm) {
|
|||||||
ctx.ServerError("NewChallenge", err)
|
ctx.ServerError("NewChallenge", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = ctx.Session.Set("u2fChallenge", challenge)
|
if err := ctx.Session.Set("u2fChallenge", challenge); err != nil {
|
||||||
if err != nil {
|
ctx.ServerError("Unable to set session key for u2fChallenge", err)
|
||||||
ctx.ServerError("Session.Set", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
regs, err := models.GetU2FRegistrationsByUID(ctx.User.ID)
|
regs, err := models.GetU2FRegistrationsByUID(ctx.User.ID)
|
||||||
@@ -42,11 +42,15 @@ func U2FRegister(ctx *context.Context, form auth.U2FRegistrationForm) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = ctx.Session.Set("u2fName", form.Name)
|
if err := ctx.Session.Set("u2fName", form.Name); err != nil {
|
||||||
if err != nil {
|
ctx.ServerError("Unable to set session key for u2fName", err)
|
||||||
ctx.ServerError("", err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Here we're just going to try to release the session early
|
||||||
|
if err := ctx.Session.Release(); err != nil {
|
||||||
|
// we'll tolerate errors here as they *should* get saved elsewhere
|
||||||
|
log.Error("Unable to save changes to the session: %v", err)
|
||||||
|
}
|
||||||
ctx.JSON(200, u2f.NewWebRegisterRequest(challenge, regs.ToRegistrations()))
|
ctx.JSON(200, u2f.NewWebRegisterRequest(challenge, regs.ToRegistrations()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,11 @@
|
|||||||
for the JavaScript code in this page.
|
for the JavaScript code in this page.
|
||||||
*/`}}
|
*/`}}
|
||||||
</script>
|
</script>
|
||||||
|
<script>
|
||||||
|
window.config = {
|
||||||
|
StaticUrlPrefix: '{{StaticUrlPrefix}}'
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<link rel="shortcut icon" href="{{StaticUrlPrefix}}/img/favicon.png" />
|
<link rel="shortcut icon" href="{{StaticUrlPrefix}}/img/favicon.png" />
|
||||||
<link rel="mask-icon" href="{{StaticUrlPrefix}}/img/gitea-safari.svg" color="#609926">
|
<link rel="mask-icon" href="{{StaticUrlPrefix}}/img/gitea-safari.svg" color="#609926">
|
||||||
<link rel="preload" href="{{StaticUrlPrefix}}/vendor/assets/font-awesome/css/font-awesome.min.css" as="style" onload="this.rel='stylesheet'">
|
<link rel="preload" href="{{StaticUrlPrefix}}/vendor/assets/font-awesome/css/font-awesome.min.css" as="style" onload="this.rel='stylesheet'">
|
||||||
|
|||||||
@@ -1,40 +1,40 @@
|
|||||||
{{if eq .HookType "gitea"}}
|
{{if eq .HookType "gitea"}}
|
||||||
<p>{{.i18n.Tr "repo.settings.add_webhook_desc" "https://docs.gitea.io/en-us/webhooks/" | Str2html}}</p>
|
<p>{{.i18n.Tr "repo.settings.add_webhook_desc" "https://docs.gitea.io/en-us/webhooks/" | Str2html}}</p>
|
||||||
<form class="ui form" action="{{.BaseLink}}/gitea/{{or .Webhook.ID "new"}}" method="post">
|
<form class="ui form" action="{{.BaseLink}}/gitea/{{or .Webhook.ID "new"}}" method="post">
|
||||||
{{.CsrfTokenHtml}}
|
{{.CsrfTokenHtml}}
|
||||||
<div class="required field {{if .Err_PayloadURL}}error{{end}}">
|
<div class="required field {{if .Err_PayloadURL}}error{{end}}">
|
||||||
<label for="payload_url">{{.i18n.Tr "repo.settings.payload_url"}}</label>
|
<label for="payload_url">{{.i18n.Tr "repo.settings.payload_url"}}</label>
|
||||||
<input id="payload_url" name="payload_url" type="url" value="{{.Webhook.URL}}" autofocus required>
|
<input id="payload_url" name="payload_url" type="url" value="{{.Webhook.URL}}" autofocus required>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>{{.i18n.Tr "repo.settings.http_method"}}</label>
|
<label>{{.i18n.Tr "repo.settings.http_method"}}</label>
|
||||||
<div class="ui selection dropdown">
|
<div class="ui selection dropdown">
|
||||||
<input type="hidden" id="http_method" name="http_method" value="{{if .Webhook.HTTPMethod}}{{.Webhook.HTTPMethod}}{{else}}POST{{end}}">
|
<input type="hidden" id="http_method" name="http_method" value="{{if .Webhook.HTTPMethod}}{{.Webhook.HTTPMethod}}{{else}}POST{{end}}">
|
||||||
<div class="default text"></div>
|
<div class="default text"></div>
|
||||||
<i class="dropdown icon"></i>
|
<i class="dropdown icon"></i>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<div class="item" data-value="POST">POST</div>
|
<div class="item" data-value="POST">POST</div>
|
||||||
<div class="item" data-value="GET">GET</div>
|
<div class="item" data-value="GET">GET</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
<label>{{.i18n.Tr "repo.settings.content_type"}}</label>
|
<label>{{.i18n.Tr "repo.settings.content_type"}}</label>
|
||||||
<div class="ui selection dropdown">
|
<div class="ui selection dropdown">
|
||||||
<input type="hidden" id="content_type" name="content_type" value="{{if .Webhook.ContentType}}{{.Webhook.ContentType}}{{else}}application/json{{end}}">
|
<input type="hidden" id="content_type" name="content_type" value="{{if .Webhook.ContentType}}{{.Webhook.ContentType}}{{else}}1{{end}}">
|
||||||
<div class="default text"></div>
|
<div class="default text"></div>
|
||||||
<i class="dropdown icon"></i>
|
<i class="dropdown icon"></i>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<div class="item" data-value="1">application/json</div>
|
<div class="item" data-value="1">application/json</div>
|
||||||
<div class="item" data-value="2">application/x-www-form-urlencoded</div>
|
<div class="item" data-value="2">application/x-www-form-urlencoded</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input class="fake" type="password">
|
<input class="fake" type="password">
|
||||||
<div class="field {{if .Err_Secret}}error{{end}}">
|
<div class="field {{if .Err_Secret}}error{{end}}">
|
||||||
<label for="secret">{{.i18n.Tr "repo.settings.secret"}}</label>
|
<label for="secret">{{.i18n.Tr "repo.settings.secret"}}</label>
|
||||||
<input id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off">
|
<input id="secret" name="secret" type="password" value="{{.Webhook.Secret}}" autocomplete="off">
|
||||||
</div>
|
</div>
|
||||||
{{template "repo/settings/webhook/settings" .}}
|
{{template "repo/settings/webhook/settings" .}}
|
||||||
</form>
|
</form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
<label>{{.i18n.Tr "repo.settings.content_type"}}</label>
|
<label>{{.i18n.Tr "repo.settings.content_type"}}</label>
|
||||||
<div class="ui selection dropdown">
|
<div class="ui selection dropdown">
|
||||||
<input type="hidden" id="content_type" name="content_type" value="{{if .Webhook.ContentType}}{{.Webhook.ContentType}}{{else}}application/json{{end}}">
|
<input type="hidden" id="content_type" name="content_type" value="{{if .Webhook.ContentType}}{{.Webhook.ContentType}}{{else}}1{{end}}">
|
||||||
<div class="default text"></div>
|
<div class="default text"></div>
|
||||||
<i class="dropdown icon"></i>
|
<i class="dropdown icon"></i>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
{{else if and (not $.DisableSSH) (or $.IsSigned $.ExposeAnonSSH)}}
|
{{else if and (not $.DisableSSH) (or $.IsSigned $.ExposeAnonSSH)}}
|
||||||
<input id="repo-clone-url" value="{{$.WikiCloneLink.SSH}}" readonly>
|
<input id="repo-clone-url" value="{{$.WikiCloneLink.SSH}}" readonly>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{if or ((not $.DisableHTTP) (and (not $.DisableSSH) (or $.IsSigned $.ExposeAnonSSH)))}}
|
{{if or (not $.DisableHTTP) (and (not $.DisableSSH) (or $.IsSigned $.ExposeAnonSSH))}}
|
||||||
<button class="ui basic icon button poping up clipboard" id="clipboard-btn" data-original="{{.i18n.Tr "repo.copy_link"}}" data-success="{{.i18n.Tr "repo.copy_link_success"}}" data-error="{{.i18n.Tr "repo.copy_link_error"}}" data-content="{{.i18n.Tr "repo.copy_link"}}" data-variation="inverted tiny" data-clipboard-target="#repo-clone-url">
|
<button class="ui basic icon button poping up clipboard" id="clipboard-btn" data-original="{{.i18n.Tr "repo.copy_link"}}" data-success="{{.i18n.Tr "repo.copy_link_success"}}" data-error="{{.i18n.Tr "repo.copy_link_error"}}" data-content="{{.i18n.Tr "repo.copy_link"}}" data-variation="inverted tiny" data-clipboard-target="#repo-clone-url">
|
||||||
<i class="octicon octicon-clippy"></i>
|
<i class="octicon octicon-clippy"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
/* This sets up webpack's chunk loading to load resources from the same
|
// This sets up the URL prefix used in webpack's chunk loading.
|
||||||
directory where it loaded index.js from. This file must be imported
|
// This file must be imported before any lazy-loading is being attempted.
|
||||||
before any lazy-loading is being attempted. */
|
const { StaticUrlPrefix } = window.config;
|
||||||
|
|
||||||
if (document.currentScript && document.currentScript.src) {
|
if (StaticUrlPrefix) {
|
||||||
|
__webpack_public_path__ = StaticUrlPrefix.endsWith('/') ? StaticUrlPrefix : `${StaticUrlPrefix}/`;
|
||||||
|
} else if (document.currentScript && document.currentScript.src) {
|
||||||
const url = new URL(document.currentScript.src);
|
const url = new URL(document.currentScript.src);
|
||||||
__webpack_public_path__ = `${url.pathname.replace(/\/[^/]*$/, '')}/`;
|
__webpack_public_path__ = `${url.pathname.replace(/\/[^/]*$/, '')}/`;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user