merge with branch master

This commit is contained in:
skyblue 2014-03-23 22:40:35 +08:00
commit 4bac361605
58 changed files with 1870 additions and 546 deletions

View file

@ -61,6 +61,7 @@ func (f *RegisterForm) Validate(errors *binding.Errors, req *http.Request, conte
type LogInForm struct {
UserName string `form:"username" binding:"Required;AlphaDash;MaxSize(30)"`
Password string `form:"passwd" binding:"Required;MinSize(6);MaxSize(30)"`
Remember string `form:"remember"`
}
func (f *LogInForm) Name(field string) string {

54
modules/auth/issue.go Normal file
View file

@ -0,0 +1,54 @@
// 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 auth
import (
"net/http"
"reflect"
"github.com/codegangsta/martini"
"github.com/gogits/binding"
"github.com/gogits/gogs/modules/base"
"github.com/gogits/gogs/modules/log"
)
type CreateIssueForm struct {
IssueName string `form:"name" binding:"Required;MaxSize(50)"`
RepoId int64 `form:"repoid" binding:"Required"`
MilestoneId int64 `form:"milestoneid" binding:"Required"`
AssigneeId int64 `form:"assigneeid"`
Labels string `form:"labels"`
Content string `form:"content"`
}
func (f *CreateIssueForm) Name(field string) string {
names := map[string]string{
"IssueName": "Issue name",
"RepoId": "Repository ID",
"MilestoneId": "Milestone ID",
}
return names[field]
}
func (f *CreateIssueForm) Validate(errors *binding.Errors, req *http.Request, context martini.Context) {
if req.Method == "GET" || errors.Count() == 0 {
return
}
data := context.Get(reflect.TypeOf(base.TmplData{})).Interface().(base.TmplData)
data["HasError"] = true
AssignForm(f, data)
if len(errors.Overall) > 0 {
for _, err := range errors.Overall {
log.Error("CreateIssueForm.Validate: %v", err)
}
return
}
validate(errors, data, f)
}

View file

@ -9,7 +9,8 @@ import (
"reflect"
"github.com/codegangsta/martini"
"github.com/martini-contrib/sessions"
"github.com/gogits/session"
"github.com/gogits/binding"
@ -19,7 +20,7 @@ import (
)
// SignedInId returns the id of signed in user.
func SignedInId(session sessions.Session) int64 {
func SignedInId(session session.SessionStore) int64 {
userId := session.Get("userId")
if userId == nil {
return 0
@ -34,7 +35,7 @@ func SignedInId(session sessions.Session) int64 {
}
// SignedInName returns the name of signed in user.
func SignedInName(session sessions.Session) string {
func SignedInName(session session.SessionStore) string {
userName := session.Get("userName")
if userName == nil {
return ""
@ -46,7 +47,7 @@ func SignedInName(session sessions.Session) string {
}
// SignedInUser returns the user object of signed user.
func SignedInUser(session sessions.Session) *models.User {
func SignedInUser(session session.SessionStore) *models.User {
id := SignedInId(session)
if id <= 0 {
return nil
@ -61,7 +62,7 @@ func SignedInUser(session sessions.Session) *models.User {
}
// IsSignedIn check if any user has signed in.
func IsSignedIn(session sessions.Session) bool {
func IsSignedIn(session session.SessionStore) bool {
return SignedInId(session) > 0
}

View file

@ -16,6 +16,7 @@ import (
"github.com/Unknwon/goconfig"
"github.com/gogits/cache"
"github.com/gogits/session"
"github.com/gogits/gogs/modules/log"
)
@ -37,21 +38,35 @@ var (
RunUser string
RepoRootPath string
EnableHttpsClone bool
LogInRememberDays int
CookieUserName string
CookieRememberName string
Cfg *goconfig.ConfigFile
MailService *Mailer
LogMode string
LogConfig string
Cache cache.Cache
CacheAdapter string
CacheConfig string
LogMode string
LogConfig string
SessionProvider string
SessionConfig *session.Config
SessionManager *session.Manager
PictureService string
PictureRootPath string
)
var Service struct {
RegisterEmailConfirm bool
DisenableRegisteration bool
RequireSignInView bool
EnableCacheAvatar bool
ActiveCodeLives int
ResetPwdCodeLives int
}
@ -82,6 +97,7 @@ func newService() {
Service.ResetPwdCodeLives = Cfg.MustInt("service", "RESET_PASSWD_CODE_LIVE_MINUTES", 180)
Service.DisenableRegisteration = Cfg.MustBool("service", "DISENABLE_REGISTERATION", false)
Service.RequireSignInView = Cfg.MustBool("service", "REQUIRE_SIGNIN_VIEW", false)
Service.EnableCacheAvatar = Cfg.MustBool("service", "ENABLE_CACHE_AVATAR", false)
}
func newLogService() {
@ -129,6 +145,10 @@ func newLogService() {
Cfg.MustValue(modeSec, "HOST", "127.0.0.1:25"),
Cfg.MustValue(modeSec, "RECEIVERS", "[]"),
Cfg.MustValue(modeSec, "SUBJECT", "Diagnostic message from serve"))
case "database":
LogConfig = fmt.Sprintf(`{"level":%s,"driver":%s,"conn":%s}`, level,
Cfg.MustValue(modeSec, "Driver"),
Cfg.MustValue(modeSec, "CONN"))
}
log.NewLogger(Cfg.MustInt64("log", "BUFFER_LEN", 10000), LogMode, LogConfig)
@ -159,6 +179,34 @@ func newCacheService() {
log.Info("Cache Service Enabled")
}
func newSessionService() {
SessionProvider = Cfg.MustValue("session", "PROVIDER", "memory")
SessionConfig = new(session.Config)
SessionConfig.ProviderConfig = Cfg.MustValue("session", "PROVIDER_CONFIG")
SessionConfig.CookieName = Cfg.MustValue("session", "COOKIE_NAME", "i_like_gogits")
SessionConfig.CookieSecure = Cfg.MustBool("session", "COOKIE_SECURE")
SessionConfig.EnableSetCookie = Cfg.MustBool("session", "ENABLE_SET_COOKIE", true)
SessionConfig.GcIntervalTime = Cfg.MustInt64("session", "GC_INTERVAL_TIME", 86400)
SessionConfig.SessionLifeTime = Cfg.MustInt64("session", "SESSION_LIFE_TIME", 86400)
SessionConfig.SessionIDHashFunc = Cfg.MustValue("session", "SESSION_ID_HASHFUNC", "sha1")
SessionConfig.SessionIDHashKey = Cfg.MustValue("session", "SESSION_ID_HASHKEY")
if SessionProvider == "file" {
os.MkdirAll(path.Dir(SessionConfig.ProviderConfig), os.ModePerm)
}
var err error
SessionManager, err = session.NewManager(SessionProvider, *SessionConfig)
if err != nil {
fmt.Printf("Init session system failed, provider: %s, %v\n",
SessionProvider, err)
os.Exit(2)
}
log.Info("Session Service Enabled")
}
func newMailService() {
// Check mailer setting.
if Cfg.MustBool("mailer", "ENABLED") {
@ -214,6 +262,15 @@ func NewConfigContext() {
SecretKey = Cfg.MustValue("security", "SECRET_KEY")
RunUser = Cfg.MustValue("", "RUN_USER")
EnableHttpsClone = Cfg.MustBool("security", "ENABLE_HTTPS_CLONE", false)
LogInRememberDays = Cfg.MustInt("security", "LOGIN_REMEMBER_DAYS")
CookieUserName = Cfg.MustValue("security", "COOKIE_USERNAME")
CookieRememberName = Cfg.MustValue("security", "COOKIE_REMEMBER_NAME")
PictureService = Cfg.MustValue("picture", "SERVICE")
PictureRootPath = Cfg.MustValue("picture", "PATH")
// Determine and create root git reposiroty path.
RepoRootPath = Cfg.MustValue("repository", "ROOT")
if err = os.MkdirAll(RepoRootPath, os.ModePerm); err != nil {
@ -226,6 +283,7 @@ func NewServices() {
newService()
newLogService()
newCacheService()
newSessionService()
newMailService()
newRegisterMailService()
}

View file

@ -72,7 +72,7 @@ func (options *CustomRender) Link(out *bytes.Buffer, link []byte, title []byte,
func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
htmlFlags := 0
htmlFlags |= gfm.HTML_USE_XHTML
// htmlFlags |= gfm.HTML_USE_XHTML
// htmlFlags |= gfm.HTML_USE_SMARTYPANTS
// htmlFlags |= gfm.HTML_SMARTYPANTS_FRACTIONS
// htmlFlags |= gfm.HTML_SMARTYPANTS_LATEX_DASHES
@ -81,7 +81,7 @@ func RenderMarkdown(rawBytes []byte, urlPrefix string) []byte {
htmlFlags |= gfm.HTML_SKIP_SCRIPT
htmlFlags |= gfm.HTML_GITHUB_BLOCKCODE
htmlFlags |= gfm.HTML_OMIT_CONTENTS
htmlFlags |= gfm.HTML_COMPLETE_PAGE
// htmlFlags |= gfm.HTML_COMPLETE_PAGE
renderer := &CustomRender{
Renderer: gfm.HtmlRenderer(htmlFlags, "", ""),
urlPrefix: urlPrefix,

View file

@ -25,13 +25,17 @@ func EncodeMd5(str string) string {
return hex.EncodeToString(m.Sum(nil))
}
// Random generate string
func GetRandomString(n int) string {
// GetRandomString generate random string by specify chars.
func GetRandomString(n int, alphabets ...byte) string {
const alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
var bytes = make([]byte, n)
rand.Read(bytes)
for i, b := range bytes {
bytes[i] = alphanum[b%byte(len(alphanum))]
if len(alphabets) == 0 {
bytes[i] = alphanum[b%byte(len(alphanum))]
} else {
bytes[i] = alphabets[b%byte(len(alphabets))]
}
}
return string(bytes)
}
@ -111,6 +115,85 @@ const (
Year = 12 * Month
)
func computeTimeDiff(diff int64) (int64, string) {
diffStr := ""
switch {
case diff <= 0:
diff = 0
diffStr = "now"
case diff < 2:
diff = 0
diffStr = "1 second"
case diff < 1*Minute:
diffStr = fmt.Sprintf("%d seconds", diff)
diff = 0
case diff < 2*Minute:
diff -= 1 * Minute
diffStr = "1 minute"
case diff < 1*Hour:
diffStr = fmt.Sprintf("%d minutes", diff/Minute)
diff -= diff / Minute * Minute
case diff < 2*Hour:
diff -= 1 * Hour
diffStr = "1 hour"
case diff < 1*Day:
diffStr = fmt.Sprintf("%d hours", diff/Hour)
diff -= diff / Hour * Hour
case diff < 2*Day:
diff -= 1 * Day
diffStr = "1 day"
case diff < 1*Week:
diffStr = fmt.Sprintf("%d days", diff/Day)
diff -= diff / Day * Day
case diff < 2*Week:
diff -= 1 * Week
diffStr = "1 week"
case diff < 1*Month:
diffStr = fmt.Sprintf("%d weeks", diff/Week)
diff -= diff / Week * Week
case diff < 2*Month:
diff -= 1 * Month
diffStr = "1 month"
case diff < 1*Year:
diffStr = fmt.Sprintf("%d months", diff/Month)
diff -= diff / Month * Month
case diff < 2*Year:
diff -= 1 * Year
diffStr = "1 year"
default:
diffStr = fmt.Sprintf("%d years", diff/Year)
diff = 0
}
return diff, diffStr
}
// TimeSincePro calculates the time interval and generate full user-friendly string.
func TimeSincePro(then time.Time) string {
now := time.Now()
diff := now.Unix() - then.Unix()
if then.After(now) {
return "future"
}
var timeStr, diffStr string
for {
if diff == 0 {
break
}
diff, diffStr = computeTimeDiff(diff)
timeStr += ", " + diffStr
}
return strings.TrimPrefix(timeStr, ", ")
}
// TimeSince calculates the time interval and generate user-friendly string.
func TimeSince(then time.Time) string {
now := time.Now()
@ -123,7 +206,6 @@ func TimeSince(then time.Time) string {
}
switch {
case diff <= 0:
return "now"
case diff <= 2:
@ -156,8 +238,10 @@ func TimeSince(then time.Time) string {
case diff < 1*Year:
return fmt.Sprintf("%d months %s", diff/Month, lbl)
case diff < 18*Month:
case diff < 2*Year:
return fmt.Sprintf("1 year %s", lbl)
default:
return fmt.Sprintf("%d years %s", diff/Year, lbl)
}
return then.String()
}
@ -387,6 +471,7 @@ type Actioner interface {
GetOpType() int
GetActUserName() string
GetRepoName() string
GetBranch() string
GetContent() string
}
@ -409,25 +494,34 @@ const (
TPL_COMMIT_REPO_LI = `<div><img id="gogs-user-avatar-commit" src="%s?s=16" alt="user-avatar" title="username"/> <a href="/%s/%s/commit/%s">%s</a> %s</div>`
)
type PushCommits struct {
Len int
Commits [][]string
}
// ActionDesc accepts int that represents action operation type
// and returns the description.
func ActionDesc(act Actioner, avatarLink string) string {
actUserName := act.GetActUserName()
repoName := act.GetRepoName()
branch := act.GetBranch()
content := act.GetContent()
switch act.GetOpType() {
case 1: // Create repository.
return fmt.Sprintf(TPL_CREATE_REPO, actUserName, actUserName, actUserName, repoName, repoName)
case 5: // Commit repository.
var commits [][]string
if err := json.Unmarshal([]byte(content), &commits); err != nil {
var push *PushCommits
if err := json.Unmarshal([]byte(content), &push); err != nil {
return err.Error()
}
buf := bytes.NewBuffer([]byte("\n"))
for _, commit := range commits {
for _, commit := range push.Commits {
buf.WriteString(fmt.Sprintf(TPL_COMMIT_REPO_LI, avatarLink, actUserName, repoName, commit[0], commit[0][:7], commit[1]) + "\n")
}
return fmt.Sprintf(TPL_COMMIT_REPO, actUserName, actUserName, actUserName, repoName, "master", "master", actUserName, repoName, actUserName, repoName,
if push.Len > 3 {
buf.WriteString(fmt.Sprintf(`<div><a href="/%s/%s/commits/%s">%d other commits >></a></div>`, actUserName, repoName, branch, push.Len))
}
return fmt.Sprintf(TPL_COMMIT_REPO, actUserName, actUserName, actUserName, repoName, branch, branch, actUserName, repoName, actUserName, repoName,
buf.String())
default:
return "invalid type"

View file

@ -5,44 +5,54 @@
package middleware
import (
"net/url"
"github.com/codegangsta/martini"
"github.com/gogits/gogs/modules/base"
)
// SignInRequire requires user to sign in.
func SignInRequire(redirect bool) martini.Handler {
return func(ctx *Context) {
if !ctx.IsSigned {
if redirect {
ctx.Redirect("/user/login")
}
return
} else if !ctx.User.IsActive && base.Service.RegisterEmailConfirm {
ctx.Data["Title"] = "Activate Your Account"
ctx.HTML(200, "user/active")
return
}
}
type ToggleOptions struct {
SignInRequire bool
SignOutRequire bool
AdminRequire bool
DisableCsrf bool
}
// SignOutRequire requires user to sign out.
func SignOutRequire() martini.Handler {
func Toggle(options *ToggleOptions) martini.Handler {
return func(ctx *Context) {
if ctx.IsSigned {
if options.SignOutRequire && ctx.IsSigned {
ctx.Redirect("/")
return
}
}
}
// AdminRequire requires user signed in as administor.
func AdminRequire() martini.Handler {
return func(ctx *Context) {
if !ctx.User.IsAdmin {
ctx.Error(403)
return
if !options.DisableCsrf {
if ctx.Req.Method == "POST" {
if !ctx.CsrfTokenValid() {
ctx.Error(403, "CSRF token does not match")
return
}
}
}
if options.SignInRequire {
if !ctx.IsSigned {
ctx.SetCookie("redirect_to", "/"+url.QueryEscape(ctx.Req.RequestURI))
ctx.Redirect("/user/login")
return
} else if !ctx.User.IsActive && base.Service.RegisterEmailConfirm {
ctx.Data["Title"] = "Activate Your Account"
ctx.HTML(200, "user/active")
return
}
}
if options.AdminRequire {
if !ctx.User.IsAdmin {
ctx.Error(403)
return
}
ctx.Data["PageIsAdmin"] = true
}
ctx.Data["PageIsAdmin"] = true
}
}

View file

@ -5,14 +5,20 @@
package middleware
import (
"crypto/hmac"
"crypto/sha1"
"encoding/base64"
"fmt"
"html/template"
"net/http"
"strconv"
"strings"
"time"
"github.com/codegangsta/martini"
"github.com/martini-contrib/sessions"
"github.com/gogits/cache"
"github.com/gogits/session"
"github.com/gogits/gogs/models"
"github.com/gogits/gogs/modules/auth"
@ -27,11 +33,13 @@ type Context struct {
p martini.Params
Req *http.Request
Res http.ResponseWriter
Session sessions.Session
Session session.SessionStore
Cache cache.Cache
User *models.User
IsSigned bool
csrfToken string
Repo struct {
IsValid bool
IsOwner bool
@ -90,23 +98,157 @@ func (ctx *Context) Handle(status int, title string, err error) {
ctx.HTML(status, fmt.Sprintf("status/%d", status))
}
func (ctx *Context) GetCookie(name string) string {
cookie, err := ctx.Req.Cookie(name)
if err != nil {
return ""
}
return cookie.Value
}
func (ctx *Context) SetCookie(name string, value string, others ...interface{}) {
cookie := http.Cookie{}
cookie.Name = name
cookie.Value = value
if len(others) > 0 {
switch v := others[0].(type) {
case int:
cookie.MaxAge = v
case int64:
cookie.MaxAge = int(v)
case int32:
cookie.MaxAge = int(v)
}
}
// default "/"
if len(others) > 1 {
if v, ok := others[1].(string); ok && len(v) > 0 {
cookie.Path = v
}
} else {
cookie.Path = "/"
}
// default empty
if len(others) > 2 {
if v, ok := others[2].(string); ok && len(v) > 0 {
cookie.Domain = v
}
}
// default empty
if len(others) > 3 {
switch v := others[3].(type) {
case bool:
cookie.Secure = v
default:
if others[3] != nil {
cookie.Secure = true
}
}
}
// default false. for session cookie default true
if len(others) > 4 {
if v, ok := others[4].(bool); ok && v {
cookie.HttpOnly = true
}
}
ctx.Res.Header().Add("Set-Cookie", cookie.String())
}
// Get secure cookie from request by a given key.
func (ctx *Context) GetSecureCookie(Secret, key string) (string, bool) {
val := ctx.GetCookie(key)
if val == "" {
return "", false
}
parts := strings.SplitN(val, "|", 3)
if len(parts) != 3 {
return "", false
}
vs := parts[0]
timestamp := parts[1]
sig := parts[2]
h := hmac.New(sha1.New, []byte(Secret))
fmt.Fprintf(h, "%s%s", vs, timestamp)
if fmt.Sprintf("%02x", h.Sum(nil)) != sig {
return "", false
}
res, _ := base64.URLEncoding.DecodeString(vs)
return string(res), true
}
// Set Secure cookie for response.
func (ctx *Context) SetSecureCookie(Secret, name, value string, others ...interface{}) {
vs := base64.URLEncoding.EncodeToString([]byte(value))
timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
h := hmac.New(sha1.New, []byte(Secret))
fmt.Fprintf(h, "%s%s", vs, timestamp)
sig := fmt.Sprintf("%02x", h.Sum(nil))
cookie := strings.Join([]string{vs, timestamp, sig}, "|")
ctx.SetCookie(name, cookie, others...)
}
func (ctx *Context) CsrfToken() string {
if len(ctx.csrfToken) > 0 {
return ctx.csrfToken
}
token := ctx.GetCookie("_csrf")
if len(token) == 0 {
token = base.GetRandomString(30)
ctx.SetCookie("_csrf", token)
}
ctx.csrfToken = token
return token
}
func (ctx *Context) CsrfTokenValid() bool {
token := ctx.Query("_csrf")
if token == "" {
token = ctx.Req.Header.Get("X-Csrf-Token")
}
if token == "" {
return false
} else if ctx.csrfToken != token {
return false
}
return true
}
// InitContext initializes a classic context for a request.
func InitContext() martini.Handler {
return func(res http.ResponseWriter, r *http.Request, c martini.Context,
session sessions.Session, rd *Render) {
return func(res http.ResponseWriter, r *http.Request, c martini.Context, rd *Render) {
ctx := &Context{
c: c,
// p: p,
Req: r,
Res: res,
Session: session,
Cache: base.Cache,
Render: rd,
Req: r,
Res: res,
Cache: base.Cache,
Render: rd,
}
ctx.Data["PageStartTime"] = time.Now()
// start session
ctx.Session = base.SessionManager.SessionStart(res, r)
rw := res.(martini.ResponseWriter)
rw.Before(func(martini.ResponseWriter) {
ctx.Session.SessionRelease(res)
})
// Get user from session if logined.
user := auth.SignedInUser(session)
user := auth.SignedInUser(ctx.Session)
ctx.User = user
ctx.IsSigned = user != nil
@ -119,7 +261,9 @@ func InitContext() martini.Handler {
ctx.Data["IsAdmin"] = ctx.User.IsAdmin
}
ctx.Data["PageStartTime"] = time.Now()
// get or create csrf token
ctx.Data["CsrfToken"] = ctx.CsrfToken()
ctx.Data["CsrfTokenHtml"] = template.HTML(`<input type="hidden" name="_csrf" value="` + ctx.csrfToken + `">`)
c.Map(ctx)

View file

@ -242,8 +242,11 @@ func (r *Render) HTMLString(name string, binding interface{}, htmlOpt ...HTMLOpt
}
}
func (r *Render) Error(status int) {
func (r *Render) Error(status int, message ...string) {
r.WriteHeader(status)
if len(message) > 0 {
r.Write([]byte(message[0]))
}
}
func (r *Render) Redirect(location string, status ...int) {

View file

@ -54,7 +54,7 @@ func RepoAssignment(redirect bool) martini.Handler {
ctx.Repo.Owner = user
// get repository
repo, err := models.GetRepositoryByName(user, params["reponame"])
repo, err := models.GetRepositoryByName(user.Id, params["reponame"])
if err != nil {
if redirect {
ctx.Redirect("/")
@ -69,8 +69,12 @@ func RepoAssignment(redirect bool) martini.Handler {
ctx.Repo.IsWatching = models.IsWatching(ctx.User.Id, repo.Id)
}
ctx.Repo.Repository = repo
scheme := "http"
if base.EnableHttpsClone {
scheme = "https"
}
ctx.Repo.CloneLink.SSH = fmt.Sprintf("git@%s:%s/%s.git", base.Domain, user.LowerName, repo.LowerName)
ctx.Repo.CloneLink.HTTPS = fmt.Sprintf("https://%s/%s/%s.git", base.Domain, user.LowerName, repo.LowerName)
ctx.Repo.CloneLink.HTTPS = fmt.Sprintf("%s://%s/%s/%s.git", scheme, base.Domain, user.LowerName, repo.LowerName)
ctx.Data["IsRepositoryValid"] = true
ctx.Data["Repository"] = repo