diff options
author | Lunny Xiao <xiaolunwen@gmail.com> | 2014-03-23 16:43:31 +0800 |
---|---|---|
committer | Lunny Xiao <xiaolunwen@gmail.com> | 2014-03-23 16:43:31 +0800 |
commit | 5c57a06c51ab17d18dcd3a8aeb81858e22b7236b (patch) | |
tree | aff44c1f3d9ba0bc550a95442414eec76d69cfcf /modules | |
parent | 1201c6a9b4897736650a4b416451aa521024b899 (diff) | |
parent | 1a16b3e99a0be7dbfeba1e683d253a22db5d1af5 (diff) |
merge
Diffstat (limited to 'modules')
-rw-r--r-- | modules/auth/auth.go | 1 | ||||
-rw-r--r-- | modules/auth/issue.go | 54 | ||||
-rw-r--r-- | modules/auth/user.go | 11 | ||||
-rw-r--r-- | modules/base/conf.go | 58 | ||||
-rw-r--r-- | modules/base/tool.go | 94 | ||||
-rw-r--r-- | modules/middleware/auth.go | 62 | ||||
-rw-r--r-- | modules/middleware/context.go | 166 | ||||
-rw-r--r-- | modules/middleware/render.go | 5 | ||||
-rw-r--r-- | modules/middleware/repo.go | 2 |
9 files changed, 402 insertions, 51 deletions
diff --git a/modules/auth/auth.go b/modules/auth/auth.go index 0e871688..2e0555f6 100644 --- a/modules/auth/auth.go +++ b/modules/auth/auth.go @@ -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 { diff --git a/modules/auth/issue.go b/modules/auth/issue.go new file mode 100644 index 00000000..e2b1f9f2 --- /dev/null +++ b/modules/auth/issue.go @@ -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) +} diff --git a/modules/auth/user.go b/modules/auth/user.go index f8d8f661..cb8db1b2 100644 --- a/modules/auth/user.go +++ b/modules/auth/user.go @@ -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 } diff --git a/modules/base/conf.go b/modules/base/conf.go index 863daca6..19f58707 100644 --- a/modules/base/conf.go +++ b/modules/base/conf.go @@ -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,33 @@ var ( RunUser string RepoRootPath string + 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 +95,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 +143,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 +177,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 +260,13 @@ func NewConfigContext() { SecretKey = Cfg.MustValue("security", "SECRET_KEY") RunUser = Cfg.MustValue("", "RUN_USER") + 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 +279,7 @@ func NewServices() { newService() newLogService() newCacheService() + newSessionService() newMailService() newRegisterMailService() } diff --git a/modules/base/tool.go b/modules/base/tool.go index 8fabb8c5..c7ee2ee8 100644 --- a/modules/base/tool.go +++ b/modules/base/tool.go @@ -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() } diff --git a/modules/middleware/auth.go b/modules/middleware/auth.go index f211de32..82c3367c 100644 --- a/modules/middleware/auth.go +++ b/modules/middleware/auth.go @@ -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 } } diff --git a/modules/middleware/context.go b/modules/middleware/context.go index a25a3dbb..d81ab999 100644 --- a/modules/middleware/context.go +++ b/modules/middleware/context.go @@ -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) diff --git a/modules/middleware/render.go b/modules/middleware/render.go index 8a541831..869ef9ab 100644 --- a/modules/middleware/render.go +++ b/modules/middleware/render.go @@ -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) { diff --git a/modules/middleware/repo.go b/modules/middleware/repo.go index a9a90e3f..3864caaf 100644 --- a/modules/middleware/repo.go +++ b/modules/middleware/repo.go @@ -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("/") |