// Use adds the session capability on router. func Use(router *wcg.Router, configure func()) { middleware.SessionConfigIni.StoreFactory = gae.SessionStoreFactory sessionBefore, sessionAfter := middleware.SessionSupport() csrf := middleware.CSRFSupport() router.Before(wcg.NewNamedHandler("session.before", func(res *wcg.Response, req *wcg.Request) { if canSkipSessionMiddleware(req) { return } sessionBefore.Process(res, req) if lib.IsOnGAE() { // Check only on GAE environment if req.Method() != "GET" && req.Method() != "HEAD" { csrf.Process(res, req) } } })) configure() router.After(wcg.NewNamedHandler("session.after", func(res *wcg.Response, req *wcg.Request) { if canSkipSessionMiddleware(req) { return } sessionAfter.Process(res, req) })) }
// StaticFile hosting static for prefix path. func ServeFile(filename string) wcg.Handler { h := wcg.HandlerFunc(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, filename) })) return wcg.NewNamedHandler("ServeFile", func(res *wcg.Response, req *wcg.Request) { h(res, req) res.Close() }) }
// NewInstance creates *Instance func NewInstance() *Instance { instance := &Instance{ Server: gae.NewServer(), } instance.Routes().Before(wcg.NewNamedHandler("init", func(res *wcg.Response, req *wcg.Request) { instance.once.Do(func() { req.Logger.Infof("----------- Server initialization ----------- ") if instance.Init != nil { instance.Init(req) } req.Logger.Infof("----------- Apps initialization ----------- ") for _, app := range instance.apps { if app.Init != nil { app.Init(req) } } req.Logger.Infof("----------- All init processes finished ----------- ") }) })) auth.Prepare(instance.Routes(), func() { instance.Routes().GET("/cron.yaml", wcg.NewNamedHandler("cron.yaml", func(res *wcg.Response, req *wcg.Request) { res.Header().Add("Content-Type", "text/yaml") res.WriteString("cron:\n") for _, app := range instance.apps { res.WriteString(app.Cron().ToYAML()) } })) instance.Routes().GET("/queue.yaml", wcg.NewNamedHandler("queue.yaml", func(res *wcg.Response, req *wcg.Request) { res.Header().Add("Content-Type", "text/yaml") res.WriteString("queue:\n") for _, app := range instance.apps { res.WriteString(app.Queue().ToYAML()) } })) }) instance.Routes().NotFound = Handler(func(req *wcg.Request) response.Response { return response.NotFound(req) }) return instance }
func byHeader(router *wcg.Router, configure func()) { router.Before(wcg.NewNamedHandler("HeaderAuth", func(res *wcg.Response, req *wcg.Request) { if !request.ByGuest(req) { // already authenticated req.Logger.Warnf("request.Authorize is called more than once.") return } // req.Logger.Infof("Token Authorization: %s", tokenString) if request.IsTask(req) { req.User = request.NewTaskUser(req) } else if request.IsCron(req) { req.User = request.CronUser } else { authorizeByAPIToken(req) } })) configure() }
// AccessLog middleware writes access logs into `out` writer with the specified `format`. // You can use the following placeholder variables in format string with '$var'. // // - IP ip address of the client. // - User an authenticated user name // - Time access time like '02/Nov/2013:17:29:48 +0900' // - Method http method // - Path path string including query string. // - Version version string such as 'HTTP/1.1' // - Status HTTP status code number // - Size response body size. // - Referer referer string // - Agent user agent string // - ResponseTime seconds taken for the response. // // if format string is "", it would be "$IP - $User [$Time] \"$Method $Path $Version\" $Status $Size \"$Referer\" \"$Agent\" $ResponseTime". func AccessLog(out io.Writer, format string) wcg.Handler { if format == "" { format = defaultAccsesLogFormat } repl := func(name string, v ...interface{}) []byte { req := v[0].(*wcg.Response) res := v[1].(*wcg.Request) fun := convFun[name] if fun != nil { return []byte(fun(req, res)) } return []byte{} } logger := wcg.NewCompiledFormatter(format, repl) // Return a function called in every request in the final phase of routers. return wcg.NewNamedHandler("AccessLog", func(res *wcg.Response, req *wcg.Request) { out.Write(logger.Format(res, req)) out.Write([]byte{'\n'}) }) }
// CSRFSupport to add a helper method csrf() to generate a hidden input for CSRF, and // returns validator handler func CSRFSupport() wcg.Handler { wcg.AddViewHelper("csrf", func(req *wcg.Request) template.HTML { if s := req.Session; s != nil { tok, _ := s.CSRFToken() return template.HTML("<input type=\"hidden\" name=\"" + CSRFTokenParamName + "\" value=\"" + tok + "\"></input>") } return "" }) wcg.AddViewHelper("csrf_token", func(req *wcg.Request) string { if s := req.Session; s != nil { tok, salt := s.CSRFToken() req.Logger.Debugf( "Generating CSRF Token: salt=%q, secret=%q, tok=%q", salt, s.CSRFSecret, tok, ) return tok } return "" }) var csrferror = map[string]string{ "error": "CSRF error", "description": "An invalid CSRF token was passed to the server.", } return wcg.NewNamedHandler("csrf", func(res *wcg.Response, req *wcg.Request) { if res.IsClosed() { return } token := req.Form(CSRFTokenParamName) if token == "" { // try to seek header token = req.Header(CSRFTokenHeaderName) } if err := req.Session.ValidateToken(token); err != nil { res.WriteJSONWithStatus(403, nil, csrferror) } }) }
func bySession(router *wcg.Router, configure func()) { fbconfig := facebook.NewAuthConfig("dummy", "dumyy", "") fbconfig.RedirectURL = "/login/facebook/callback" fbconfig.ContextFactory = func(res *wcg.Response, req *wcg.Request) context.Context { return gae.NewContext(req) } fbconfig.UnauthorizedHandler = wcg.AnonymousHandler(func(res *wcg.Response, req *wcg.Request) { res.TemplatesWithStatus(401, nil, "permrejected.html", "header.html", "footer.html") }) fbconfig.AuthorizedHandler = wcg.AnonymousHandler(func(res *wcg.Response, req *wcg.Request) { ref, _ := req.Session.Get("LoginRef") if ref != "" && strings.HasPrefix(ref, "/") { res.Redirect(wcg.AbsoluteURL(req, ref), http.StatusFound) } else { res.Redirect("/", http.StatusFound) } }) fbconfig.InvalidatedHandler = wcg.AnonymousHandler(func(res *wcg.Response, req *wcg.Request) { req.Logger.Debugf("Guest user access.") }) fbconfig.Scopes = []string{} fbauth, fbcallback, fbvalidates, fblogout := middleware.OAuth2(fbconfig) // set routes router.Before(wcg.NewNamedHandler("facebook.validate", func(res *wcg.Response, req *wcg.Request) { if !request.ByGuest(req) { // already authenticated return } if req.Session == nil { return } // Check the fbconfig from ServerConfig fbapp := configs.GetMultiValues( req, "facebook_app_id", "facebook_app_secret", "facebook_page_id", ) if fbapp[0] != "" && fbapp[1] != "" { fbconfig.ClientID = fbapp[0] fbconfig.ClientSecret = fbapp[1] fbvalidates.Process(res, req) } })) router.GET("/login/facebook", wcg.NewNamedHandler("facebook.login.auth", func(res *wcg.Response, req *wcg.Request) { if !isFBConfigured(fbconfig) { return } req.Session.Set("LoginRef", req.Query("ref")) fbauth.Process(res, req) })) router.GET("/login/facebook/callback", wcg.NewNamedHandler("facebook.login.callback", func(res *wcg.Response, req *wcg.Request) { if !isFBConfigured(fbconfig) { return } fbcallback.Process(res, req) })) router.POST("/logout/facebook", wcg.NewNamedHandler("facebook.logout", func(res *wcg.Response, req *wcg.Request) { if !isFBConfigured(fbconfig) { return } fblogout.Process(res, req) res.Redirect("/", http.StatusFound) })) configure() }
// SessionSupportWithConfig returns two middleware functions for session support, which need to be registered // on route.Before and route.After. // // Example: // // sprepare, scomplete := SessionSupport() // // route.Before(sprepare) // route.After(scomplete) // func SessionSupportWithConfig(cfg *SessionConfig) (wcg.Handler, wcg.Handler) { if cfg.StoreFactory == nil { memorystore := wcg.NewMemorySessionStore() cfg.StoreFactory = func(req *wcg.Request) wcg.SessionStore { req.Logger.Warnf("Memory SessionStore is used so that the session data would lost suddenly.") req.Logger.Warnf("If you are in GAE environment (including devserver), you must use GAESessionStoreFactory") return memorystore } } // load handler loads the session data from backend storage. load := func(res *wcg.Response, req *wcg.Request) { if res.IsClosed() { return } var sess *wcg.Session var err error store := cfg.StoreFactory(req) c, err := req.SignedCookie(cfg.CookieName, cfg.Key) if err != nil { req.Logger.Debugf("Could not decode the signed cookie on %s: %v", cfg.Key, err) } if c != nil { if wcg.IsUUID(c.Value) { sess, err = store.Load(wcg.UUID(c.Value)) } if err != nil { req.Logger.Warnf( "Could not load the session (id: %q) from the store (%v), use a new session.", err, c.Value) sess = nil c = nil } else { if sess == nil { req.Logger.Debugf("Session data for %q was not found on backend.", c.Value) } else { req.Logger.Debugf("Session data for %q found.", c.Value) expiredAt := sess.Timestamp.Add(time.Duration(cfg.MaxAge) * time.Second) if expiredAt.Before(time.Now()) { // expired req.Logger.Infof("Session data for %q is found but expired.", c.Value) if _, err := store.Delete(sess.ID); err != nil { req.Logger.Errorf("An error occurred while deleting the expired session (%q): %v", sess.ID, err) } sess = nil } } } } if c == nil { // no cookie found c = &http.Cookie{} c.Name = cfg.CookieName c.HttpOnly = cfg.HTTPOnly c.MaxAge = cfg.MaxAge if cfg.Domain != "" { c.Domain = cfg.Domain } } c.Path = cfg.Path // No session found if sess == nil { sess = wcg.NewSession(req) sess.Timestamp = time.Now() req.Logger.Debugf("Creating the new session.") store := cfg.StoreFactory(req) err := store.Save(sess) if err != nil { res.RenderInternalError("New Session could not be created: %v", err) return } } else { // Update the timestamp. sess.Timestamp = time.Now() } // Set the cookie data c.Value = string(sess.ID) c.Expires = sess.Timestamp.Add(time.Duration(cfg.MaxAge) * time.Second) c.MaxAge = cfg.MaxAge req.Session = sess encoded, _ := sess.Encode() req.SetLocal("__session_support", encoded) res.SetSignedCookie(c, cfg.Key) } // save handler store the cookie data into backend storage. save := func(res *wcg.Response, req *wcg.Request) { if encoded, ok := req.Local("__session_support").(string); ok { nencoded, _ := req.Session.Encode() // Store session if it is changed. if encoded != nencoded { store := cfg.StoreFactory(req) err := store.Save(req.Session) if err != nil { // TODO: log session error req.Logger.Errorf("Could not save the session data on backend: %v", err) } else { req.Logger.Debugf("Successfully stored the session data on backend.") } } else { req.Logger.Debugf("Session is not changed, skipped to be stored.") } } } return wcg.NewNamedHandler("Session.Load", load), wcg.NewNamedHandler("Session.Save", save) }
// OAuth2 composes a list of middleware functions to implement oauth2 handlers. // // - 1st: would be a middleware that redicts to the authorize URL. // - 2nd: would be a middleware that serves the callback from OAuth URL. if the client authorize the access, // it would call AuthorizedHandler after registering the token with the session. // otherwise, it would call UnauthorizedHandler. // - 3rd: would be a middleware that checks the token stored in the session and validate it. // if the token is not found or invalidated, it would call InvalidateHandler. // - 4th: would be a middleware that perform logout by cleaning up the current oauth token from a sessions // // This support function also adds some view helpers on your view system. // // oauth2_by(providerName string) : returns true if the user is authoriezed // func OAuth2(cfg *OAuth2Config) ( redirector wcg.Handler, // middleware to redirect authorize URL callbackHandler wcg.Handler, // middleware to serve the callback from OAuth URL validator wcg.Handler, // middleware to validate the token in session. logoutHandler wcg.Handler, // middleware to perform logout. ) { registerViewHelpers() if cfg.ClientID == "" || cfg.ClientSecret == "" { panic("ClietnID or ClietnSecret is empty: please check your configuration is corret and loaded.") } u, err := url.Parse(cfg.Endpoint.AuthURL) if err != nil { panic(err) } host := u.Host pname := cfg.ParameterName authh := cfg.AuthorizedHandler unauthh := cfg.UnauthorizedHandler unvalidh := cfg.InvalidatedHandler contextFactory := cfg.ContextFactory if pname == "" { pname = DefaultParameterName } if authh == nil { authh = DefaultAuthorizedHandler } if unauthh == nil { unauthh = DefaultUnauthorizedHandler } if contextFactory == nil { contextFactory = DefaultContextFactory } redirector = wcg.NewNamedHandler("OAuth2.redirector", func(res *wcg.Response, req *wcg.Request) { if req.Session == nil { res.RenderInternalError("OAuth2: Session is nil") res.End() return } res.Redirect(cfg.Use(req).AuthCodeURL(""), http.StatusFound) }) callbackHandler = wcg.NewNamedHandler("OAUth2.callback", func(res *wcg.Response, req *wcg.Request) { if req.Session == nil { res.RenderInternalError("OAuth2: Session is nil") res.End() return } // get a code and exchange to fetch the access token code := req.Form(pname) if code == "" { unauthh.Process(res, req) return } req.Logger.Debugf("Exchange OAuth2 token using code:%q", code) token, err := cfg.Use(req).Exchange(contextFactory(res, req), code) if err != nil || token == nil { req.Logger.Errorf("Token exchange failed: %v", err) unauthh.Process(res, req) return } req.Logger.Debugf("Got an oauth2 token: %v", token) // token is validated so fetch the user using UserFactory. req.Logger.Debugf("OAuth2: code authorization finished. Now retriving an user object.") user, err := cfg.UserFactory(res, req, token) if err != nil { req.Logger.Errorf("We could not fetch the user profilde from the token: %v", err) unauthh.Process(res, req) return } user.lastLogin = time.Now() user.Token = token req.User = user req.Logger.Infof("OAuth2 completed successfully (authed via %q)", cfg.Endpoint.AuthURL) // store token with session. storeOAuth2User(host, user, req) authh.Process(res, req) }) validator = wcg.NewNamedHandler("OAuth2.validator", func(res *wcg.Response, req *wcg.Request) { if user := FindOAuth2UserInSession(req.Session, host); user != nil { req.Logger.Debugf("OAuth2 token is found in this session.") req.User = user } else { req.Logger.Debugf("OAuth2 token is not found or expired in this session.") req.Logger.Debugf("Session dump: %v", req.Session) unvalidh.Process(res, req) } }) logoutHandler = wcg.NewNamedHandler("OAuth2.logout", func(res *wcg.Response, req *wcg.Request) { removeOAuth2User(host, req) }) return redirector, callbackHandler, validator, logoutHandler }
return &oauth2.Config{ ClientID: r.ClientID, ClientSecret: r.ClientSecret, Scopes: r.Scopes, Endpoint: r.Endpoint, RedirectURL: redirect, } } // DefaultParameterName is a parameter name for oauth code. ("code" by default) var DefaultParameterName = "code" // DefaultUnauthorizedHandler is a defualt http handler when unauthorlized. var DefaultUnauthorizedHandler = wcg.NewNamedHandler("OAuth2.DefaultUnauthorized", func(res *wcg.Response, req *wcg.Request) { res.WriteHeader(403) res.WriteString("Unauthorized.") res.End() }) // DefaultAuthorizedHandler is a defualt http handler when authorlized. var DefaultAuthorizedHandler = wcg.NewNamedHandler("OAuth2.DefaultAutorized", func(res *wcg.Response, req *wcg.Request) { res.WriteString("Authorized.") res.End() }) // DefaultInvalidatedHandler is a defualt http handler when invalidated. var DefaultInvalidatedHandler = wcg.NewNamedHandler("OAuth2.DefaultInvalidated", func(res *wcg.Response, req *wcg.Request) { res.WriteHeader(403) res.WriteString("Invalidated.")