diff options
Diffstat (limited to 'modules/middleware')
-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 | 8 |
4 files changed, 201 insertions, 40 deletions
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..eea2570c 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("/") @@ -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 |