internal: move packages under this directory (#5836)

* Rename pkg -> internal

* Rename routes -> route

* Move route -> internal/route

* Rename models -> db

* Move db -> internal/db

* Fix route2 -> route

* Move cmd -> internal/cmd

* Bump version
This commit is contained in:
Unknwon
2019-10-24 01:51:46 -07:00
committed by GitHub
parent 613139e7be
commit 01c8df01ec
178 changed files with 1609 additions and 1608 deletions

107
internal/context/api.go Normal file
View File

@@ -0,0 +1,107 @@
// Copyright 2016 The Gogs 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 context
import (
"fmt"
"net/http"
"strings"
"github.com/unknwon/paginater"
log "gopkg.in/clog.v1"
"gopkg.in/macaron.v1"
"gogs.io/gogs/internal/setting"
)
type APIContext struct {
*Context // TODO: Reduce to only needed fields instead of full shadow
// Base URL for the version of API endpoints, e.g. https://try.gogs.io/api/v1
BaseURL string
Org *APIOrganization
}
// FIXME: move this constant to github.com/gogs/go-gogs-client
const DocURL = "https://github.com/gogs/docs-api"
// Error responses error message to client with given message.
// If status is 500, also it prints error to log.
func (c *APIContext) Error(status int, title string, obj interface{}) {
var message string
if err, ok := obj.(error); ok {
message = err.Error()
} else {
message = obj.(string)
}
if status == http.StatusInternalServerError {
log.Error(3, "%s: %s", title, message)
}
c.JSON(status, map[string]string{
"message": message,
"url": DocURL,
})
}
// NoContent renders the 204 response.
func (c *APIContext) NoContent() {
c.Status(http.StatusNoContent)
}
// NotFound renders the 404 response.
func (c *APIContext) NotFound() {
c.Status(http.StatusNotFound)
}
// ServerError renders the 500 response.
func (c *APIContext) ServerError(title string, err error) {
c.Error(http.StatusInternalServerError, title, err)
}
// NotFoundOrServerError use error check function to determine if the error
// is about not found. It responses with 404 status code for not found error,
// or error context description for logging purpose of 500 server error.
func (c *APIContext) NotFoundOrServerError(title string, errck func(error) bool, err error) {
if errck(err) {
c.NotFound()
return
}
c.ServerError(title, err)
}
// SetLinkHeader sets pagination link header by given total number and page size.
func (c *APIContext) SetLinkHeader(total, pageSize int) {
page := paginater.New(total, pageSize, c.QueryInt("page"), 0)
links := make([]string, 0, 4)
if page.HasNext() {
links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"next\"", setting.AppURL, c.Req.URL.Path[1:], page.Next()))
}
if !page.IsLast() {
links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"last\"", setting.AppURL, c.Req.URL.Path[1:], page.TotalPages()))
}
if !page.IsFirst() {
links = append(links, fmt.Sprintf("<%s%s?page=1>; rel=\"first\"", setting.AppURL, c.Req.URL.Path[1:]))
}
if page.HasPrevious() {
links = append(links, fmt.Sprintf("<%s%s?page=%d>; rel=\"prev\"", setting.AppURL, c.Req.URL.Path[1:], page.Previous()))
}
if len(links) > 0 {
c.Header().Set("Link", strings.Join(links, ","))
}
}
func APIContexter() macaron.Handler {
return func(ctx *Context) {
c := &APIContext{
Context: ctx,
BaseURL: setting.AppURL + "api/v1",
}
ctx.Map(c)
}
}

View File

@@ -0,0 +1,14 @@
// Copyright 2016 The Gogs 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 context
import (
"gogs.io/gogs/internal/db"
)
type APIOrganization struct {
Organization *db.User
Team *db.Team
}

112
internal/context/auth.go Normal file
View File

@@ -0,0 +1,112 @@
// Copyright 2014 The Gogs 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 context
import (
"net/http"
"net/url"
"strings"
"github.com/go-macaron/csrf"
"gopkg.in/macaron.v1"
"gogs.io/gogs/internal/auth"
"gogs.io/gogs/internal/setting"
"gogs.io/gogs/internal/tool"
)
type ToggleOptions struct {
SignInRequired bool
SignOutRequired bool
AdminRequired bool
DisableCSRF bool
}
func Toggle(options *ToggleOptions) macaron.Handler {
return func(c *Context) {
// Cannot view any page before installation.
if !setting.InstallLock {
c.Redirect(setting.AppSubURL + "/install")
return
}
// Check prohibit login users.
if c.IsLogged && c.User.ProhibitLogin {
c.Data["Title"] = c.Tr("auth.prohibit_login")
c.HTML(200, "user/auth/prohibit_login")
return
}
// Check non-logged users landing page.
if !c.IsLogged && c.Req.RequestURI == "/" && setting.LandingPageURL != setting.LANDING_PAGE_HOME {
c.Redirect(setting.AppSubURL + string(setting.LandingPageURL))
return
}
// Redirect to dashboard if user tries to visit any non-login page.
if options.SignOutRequired && c.IsLogged && c.Req.RequestURI != "/" {
c.Redirect(setting.AppSubURL + "/")
return
}
if !options.SignOutRequired && !options.DisableCSRF && c.Req.Method == "POST" && !auth.IsAPIPath(c.Req.URL.Path) {
csrf.Validate(c.Context, c.csrf)
if c.Written() {
return
}
}
if options.SignInRequired {
if !c.IsLogged {
// Restrict API calls with error message.
if auth.IsAPIPath(c.Req.URL.Path) {
c.JSON(403, map[string]string{
"message": "Only signed in user is allowed to call APIs.",
})
return
}
c.SetCookie("redirect_to", url.QueryEscape(setting.AppSubURL+c.Req.RequestURI), 0, setting.AppSubURL)
c.Redirect(setting.AppSubURL + "/user/login")
return
} else if !c.User.IsActive && setting.Service.RegisterEmailConfirm {
c.Data["Title"] = c.Tr("auth.active_your_account")
c.HTML(200, "user/auth/activate")
return
}
}
// Redirect to log in page if auto-signin info is provided and has not signed in.
if !options.SignOutRequired && !c.IsLogged && !auth.IsAPIPath(c.Req.URL.Path) &&
len(c.GetCookie(setting.CookieUserName)) > 0 {
c.SetCookie("redirect_to", url.QueryEscape(setting.AppSubURL+c.Req.RequestURI), 0, setting.AppSubURL)
c.Redirect(setting.AppSubURL + "/user/login")
return
}
if options.AdminRequired {
if !c.User.IsAdmin {
c.Error(403)
return
}
c.Data["PageIsAdmin"] = true
}
}
}
// RequireBasicAuth verifies HTTP Basic Authentication header with given credentials
func (c *Context) RequireBasicAuth(username, password string) {
fields := strings.Fields(c.Req.Header.Get("Authorization"))
if len(fields) != 2 || fields[0] != "Basic" {
c.Status(http.StatusUnauthorized)
return
}
uname, passwd, _ := tool.BasicAuthDecode(fields[1])
if uname != username || passwd != password {
c.Status(http.StatusForbidden)
return
}
}

334
internal/context/context.go Normal file
View File

@@ -0,0 +1,334 @@
// Copyright 2014 The Gogs 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 context
import (
"fmt"
"io"
"net/http"
"path"
"strings"
"time"
"github.com/go-macaron/cache"
"github.com/go-macaron/csrf"
"github.com/go-macaron/i18n"
"github.com/go-macaron/session"
"github.com/unknwon/com"
log "gopkg.in/clog.v1"
"gopkg.in/macaron.v1"
"gogs.io/gogs/internal/auth"
"gogs.io/gogs/internal/db"
"gogs.io/gogs/internal/db/errors"
"gogs.io/gogs/internal/form"
"gogs.io/gogs/internal/setting"
"gogs.io/gogs/internal/template"
)
// Context represents context of a request.
type Context struct {
*macaron.Context
Cache cache.Cache
csrf csrf.CSRF
Flash *session.Flash
Session session.Store
Link string // Current request URL
User *db.User
IsLogged bool
IsBasicAuth bool
IsTokenAuth bool
Repo *Repository
Org *Organization
}
// Title sets "Title" field in template data.
func (c *Context) Title(locale string) {
c.Data["Title"] = c.Tr(locale)
}
// PageIs sets "PageIsxxx" field in template data.
func (c *Context) PageIs(name string) {
c.Data["PageIs"+name] = true
}
// Require sets "Requirexxx" field in template data.
func (c *Context) Require(name string) {
c.Data["Require"+name] = true
}
func (c *Context) RequireHighlightJS() {
c.Require("HighlightJS")
}
func (c *Context) RequireSimpleMDE() {
c.Require("SimpleMDE")
}
func (c *Context) RequireAutosize() {
c.Require("Autosize")
}
func (c *Context) RequireDropzone() {
c.Require("Dropzone")
}
// FormErr sets "Err_xxx" field in template data.
func (c *Context) FormErr(names ...string) {
for i := range names {
c.Data["Err_"+names[i]] = true
}
}
// UserID returns ID of current logged in user.
// It returns 0 if visitor is anonymous.
func (c *Context) UserID() int64 {
if !c.IsLogged {
return 0
}
return c.User.ID
}
// HasError returns true if error occurs in form validation.
func (c *Context) HasApiError() bool {
hasErr, ok := c.Data["HasError"]
if !ok {
return false
}
return hasErr.(bool)
}
func (c *Context) GetErrMsg() string {
return c.Data["ErrorMsg"].(string)
}
// HasError returns true if error occurs in form validation.
func (c *Context) HasError() bool {
hasErr, ok := c.Data["HasError"]
if !ok {
return false
}
c.Flash.ErrorMsg = c.Data["ErrorMsg"].(string)
c.Data["Flash"] = c.Flash
return hasErr.(bool)
}
// HasValue returns true if value of given name exists.
func (c *Context) HasValue(name string) bool {
_, ok := c.Data[name]
return ok
}
// HTML responses template with given status.
func (c *Context) HTML(status int, name string) {
log.Trace("Template: %s", name)
c.Context.HTML(status, name)
}
// Success responses template with status http.StatusOK.
func (c *Context) Success(name string) {
c.HTML(http.StatusOK, name)
}
// JSONSuccess responses JSON with status http.StatusOK.
func (c *Context) JSONSuccess(data interface{}) {
c.JSON(http.StatusOK, data)
}
// RawRedirect simply calls underlying Redirect method with no escape.
func (c *Context) RawRedirect(location string, status ...int) {
c.Context.Redirect(location, status...)
}
// Redirect responses redirection wtih given location and status.
// It escapes special characters in the location string.
func (c *Context) Redirect(location string, status ...int) {
c.Context.Redirect(template.EscapePound(location), status...)
}
// SubURLRedirect responses redirection wtih given location and status.
// It prepends setting.AppSubURL to the location string.
func (c *Context) SubURLRedirect(location string, status ...int) {
c.Redirect(setting.AppSubURL+location, status...)
}
// RenderWithErr used for page has form validation but need to prompt error to users.
func (c *Context) RenderWithErr(msg, tpl string, f interface{}) {
if f != nil {
form.Assign(f, c.Data)
}
c.Flash.ErrorMsg = msg
c.Data["Flash"] = c.Flash
c.HTML(http.StatusOK, tpl)
}
// Handle handles and logs error by given status.
func (c *Context) Handle(status int, title string, err error) {
switch status {
case http.StatusNotFound:
c.Data["Title"] = "Page Not Found"
case http.StatusInternalServerError:
c.Data["Title"] = "Internal Server Error"
log.Error(3, "%s: %v", title, err)
if !setting.ProdMode || (c.IsLogged && c.User.IsAdmin) {
c.Data["ErrorMsg"] = err
}
}
c.HTML(status, fmt.Sprintf("status/%d", status))
}
// NotFound renders the 404 page.
func (c *Context) NotFound() {
c.Handle(http.StatusNotFound, "", nil)
}
// ServerError renders the 500 page.
func (c *Context) ServerError(title string, err error) {
c.Handle(http.StatusInternalServerError, title, err)
}
// NotFoundOrServerError use error check function to determine if the error
// is about not found. It responses with 404 status code for not found error,
// or error context description for logging purpose of 500 server error.
func (c *Context) NotFoundOrServerError(title string, errck func(error) bool, err error) {
if errck(err) {
c.NotFound()
return
}
c.ServerError(title, err)
}
func (c *Context) HandleText(status int, title string) {
c.PlainText(status, []byte(title))
}
func (c *Context) ServeContent(name string, r io.ReadSeeker, params ...interface{}) {
modtime := time.Now()
for _, p := range params {
switch v := p.(type) {
case time.Time:
modtime = v
}
}
c.Resp.Header().Set("Content-Description", "File Transfer")
c.Resp.Header().Set("Content-Type", "application/octet-stream")
c.Resp.Header().Set("Content-Disposition", "attachment; filename="+name)
c.Resp.Header().Set("Content-Transfer-Encoding", "binary")
c.Resp.Header().Set("Expires", "0")
c.Resp.Header().Set("Cache-Control", "must-revalidate")
c.Resp.Header().Set("Pragma", "public")
http.ServeContent(c.Resp, c.Req.Request, name, modtime, r)
}
// Contexter initializes a classic context for a request.
func Contexter() macaron.Handler {
return func(ctx *macaron.Context, l i18n.Locale, cache cache.Cache, sess session.Store, f *session.Flash, x csrf.CSRF) {
c := &Context{
Context: ctx,
Cache: cache,
csrf: x,
Flash: f,
Session: sess,
Link: setting.AppSubURL + strings.TrimSuffix(ctx.Req.URL.Path, "/"),
Repo: &Repository{
PullRequest: &PullRequest{},
},
Org: &Organization{},
}
c.Data["Link"] = template.EscapePound(c.Link)
c.Data["PageStartTime"] = time.Now()
// Quick responses appropriate go-get meta with status 200
// regardless of if user have access to the repository,
// or the repository does not exist at all.
// This is particular a workaround for "go get" command which does not respect
// .netrc file.
if c.Query("go-get") == "1" {
ownerName := c.Params(":username")
repoName := c.Params(":reponame")
branchName := "master"
owner, err := db.GetUserByName(ownerName)
if err != nil {
c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
return
}
repo, err := db.GetRepositoryByName(owner.ID, repoName)
if err == nil && len(repo.DefaultBranch) > 0 {
branchName = repo.DefaultBranch
}
prefix := setting.AppURL + path.Join(ownerName, repoName, "src", branchName)
insecureFlag := ""
if !strings.HasPrefix(setting.AppURL, "https://") {
insecureFlag = "--insecure "
}
c.PlainText(http.StatusOK, []byte(com.Expand(`<!doctype html>
<html>
<head>
<meta name="go-import" content="{GoGetImport} git {CloneLink}">
<meta name="go-source" content="{GoGetImport} _ {GoDocDirectory} {GoDocFile}">
</head>
<body>
go get {InsecureFlag}{GoGetImport}
</body>
</html>
`, map[string]string{
"GoGetImport": path.Join(setting.HostAddress, setting.AppSubURL, repo.FullName()),
"CloneLink": db.ComposeHTTPSCloneURL(ownerName, repoName),
"GoDocDirectory": prefix + "{/dir}",
"GoDocFile": prefix + "{/dir}/{file}#L{line}",
"InsecureFlag": insecureFlag,
})))
return
}
if len(setting.HTTP.AccessControlAllowOrigin) > 0 {
c.Header().Set("Access-Control-Allow-Origin", setting.HTTP.AccessControlAllowOrigin)
c.Header().Set("'Access-Control-Allow-Credentials' ", "true")
c.Header().Set("Access-Control-Max-Age", "3600")
c.Header().Set("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With")
}
// Get user from session or header when possible
c.User, c.IsBasicAuth, c.IsTokenAuth = auth.SignedInUser(c.Context, c.Session)
if c.User != nil {
c.IsLogged = true
c.Data["IsLogged"] = c.IsLogged
c.Data["LoggedUser"] = c.User
c.Data["LoggedUserID"] = c.User.ID
c.Data["LoggedUserName"] = c.User.Name
c.Data["IsAdmin"] = c.User.IsAdmin
} else {
c.Data["LoggedUserID"] = 0
c.Data["LoggedUserName"] = ""
}
// If request sends files, parse them here otherwise the Query() can't be parsed and the CsrfToken will be invalid.
if c.Req.Method == "POST" && strings.Contains(c.Req.Header.Get("Content-Type"), "multipart/form-data") {
if err := c.Req.ParseMultipartForm(setting.AttachmentMaxSize << 20); err != nil && !strings.Contains(err.Error(), "EOF") { // 32MB max size
c.ServerError("ParseMultipartForm", err)
return
}
}
c.Data["CSRFToken"] = x.GetToken()
c.Data["CSRFTokenHTML"] = template.Safe(`<input type="hidden" name="_csrf" value="` + x.GetToken() + `">`)
log.Trace("Session ID: %s", sess.ID())
log.Trace("CSRF Token: %v", c.Data["CSRFToken"])
c.Data["ShowRegistrationButton"] = setting.Service.ShowRegistrationButton
c.Data["ShowFooterBranding"] = setting.ShowFooterBranding
c.Data["ShowFooterVersion"] = setting.ShowFooterVersion
c.renderNoticeBanner()
ctx.Map(c)
}
}

View File

@@ -0,0 +1,62 @@
// Copyright 2019 The Gogs 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 context
import (
"os"
"path"
"github.com/unknwon/com"
log "gopkg.in/clog.v1"
"gogs.io/gogs/internal/markup"
"gogs.io/gogs/internal/setting"
"gogs.io/gogs/internal/tool"
)
// renderNoticeBanner checks if a notice banner file exists and loads the message to display
// on all pages.
func (c *Context) renderNoticeBanner() {
fpath := path.Join(setting.CustomPath, "notice", "banner.md")
if !com.IsExist(fpath) {
return
}
f, err := os.Open(fpath)
if err != nil {
log.Error(2, "Failed to open file %q: %v", fpath, err)
return
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
log.Error(2, "Failed to stat file %q: %v", fpath, err)
return
}
// Limit size to prevent very large messages from breaking pages
var maxSize int64 = 1024
if fi.Size() > maxSize { // Refuse to print very long messages
log.Warn("Notice banner file %q size too large [%d > %d]: refusing to render", fpath, fi.Size(), maxSize)
return
}
buf := make([]byte, maxSize)
n, err := f.Read(buf)
if err != nil {
log.Error(2, "Failed to read file %q: %v", fpath, err)
return
}
buf = buf[:n]
if !tool.IsTextFile(buf) {
log.Warn("Notice banner file %q does not appear to be a text file: aborting", fpath)
return
}
c.Data["ServerNotice"] = string(markup.RawMarkdown(buf, ""))
}

150
internal/context/org.go Normal file
View File

@@ -0,0 +1,150 @@
// Copyright 2014 The Gogs 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 context
import (
"strings"
"gopkg.in/macaron.v1"
"gogs.io/gogs/internal/db"
"gogs.io/gogs/internal/db/errors"
"gogs.io/gogs/internal/setting"
)
type Organization struct {
IsOwner bool
IsMember bool
IsTeamMember bool // Is member of team.
IsTeamAdmin bool // In owner team or team that has admin permission level.
Organization *db.User
OrgLink string
Team *db.Team
}
func HandleOrgAssignment(c *Context, args ...bool) {
var (
requireMember bool
requireOwner bool
requireTeamMember bool
requireTeamAdmin bool
)
if len(args) >= 1 {
requireMember = args[0]
}
if len(args) >= 2 {
requireOwner = args[1]
}
if len(args) >= 3 {
requireTeamMember = args[2]
}
if len(args) >= 4 {
requireTeamAdmin = args[3]
}
orgName := c.Params(":org")
var err error
c.Org.Organization, err = db.GetUserByName(orgName)
if err != nil {
c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
return
}
org := c.Org.Organization
c.Data["Org"] = org
// Force redirection when username is actually a user.
if !org.IsOrganization() {
c.Redirect("/" + org.Name)
return
}
// Admin has super access.
if c.IsLogged && c.User.IsAdmin {
c.Org.IsOwner = true
c.Org.IsMember = true
c.Org.IsTeamMember = true
c.Org.IsTeamAdmin = true
} else if c.IsLogged {
c.Org.IsOwner = org.IsOwnedBy(c.User.ID)
if c.Org.IsOwner {
c.Org.IsMember = true
c.Org.IsTeamMember = true
c.Org.IsTeamAdmin = true
} else {
if org.IsOrgMember(c.User.ID) {
c.Org.IsMember = true
}
}
} else {
// Fake data.
c.Data["SignedUser"] = &db.User{}
}
if (requireMember && !c.Org.IsMember) ||
(requireOwner && !c.Org.IsOwner) {
c.Handle(404, "OrgAssignment", err)
return
}
c.Data["IsOrganizationOwner"] = c.Org.IsOwner
c.Data["IsOrganizationMember"] = c.Org.IsMember
c.Org.OrgLink = setting.AppSubURL + "/org/" + org.Name
c.Data["OrgLink"] = c.Org.OrgLink
// Team.
if c.Org.IsMember {
if c.Org.IsOwner {
if err := org.GetTeams(); err != nil {
c.Handle(500, "GetTeams", err)
return
}
} else {
org.Teams, err = org.GetUserTeams(c.User.ID)
if err != nil {
c.Handle(500, "GetUserTeams", err)
return
}
}
}
teamName := c.Params(":team")
if len(teamName) > 0 {
teamExists := false
for _, team := range org.Teams {
if team.LowerName == strings.ToLower(teamName) {
teamExists = true
c.Org.Team = team
c.Org.IsTeamMember = true
c.Data["Team"] = c.Org.Team
break
}
}
if !teamExists {
c.Handle(404, "OrgAssignment", err)
return
}
c.Data["IsTeamMember"] = c.Org.IsTeamMember
if requireTeamMember && !c.Org.IsTeamMember {
c.Handle(404, "OrgAssignment", err)
return
}
c.Org.IsTeamAdmin = c.Org.Team.IsOwnerTeam() || c.Org.Team.Authorize >= db.ACCESS_MODE_ADMIN
c.Data["IsTeamAdmin"] = c.Org.IsTeamAdmin
if requireTeamAdmin && !c.Org.IsTeamAdmin {
c.Handle(404, "OrgAssignment", err)
return
}
}
}
func OrgAssignment(args ...bool) macaron.Handler {
return func(c *Context) {
HandleOrgAssignment(c, args...)
}
}

437
internal/context/repo.go Normal file
View File

@@ -0,0 +1,437 @@
// Copyright 2014 The Gogs 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 context
import (
"fmt"
"io/ioutil"
"strings"
"gopkg.in/editorconfig/editorconfig-core-go.v1"
"gopkg.in/macaron.v1"
"github.com/gogs/git-module"
"gogs.io/gogs/internal/db"
"gogs.io/gogs/internal/db/errors"
"gogs.io/gogs/internal/setting"
)
type PullRequest struct {
BaseRepo *db.Repository
Allowed bool
SameRepo bool
HeadInfo string // [<user>:]<branch>
}
type Repository struct {
AccessMode db.AccessMode
IsWatching bool
IsViewBranch bool
IsViewTag bool
IsViewCommit bool
Repository *db.Repository
Owner *db.User
Commit *git.Commit
Tag *git.Tag
GitRepo *git.Repository
BranchName string
TagName string
TreePath string
CommitID string
RepoLink string
CloneLink db.CloneLink
CommitsCount int64
Mirror *db.Mirror
PullRequest *PullRequest
}
// IsOwner returns true if current user is the owner of repository.
func (r *Repository) IsOwner() bool {
return r.AccessMode >= db.ACCESS_MODE_OWNER
}
// IsAdmin returns true if current user has admin or higher access of repository.
func (r *Repository) IsAdmin() bool {
return r.AccessMode >= db.ACCESS_MODE_ADMIN
}
// IsWriter returns true if current user has write or higher access of repository.
func (r *Repository) IsWriter() bool {
return r.AccessMode >= db.ACCESS_MODE_WRITE
}
// HasAccess returns true if the current user has at least read access for this repository
func (r *Repository) HasAccess() bool {
return r.AccessMode >= db.ACCESS_MODE_READ
}
// CanEnableEditor returns true if repository is editable and user has proper access level.
func (r *Repository) CanEnableEditor() bool {
return r.Repository.CanEnableEditor() && r.IsViewBranch && r.IsWriter() && !r.Repository.IsBranchRequirePullRequest(r.BranchName)
}
// GetEditorconfig returns the .editorconfig definition if found in the
// HEAD of the default repo branch.
func (r *Repository) GetEditorconfig() (*editorconfig.Editorconfig, error) {
commit, err := r.GitRepo.GetBranchCommit(r.Repository.DefaultBranch)
if err != nil {
return nil, err
}
treeEntry, err := commit.GetTreeEntryByPath(".editorconfig")
if err != nil {
return nil, err
}
reader, err := treeEntry.Blob().Data()
if err != nil {
return nil, err
}
data, err := ioutil.ReadAll(reader)
if err != nil {
return nil, err
}
return editorconfig.ParseBytes(data)
}
// PullRequestURL returns URL for composing a pull request.
// This function does not check if the repository can actually compose a pull request.
func (r *Repository) PullRequestURL(baseBranch, headBranch string) string {
repoLink := r.RepoLink
if r.PullRequest.BaseRepo != nil {
repoLink = r.PullRequest.BaseRepo.Link()
}
return fmt.Sprintf("%s/compare/%s...%s:%s", repoLink, baseBranch, r.Owner.Name, headBranch)
}
// [0]: issues, [1]: wiki
func RepoAssignment(pages ...bool) macaron.Handler {
return func(c *Context) {
var (
owner *db.User
err error
isIssuesPage bool
isWikiPage bool
)
if len(pages) > 0 {
isIssuesPage = pages[0]
}
if len(pages) > 1 {
isWikiPage = pages[1]
}
ownerName := c.Params(":username")
repoName := strings.TrimSuffix(c.Params(":reponame"), ".git")
refName := c.Params(":branchname")
if len(refName) == 0 {
refName = c.Params(":path")
}
// Check if the user is the same as the repository owner
if c.IsLogged && c.User.LowerName == strings.ToLower(ownerName) {
owner = c.User
} else {
owner, err = db.GetUserByName(ownerName)
if err != nil {
c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
return
}
}
c.Repo.Owner = owner
c.Data["Username"] = c.Repo.Owner.Name
repo, err := db.GetRepositoryByName(owner.ID, repoName)
if err != nil {
c.NotFoundOrServerError("GetRepositoryByName", errors.IsRepoNotExist, err)
return
}
c.Repo.Repository = repo
c.Data["RepoName"] = c.Repo.Repository.Name
c.Data["IsBareRepo"] = c.Repo.Repository.IsBare
c.Repo.RepoLink = repo.Link()
c.Data["RepoLink"] = c.Repo.RepoLink
c.Data["RepoRelPath"] = c.Repo.Owner.Name + "/" + c.Repo.Repository.Name
// Admin has super access.
if c.IsLogged && c.User.IsAdmin {
c.Repo.AccessMode = db.ACCESS_MODE_OWNER
} else {
mode, err := db.UserAccessMode(c.UserID(), repo)
if err != nil {
c.ServerError("UserAccessMode", err)
return
}
c.Repo.AccessMode = mode
}
// Check access
if c.Repo.AccessMode == db.ACCESS_MODE_NONE {
// Redirect to any accessible page if not yet on it
if repo.IsPartialPublic() &&
(!(isIssuesPage || isWikiPage) ||
(isIssuesPage && !repo.CanGuestViewIssues()) ||
(isWikiPage && !repo.CanGuestViewWiki())) {
switch {
case repo.CanGuestViewIssues():
c.Redirect(repo.Link() + "/issues")
case repo.CanGuestViewWiki():
c.Redirect(repo.Link() + "/wiki")
default:
c.NotFound()
}
return
}
// Response 404 if user is on completely private repository or possible accessible page but owner doesn't enabled
if !repo.IsPartialPublic() ||
(isIssuesPage && !repo.CanGuestViewIssues()) ||
(isWikiPage && !repo.CanGuestViewWiki()) {
c.NotFound()
return
}
c.Repo.Repository.EnableIssues = repo.CanGuestViewIssues()
c.Repo.Repository.EnableWiki = repo.CanGuestViewWiki()
}
if repo.IsMirror {
c.Repo.Mirror, err = db.GetMirrorByRepoID(repo.ID)
if err != nil {
c.ServerError("GetMirror", err)
return
}
c.Data["MirrorEnablePrune"] = c.Repo.Mirror.EnablePrune
c.Data["MirrorInterval"] = c.Repo.Mirror.Interval
c.Data["Mirror"] = c.Repo.Mirror
}
gitRepo, err := git.OpenRepository(db.RepoPath(ownerName, repoName))
if err != nil {
c.ServerError(fmt.Sprintf("RepoAssignment Invalid repo '%s'", c.Repo.Repository.RepoPath()), err)
return
}
c.Repo.GitRepo = gitRepo
tags, err := c.Repo.GitRepo.GetTags()
if err != nil {
c.ServerError(fmt.Sprintf("GetTags '%s'", c.Repo.Repository.RepoPath()), err)
return
}
c.Data["Tags"] = tags
c.Repo.Repository.NumTags = len(tags)
c.Data["Title"] = owner.Name + "/" + repo.Name
c.Data["Repository"] = repo
c.Data["Owner"] = c.Repo.Repository.Owner
c.Data["IsRepositoryOwner"] = c.Repo.IsOwner()
c.Data["IsRepositoryAdmin"] = c.Repo.IsAdmin()
c.Data["IsRepositoryWriter"] = c.Repo.IsWriter()
c.Data["DisableSSH"] = setting.SSH.Disabled
c.Data["DisableHTTP"] = setting.Repository.DisableHTTPGit
c.Data["CloneLink"] = repo.CloneLink()
c.Data["WikiCloneLink"] = repo.WikiCloneLink()
if c.IsLogged {
c.Data["IsWatchingRepo"] = db.IsWatching(c.User.ID, repo.ID)
c.Data["IsStaringRepo"] = db.IsStaring(c.User.ID, repo.ID)
}
// repo is bare and display enable
if c.Repo.Repository.IsBare {
return
}
c.Data["TagName"] = c.Repo.TagName
brs, err := c.Repo.GitRepo.GetBranches()
if err != nil {
c.ServerError("GetBranches", err)
return
}
c.Data["Branches"] = brs
c.Data["BrancheCount"] = len(brs)
// If not branch selected, try default one.
// If default branch doesn't exists, fall back to some other branch.
if len(c.Repo.BranchName) == 0 {
if len(c.Repo.Repository.DefaultBranch) > 0 && gitRepo.IsBranchExist(c.Repo.Repository.DefaultBranch) {
c.Repo.BranchName = c.Repo.Repository.DefaultBranch
} else if len(brs) > 0 {
c.Repo.BranchName = brs[0]
}
}
c.Data["BranchName"] = c.Repo.BranchName
c.Data["CommitID"] = c.Repo.CommitID
c.Data["IsGuest"] = !c.Repo.HasAccess()
}
}
// RepoRef handles repository reference name including those contain `/`.
func RepoRef() macaron.Handler {
return func(c *Context) {
// Empty repository does not have reference information.
if c.Repo.Repository.IsBare {
return
}
var (
refName string
err error
)
// For API calls.
if c.Repo.GitRepo == nil {
repoPath := db.RepoPath(c.Repo.Owner.Name, c.Repo.Repository.Name)
c.Repo.GitRepo, err = git.OpenRepository(repoPath)
if err != nil {
c.Handle(500, "RepoRef Invalid repo "+repoPath, err)
return
}
}
// Get default branch.
if len(c.Params("*")) == 0 {
refName = c.Repo.Repository.DefaultBranch
if !c.Repo.GitRepo.IsBranchExist(refName) {
brs, err := c.Repo.GitRepo.GetBranches()
if err != nil {
c.Handle(500, "GetBranches", err)
return
}
refName = brs[0]
}
c.Repo.Commit, err = c.Repo.GitRepo.GetBranchCommit(refName)
if err != nil {
c.Handle(500, "GetBranchCommit", err)
return
}
c.Repo.CommitID = c.Repo.Commit.ID.String()
c.Repo.IsViewBranch = true
} else {
hasMatched := false
parts := strings.Split(c.Params("*"), "/")
for i, part := range parts {
refName = strings.TrimPrefix(refName+"/"+part, "/")
if c.Repo.GitRepo.IsBranchExist(refName) ||
c.Repo.GitRepo.IsTagExist(refName) {
if i < len(parts)-1 {
c.Repo.TreePath = strings.Join(parts[i+1:], "/")
}
hasMatched = true
break
}
}
if !hasMatched && len(parts[0]) == 40 {
refName = parts[0]
c.Repo.TreePath = strings.Join(parts[1:], "/")
}
if c.Repo.GitRepo.IsBranchExist(refName) {
c.Repo.IsViewBranch = true
c.Repo.Commit, err = c.Repo.GitRepo.GetBranchCommit(refName)
if err != nil {
c.Handle(500, "GetBranchCommit", err)
return
}
c.Repo.CommitID = c.Repo.Commit.ID.String()
} else if c.Repo.GitRepo.IsTagExist(refName) {
c.Repo.IsViewTag = true
c.Repo.Commit, err = c.Repo.GitRepo.GetTagCommit(refName)
if err != nil {
c.Handle(500, "GetTagCommit", err)
return
}
c.Repo.CommitID = c.Repo.Commit.ID.String()
} else if len(refName) == 40 {
c.Repo.IsViewCommit = true
c.Repo.CommitID = refName
c.Repo.Commit, err = c.Repo.GitRepo.GetCommit(refName)
if err != nil {
c.NotFound()
return
}
} else {
c.Handle(404, "RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
return
}
}
c.Repo.BranchName = refName
c.Data["BranchName"] = c.Repo.BranchName
c.Data["CommitID"] = c.Repo.CommitID
c.Data["TreePath"] = c.Repo.TreePath
c.Data["IsViewBranch"] = c.Repo.IsViewBranch
c.Data["IsViewTag"] = c.Repo.IsViewTag
c.Data["IsViewCommit"] = c.Repo.IsViewCommit
// People who have push access or have fored repository can propose a new pull request.
if c.Repo.IsWriter() || (c.IsLogged && c.User.HasForkedRepo(c.Repo.Repository.ID)) {
// Pull request is allowed if this is a fork repository
// and base repository accepts pull requests.
if c.Repo.Repository.BaseRepo != nil {
if c.Repo.Repository.BaseRepo.AllowsPulls() {
c.Repo.PullRequest.Allowed = true
// In-repository pull requests has higher priority than cross-repository if user is viewing
// base repository and 1) has write access to it 2) has forked it.
if c.Repo.IsWriter() {
c.Data["BaseRepo"] = c.Repo.Repository.BaseRepo
c.Repo.PullRequest.BaseRepo = c.Repo.Repository.BaseRepo
c.Repo.PullRequest.HeadInfo = c.Repo.Owner.Name + ":" + c.Repo.BranchName
} else {
c.Data["BaseRepo"] = c.Repo.Repository
c.Repo.PullRequest.BaseRepo = c.Repo.Repository
c.Repo.PullRequest.HeadInfo = c.User.Name + ":" + c.Repo.BranchName
}
}
} else {
// Or, this is repository accepts pull requests between branches.
if c.Repo.Repository.AllowsPulls() {
c.Data["BaseRepo"] = c.Repo.Repository
c.Repo.PullRequest.BaseRepo = c.Repo.Repository
c.Repo.PullRequest.Allowed = true
c.Repo.PullRequest.SameRepo = true
c.Repo.PullRequest.HeadInfo = c.Repo.BranchName
}
}
}
c.Data["PullRequestCtx"] = c.Repo.PullRequest
}
}
func RequireRepoAdmin() macaron.Handler {
return func(c *Context) {
if !c.IsLogged || (!c.Repo.IsAdmin() && !c.User.IsAdmin) {
c.NotFound()
return
}
}
}
func RequireRepoWriter() macaron.Handler {
return func(c *Context) {
if !c.IsLogged || (!c.Repo.IsWriter() && !c.User.IsAdmin) {
c.NotFound()
return
}
}
}
// GitHookService checks if repository Git hooks service has been enabled.
func GitHookService() macaron.Handler {
return func(c *Context) {
if !c.User.CanEditGitHook() {
c.NotFound()
return
}
}
}

30
internal/context/user.go Normal file
View File

@@ -0,0 +1,30 @@
// Copyright 2018 The Gogs 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 context
import (
"gopkg.in/macaron.v1"
"gogs.io/gogs/internal/db"
"gogs.io/gogs/internal/db/errors"
)
// ParamsUser is the wrapper type of the target user defined by URL parameter, namely ':username'.
type ParamsUser struct {
*db.User
}
// InjectParamsUser returns a handler that retrieves target user based on URL parameter ':username',
// and injects it as *ParamsUser.
func InjectParamsUser() macaron.Handler {
return func(c *Context) {
user, err := db.GetUserByName(c.Params(":username"))
if err != nil {
c.NotFoundOrServerError("GetUserByName", errors.IsUserNotExist, err)
return
}
c.Map(&ParamsUser{user})
}
}