forked from forgejo/forgejo
Oauth2 consumer (#679)
* initial stuff for oauth2 login, fails on: * login button on the signIn page to start the OAuth2 flow and a callback for each provider Only GitHub is implemented for now * show login button only when the OAuth2 consumer is configured (and activated) * create macaron group for oauth2 urls * prevent net/http in modules (other then oauth2) * use a new data sessions oauth2 folder for storing the oauth2 session data * add missing 2FA when this is enabled on the user * add password option for OAuth2 user , for use with git over http and login to the GUI * add tip for registering a GitHub OAuth application * at startup of Gitea register all configured providers and also on adding/deleting of new providers * custom handling of errors in oauth2 request init + show better tip * add ExternalLoginUser model and migration script to add it to database * link a external account to an existing account (still need to handle wrong login and signup) and remove if user is removed * remove the linked external account from the user his settings * if user is unknown we allow him to register a new account or link it to some existing account * sign up with button on signin page (als change OAuth2Provider structure so we can store basic stuff about providers) * from gorilla/sessions docs: "Important Note: If you aren't using gorilla/mux, you need to wrap your handlers with context.ClearHandler as or else you will leak memory!" (we're using gorilla/sessions for storing oauth2 sessions) * use updated goth lib that now supports getting the OAuth2 user if the AccessToken is still valid instead of re-authenticating (prevent flooding the OAuth2 provider)
This commit is contained in:
parent
fd941db246
commit
01d957677f
76 changed files with 7275 additions and 137 deletions
27
vendor/github.com/gorilla/sessions/LICENSE
generated
vendored
Normal file
27
vendor/github.com/gorilla/sessions/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
81
vendor/github.com/gorilla/sessions/README.md
generated
vendored
Normal file
81
vendor/github.com/gorilla/sessions/README.md
generated
vendored
Normal file
|
@ -0,0 +1,81 @@
|
|||
sessions
|
||||
========
|
||||
[](https://godoc.org/github.com/gorilla/sessions) [](https://travis-ci.org/gorilla/sessions)
|
||||
|
||||
gorilla/sessions provides cookie and filesystem sessions and infrastructure for
|
||||
custom session backends.
|
||||
|
||||
The key features are:
|
||||
|
||||
* Simple API: use it as an easy way to set signed (and optionally
|
||||
encrypted) cookies.
|
||||
* Built-in backends to store sessions in cookies or the filesystem.
|
||||
* Flash messages: session values that last until read.
|
||||
* Convenient way to switch session persistency (aka "remember me") and set
|
||||
other attributes.
|
||||
* Mechanism to rotate authentication and encryption keys.
|
||||
* Multiple sessions per request, even using different backends.
|
||||
* Interfaces and infrastructure for custom session backends: sessions from
|
||||
different stores can be retrieved and batch-saved using a common API.
|
||||
|
||||
Let's start with an example that shows the sessions API in a nutshell:
|
||||
|
||||
```go
|
||||
import (
|
||||
"net/http"
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
var store = sessions.NewCookieStore([]byte("something-very-secret"))
|
||||
|
||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Get a session. We're ignoring the error resulted from decoding an
|
||||
// existing session: Get() always returns a session, even if empty.
|
||||
session, _ := store.Get(r, "session-name")
|
||||
// Set some session values.
|
||||
session.Values["foo"] = "bar"
|
||||
session.Values[42] = 43
|
||||
// Save it before we write to the response/return from the handler.
|
||||
session.Save(r, w)
|
||||
}
|
||||
```
|
||||
|
||||
First we initialize a session store calling `NewCookieStore()` and passing a
|
||||
secret key used to authenticate the session. Inside the handler, we call
|
||||
`store.Get()` to retrieve an existing session or a new one. Then we set some
|
||||
session values in session.Values, which is a `map[interface{}]interface{}`.
|
||||
And finally we call `session.Save()` to save the session in the response.
|
||||
|
||||
Important Note: If you aren't using gorilla/mux, you need to wrap your handlers
|
||||
with
|
||||
[`context.ClearHandler`](http://www.gorillatoolkit.org/pkg/context#ClearHandler)
|
||||
as or else you will leak memory! An easy way to do this is to wrap the top-level
|
||||
mux when calling http.ListenAndServe:
|
||||
|
||||
More examples are available [on the Gorilla
|
||||
website](http://www.gorillatoolkit.org/pkg/sessions).
|
||||
|
||||
## Store Implementations
|
||||
|
||||
Other implementations of the `sessions.Store` interface:
|
||||
|
||||
* [github.com/starJammer/gorilla-sessions-arangodb](https://github.com/starJammer/gorilla-sessions-arangodb) - ArangoDB
|
||||
* [github.com/yosssi/boltstore](https://github.com/yosssi/boltstore) - Bolt
|
||||
* [github.com/srinathgs/couchbasestore](https://github.com/srinathgs/couchbasestore) - Couchbase
|
||||
* [github.com/denizeren/dynamostore](https://github.com/denizeren/dynamostore) - Dynamodb on AWS
|
||||
* [github.com/bradleypeabody/gorilla-sessions-memcache](https://github.com/bradleypeabody/gorilla-sessions-memcache) - Memcache
|
||||
* [github.com/dsoprea/go-appengine-sessioncascade](https://github.com/dsoprea/go-appengine-sessioncascade) - Memcache/Datastore/Context in AppEngine
|
||||
* [github.com/kidstuff/mongostore](https://github.com/kidstuff/mongostore) - MongoDB
|
||||
* [github.com/srinathgs/mysqlstore](https://github.com/srinathgs/mysqlstore) - MySQL
|
||||
* [github.com/EnumApps/clustersqlstore](https://github.com/EnumApps/clustersqlstore) - MySQL Cluster
|
||||
* [github.com/antonlindstrom/pgstore](https://github.com/antonlindstrom/pgstore) - PostgreSQL
|
||||
* [github.com/boj/redistore](https://github.com/boj/redistore) - Redis
|
||||
* [github.com/boj/rethinkstore](https://github.com/boj/rethinkstore) - RethinkDB
|
||||
* [github.com/boj/riakstore](https://github.com/boj/riakstore) - Riak
|
||||
* [github.com/michaeljs1990/sqlitestore](https://github.com/michaeljs1990/sqlitestore) - SQLite
|
||||
* [github.com/wader/gormstore](https://github.com/wader/gormstore) - GORM (MySQL, PostgreSQL, SQLite)
|
||||
* [github.com/gernest/qlstore](https://github.com/gernest/qlstore) - ql
|
||||
|
||||
## License
|
||||
|
||||
BSD licensed. See the LICENSE file for details.
|
199
vendor/github.com/gorilla/sessions/doc.go
generated
vendored
Normal file
199
vendor/github.com/gorilla/sessions/doc.go
generated
vendored
Normal file
|
@ -0,0 +1,199 @@
|
|||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package sessions provides cookie and filesystem sessions and
|
||||
infrastructure for custom session backends.
|
||||
|
||||
The key features are:
|
||||
|
||||
* Simple API: use it as an easy way to set signed (and optionally
|
||||
encrypted) cookies.
|
||||
* Built-in backends to store sessions in cookies or the filesystem.
|
||||
* Flash messages: session values that last until read.
|
||||
* Convenient way to switch session persistency (aka "remember me") and set
|
||||
other attributes.
|
||||
* Mechanism to rotate authentication and encryption keys.
|
||||
* Multiple sessions per request, even using different backends.
|
||||
* Interfaces and infrastructure for custom session backends: sessions from
|
||||
different stores can be retrieved and batch-saved using a common API.
|
||||
|
||||
Let's start with an example that shows the sessions API in a nutshell:
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
var store = sessions.NewCookieStore([]byte("something-very-secret"))
|
||||
|
||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Get a session. We're ignoring the error resulted from decoding an
|
||||
// existing session: Get() always returns a session, even if empty.
|
||||
session, err := store.Get(r, "session-name")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Set some session values.
|
||||
session.Values["foo"] = "bar"
|
||||
session.Values[42] = 43
|
||||
// Save it before we write to the response/return from the handler.
|
||||
session.Save(r, w)
|
||||
}
|
||||
|
||||
First we initialize a session store calling NewCookieStore() and passing a
|
||||
secret key used to authenticate the session. Inside the handler, we call
|
||||
store.Get() to retrieve an existing session or a new one. Then we set some
|
||||
session values in session.Values, which is a map[interface{}]interface{}.
|
||||
And finally we call session.Save() to save the session in the response.
|
||||
|
||||
Note that in production code, we should check for errors when calling
|
||||
session.Save(r, w), and either display an error message or otherwise handle it.
|
||||
|
||||
Save must be called before writing to the response, otherwise the session
|
||||
cookie will not be sent to the client.
|
||||
|
||||
Important Note: If you aren't using gorilla/mux, you need to wrap your handlers
|
||||
with context.ClearHandler as or else you will leak memory! An easy way to do this
|
||||
is to wrap the top-level mux when calling http.ListenAndServe:
|
||||
|
||||
http.ListenAndServe(":8080", context.ClearHandler(http.DefaultServeMux))
|
||||
|
||||
The ClearHandler function is provided by the gorilla/context package.
|
||||
|
||||
That's all you need to know for the basic usage. Let's take a look at other
|
||||
options, starting with flash messages.
|
||||
|
||||
Flash messages are session values that last until read. The term appeared with
|
||||
Ruby On Rails a few years back. When we request a flash message, it is removed
|
||||
from the session. To add a flash, call session.AddFlash(), and to get all
|
||||
flashes, call session.Flashes(). Here is an example:
|
||||
|
||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Get a session.
|
||||
session, err := store.Get(r, "session-name")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Get the previously flashes, if any.
|
||||
if flashes := session.Flashes(); len(flashes) > 0 {
|
||||
// Use the flash values.
|
||||
} else {
|
||||
// Set a new flash.
|
||||
session.AddFlash("Hello, flash messages world!")
|
||||
}
|
||||
session.Save(r, w)
|
||||
}
|
||||
|
||||
Flash messages are useful to set information to be read after a redirection,
|
||||
like after form submissions.
|
||||
|
||||
There may also be cases where you want to store a complex datatype within a
|
||||
session, such as a struct. Sessions are serialised using the encoding/gob package,
|
||||
so it is easy to register new datatypes for storage in sessions:
|
||||
|
||||
import(
|
||||
"encoding/gob"
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
type Person struct {
|
||||
FirstName string
|
||||
LastName string
|
||||
Email string
|
||||
Age int
|
||||
}
|
||||
|
||||
type M map[string]interface{}
|
||||
|
||||
func init() {
|
||||
|
||||
gob.Register(&Person{})
|
||||
gob.Register(&M{})
|
||||
}
|
||||
|
||||
As it's not possible to pass a raw type as a parameter to a function, gob.Register()
|
||||
relies on us passing it a value of the desired type. In the example above we've passed
|
||||
it a pointer to a struct and a pointer to a custom type representing a
|
||||
map[string]interface. (We could have passed non-pointer values if we wished.) This will
|
||||
then allow us to serialise/deserialise values of those types to and from our sessions.
|
||||
|
||||
Note that because session values are stored in a map[string]interface{}, there's
|
||||
a need to type-assert data when retrieving it. We'll use the Person struct we registered above:
|
||||
|
||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
session, err := store.Get(r, "session-name")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Retrieve our struct and type-assert it
|
||||
val := session.Values["person"]
|
||||
var person = &Person{}
|
||||
if person, ok := val.(*Person); !ok {
|
||||
// Handle the case that it's not an expected type
|
||||
}
|
||||
|
||||
// Now we can use our person object
|
||||
}
|
||||
|
||||
By default, session cookies last for a month. This is probably too long for
|
||||
some cases, but it is easy to change this and other attributes during
|
||||
runtime. Sessions can be configured individually or the store can be
|
||||
configured and then all sessions saved using it will use that configuration.
|
||||
We access session.Options or store.Options to set a new configuration. The
|
||||
fields are basically a subset of http.Cookie fields. Let's change the
|
||||
maximum age of a session to one week:
|
||||
|
||||
session.Options = &sessions.Options{
|
||||
Path: "/",
|
||||
MaxAge: 86400 * 7,
|
||||
HttpOnly: true,
|
||||
}
|
||||
|
||||
Sometimes we may want to change authentication and/or encryption keys without
|
||||
breaking existing sessions. The CookieStore supports key rotation, and to use
|
||||
it you just need to set multiple authentication and encryption keys, in pairs,
|
||||
to be tested in order:
|
||||
|
||||
var store = sessions.NewCookieStore(
|
||||
[]byte("new-authentication-key"),
|
||||
[]byte("new-encryption-key"),
|
||||
[]byte("old-authentication-key"),
|
||||
[]byte("old-encryption-key"),
|
||||
)
|
||||
|
||||
New sessions will be saved using the first pair. Old sessions can still be
|
||||
read because the first pair will fail, and the second will be tested. This
|
||||
makes it easy to "rotate" secret keys and still be able to validate existing
|
||||
sessions. Note: for all pairs the encryption key is optional; set it to nil
|
||||
or omit it and and encryption won't be used.
|
||||
|
||||
Multiple sessions can be used in the same request, even with different
|
||||
session backends. When this happens, calling Save() on each session
|
||||
individually would be cumbersome, so we have a way to save all sessions
|
||||
at once: it's sessions.Save(). Here's an example:
|
||||
|
||||
var store = sessions.NewCookieStore([]byte("something-very-secret"))
|
||||
|
||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Get a session and set a value.
|
||||
session1, _ := store.Get(r, "session-one")
|
||||
session1.Values["foo"] = "bar"
|
||||
// Get another session and set another value.
|
||||
session2, _ := store.Get(r, "session-two")
|
||||
session2.Values[42] = 43
|
||||
// Save all sessions.
|
||||
sessions.Save(r, w)
|
||||
}
|
||||
|
||||
This is possible because when we call Get() from a session store, it adds the
|
||||
session to a common registry. Save() uses it to save all registered sessions.
|
||||
*/
|
||||
package sessions
|
102
vendor/github.com/gorilla/sessions/lex.go
generated
vendored
Normal file
102
vendor/github.com/gorilla/sessions/lex.go
generated
vendored
Normal file
|
@ -0,0 +1,102 @@
|
|||
// This file contains code adapted from the Go standard library
|
||||
// https://github.com/golang/go/blob/39ad0fd0789872f9469167be7fe9578625ff246e/src/net/http/lex.go
|
||||
|
||||
package sessions
|
||||
|
||||
import "strings"
|
||||
|
||||
var isTokenTable = [127]bool{
|
||||
'!': true,
|
||||
'#': true,
|
||||
'$': true,
|
||||
'%': true,
|
||||
'&': true,
|
||||
'\'': true,
|
||||
'*': true,
|
||||
'+': true,
|
||||
'-': true,
|
||||
'.': true,
|
||||
'0': true,
|
||||
'1': true,
|
||||
'2': true,
|
||||
'3': true,
|
||||
'4': true,
|
||||
'5': true,
|
||||
'6': true,
|
||||
'7': true,
|
||||
'8': true,
|
||||
'9': true,
|
||||
'A': true,
|
||||
'B': true,
|
||||
'C': true,
|
||||
'D': true,
|
||||
'E': true,
|
||||
'F': true,
|
||||
'G': true,
|
||||
'H': true,
|
||||
'I': true,
|
||||
'J': true,
|
||||
'K': true,
|
||||
'L': true,
|
||||
'M': true,
|
||||
'N': true,
|
||||
'O': true,
|
||||
'P': true,
|
||||
'Q': true,
|
||||
'R': true,
|
||||
'S': true,
|
||||
'T': true,
|
||||
'U': true,
|
||||
'W': true,
|
||||
'V': true,
|
||||
'X': true,
|
||||
'Y': true,
|
||||
'Z': true,
|
||||
'^': true,
|
||||
'_': true,
|
||||
'`': true,
|
||||
'a': true,
|
||||
'b': true,
|
||||
'c': true,
|
||||
'd': true,
|
||||
'e': true,
|
||||
'f': true,
|
||||
'g': true,
|
||||
'h': true,
|
||||
'i': true,
|
||||
'j': true,
|
||||
'k': true,
|
||||
'l': true,
|
||||
'm': true,
|
||||
'n': true,
|
||||
'o': true,
|
||||
'p': true,
|
||||
'q': true,
|
||||
'r': true,
|
||||
's': true,
|
||||
't': true,
|
||||
'u': true,
|
||||
'v': true,
|
||||
'w': true,
|
||||
'x': true,
|
||||
'y': true,
|
||||
'z': true,
|
||||
'|': true,
|
||||
'~': true,
|
||||
}
|
||||
|
||||
func isToken(r rune) bool {
|
||||
i := int(r)
|
||||
return i < len(isTokenTable) && isTokenTable[i]
|
||||
}
|
||||
|
||||
func isNotToken(r rune) bool {
|
||||
return !isToken(r)
|
||||
}
|
||||
|
||||
func isCookieNameValid(raw string) bool {
|
||||
if raw == "" {
|
||||
return false
|
||||
}
|
||||
return strings.IndexFunc(raw, isNotToken) < 0
|
||||
}
|
241
vendor/github.com/gorilla/sessions/sessions.go
generated
vendored
Normal file
241
vendor/github.com/gorilla/sessions/sessions.go
generated
vendored
Normal file
|
@ -0,0 +1,241 @@
|
|||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/context"
|
||||
)
|
||||
|
||||
// Default flashes key.
|
||||
const flashesKey = "_flash"
|
||||
|
||||
// Options --------------------------------------------------------------------
|
||||
|
||||
// Options stores configuration for a session or session store.
|
||||
//
|
||||
// Fields are a subset of http.Cookie fields.
|
||||
type Options struct {
|
||||
Path string
|
||||
Domain string
|
||||
// MaxAge=0 means no 'Max-Age' attribute specified.
|
||||
// MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'.
|
||||
// MaxAge>0 means Max-Age attribute present and given in seconds.
|
||||
MaxAge int
|
||||
Secure bool
|
||||
HttpOnly bool
|
||||
}
|
||||
|
||||
// Session --------------------------------------------------------------------
|
||||
|
||||
// NewSession is called by session stores to create a new session instance.
|
||||
func NewSession(store Store, name string) *Session {
|
||||
return &Session{
|
||||
Values: make(map[interface{}]interface{}),
|
||||
store: store,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
// Session stores the values and optional configuration for a session.
|
||||
type Session struct {
|
||||
// The ID of the session, generated by stores. It should not be used for
|
||||
// user data.
|
||||
ID string
|
||||
// Values contains the user-data for the session.
|
||||
Values map[interface{}]interface{}
|
||||
Options *Options
|
||||
IsNew bool
|
||||
store Store
|
||||
name string
|
||||
}
|
||||
|
||||
// Flashes returns a slice of flash messages from the session.
|
||||
//
|
||||
// A single variadic argument is accepted, and it is optional: it defines
|
||||
// the flash key. If not defined "_flash" is used by default.
|
||||
func (s *Session) Flashes(vars ...string) []interface{} {
|
||||
var flashes []interface{}
|
||||
key := flashesKey
|
||||
if len(vars) > 0 {
|
||||
key = vars[0]
|
||||
}
|
||||
if v, ok := s.Values[key]; ok {
|
||||
// Drop the flashes and return it.
|
||||
delete(s.Values, key)
|
||||
flashes = v.([]interface{})
|
||||
}
|
||||
return flashes
|
||||
}
|
||||
|
||||
// AddFlash adds a flash message to the session.
|
||||
//
|
||||
// A single variadic argument is accepted, and it is optional: it defines
|
||||
// the flash key. If not defined "_flash" is used by default.
|
||||
func (s *Session) AddFlash(value interface{}, vars ...string) {
|
||||
key := flashesKey
|
||||
if len(vars) > 0 {
|
||||
key = vars[0]
|
||||
}
|
||||
var flashes []interface{}
|
||||
if v, ok := s.Values[key]; ok {
|
||||
flashes = v.([]interface{})
|
||||
}
|
||||
s.Values[key] = append(flashes, value)
|
||||
}
|
||||
|
||||
// Save is a convenience method to save this session. It is the same as calling
|
||||
// store.Save(request, response, session). You should call Save before writing to
|
||||
// the response or returning from the handler.
|
||||
func (s *Session) Save(r *http.Request, w http.ResponseWriter) error {
|
||||
return s.store.Save(r, w, s)
|
||||
}
|
||||
|
||||
// Name returns the name used to register the session.
|
||||
func (s *Session) Name() string {
|
||||
return s.name
|
||||
}
|
||||
|
||||
// Store returns the session store used to register the session.
|
||||
func (s *Session) Store() Store {
|
||||
return s.store
|
||||
}
|
||||
|
||||
// Registry -------------------------------------------------------------------
|
||||
|
||||
// sessionInfo stores a session tracked by the registry.
|
||||
type sessionInfo struct {
|
||||
s *Session
|
||||
e error
|
||||
}
|
||||
|
||||
// contextKey is the type used to store the registry in the context.
|
||||
type contextKey int
|
||||
|
||||
// registryKey is the key used to store the registry in the context.
|
||||
const registryKey contextKey = 0
|
||||
|
||||
// GetRegistry returns a registry instance for the current request.
|
||||
func GetRegistry(r *http.Request) *Registry {
|
||||
registry := context.Get(r, registryKey)
|
||||
if registry != nil {
|
||||
return registry.(*Registry)
|
||||
}
|
||||
newRegistry := &Registry{
|
||||
request: r,
|
||||
sessions: make(map[string]sessionInfo),
|
||||
}
|
||||
context.Set(r, registryKey, newRegistry)
|
||||
return newRegistry
|
||||
}
|
||||
|
||||
// Registry stores sessions used during a request.
|
||||
type Registry struct {
|
||||
request *http.Request
|
||||
sessions map[string]sessionInfo
|
||||
}
|
||||
|
||||
// Get registers and returns a session for the given name and session store.
|
||||
//
|
||||
// It returns a new session if there are no sessions registered for the name.
|
||||
func (s *Registry) Get(store Store, name string) (session *Session, err error) {
|
||||
if !isCookieNameValid(name) {
|
||||
return nil, fmt.Errorf("sessions: invalid character in cookie name: %s", name)
|
||||
}
|
||||
if info, ok := s.sessions[name]; ok {
|
||||
session, err = info.s, info.e
|
||||
} else {
|
||||
session, err = store.New(s.request, name)
|
||||
session.name = name
|
||||
s.sessions[name] = sessionInfo{s: session, e: err}
|
||||
}
|
||||
session.store = store
|
||||
return
|
||||
}
|
||||
|
||||
// Save saves all sessions registered for the current request.
|
||||
func (s *Registry) Save(w http.ResponseWriter) error {
|
||||
var errMulti MultiError
|
||||
for name, info := range s.sessions {
|
||||
session := info.s
|
||||
if session.store == nil {
|
||||
errMulti = append(errMulti, fmt.Errorf(
|
||||
"sessions: missing store for session %q", name))
|
||||
} else if err := session.store.Save(s.request, w, session); err != nil {
|
||||
errMulti = append(errMulti, fmt.Errorf(
|
||||
"sessions: error saving session %q -- %v", name, err))
|
||||
}
|
||||
}
|
||||
if errMulti != nil {
|
||||
return errMulti
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helpers --------------------------------------------------------------------
|
||||
|
||||
func init() {
|
||||
gob.Register([]interface{}{})
|
||||
}
|
||||
|
||||
// Save saves all sessions used during the current request.
|
||||
func Save(r *http.Request, w http.ResponseWriter) error {
|
||||
return GetRegistry(r).Save(w)
|
||||
}
|
||||
|
||||
// NewCookie returns an http.Cookie with the options set. It also sets
|
||||
// the Expires field calculated based on the MaxAge value, for Internet
|
||||
// Explorer compatibility.
|
||||
func NewCookie(name, value string, options *Options) *http.Cookie {
|
||||
cookie := &http.Cookie{
|
||||
Name: name,
|
||||
Value: value,
|
||||
Path: options.Path,
|
||||
Domain: options.Domain,
|
||||
MaxAge: options.MaxAge,
|
||||
Secure: options.Secure,
|
||||
HttpOnly: options.HttpOnly,
|
||||
}
|
||||
if options.MaxAge > 0 {
|
||||
d := time.Duration(options.MaxAge) * time.Second
|
||||
cookie.Expires = time.Now().Add(d)
|
||||
} else if options.MaxAge < 0 {
|
||||
// Set it to the past to expire now.
|
||||
cookie.Expires = time.Unix(1, 0)
|
||||
}
|
||||
return cookie
|
||||
}
|
||||
|
||||
// Error ----------------------------------------------------------------------
|
||||
|
||||
// MultiError stores multiple errors.
|
||||
//
|
||||
// Borrowed from the App Engine SDK.
|
||||
type MultiError []error
|
||||
|
||||
func (m MultiError) Error() string {
|
||||
s, n := "", 0
|
||||
for _, e := range m {
|
||||
if e != nil {
|
||||
if n == 0 {
|
||||
s = e.Error()
|
||||
}
|
||||
n++
|
||||
}
|
||||
}
|
||||
switch n {
|
||||
case 0:
|
||||
return "(0 errors)"
|
||||
case 1:
|
||||
return s
|
||||
case 2:
|
||||
return s + " (and 1 other error)"
|
||||
}
|
||||
return fmt.Sprintf("%s (and %d other errors)", s, n-1)
|
||||
}
|
295
vendor/github.com/gorilla/sessions/store.go
generated
vendored
Normal file
295
vendor/github.com/gorilla/sessions/store.go
generated
vendored
Normal file
|
@ -0,0 +1,295 @@
|
|||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package sessions
|
||||
|
||||
import (
|
||||
"encoding/base32"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/gorilla/securecookie"
|
||||
)
|
||||
|
||||
// Store is an interface for custom session stores.
|
||||
//
|
||||
// See CookieStore and FilesystemStore for examples.
|
||||
type Store interface {
|
||||
// Get should return a cached session.
|
||||
Get(r *http.Request, name string) (*Session, error)
|
||||
|
||||
// New should create and return a new session.
|
||||
//
|
||||
// Note that New should never return a nil session, even in the case of
|
||||
// an error if using the Registry infrastructure to cache the session.
|
||||
New(r *http.Request, name string) (*Session, error)
|
||||
|
||||
// Save should persist session to the underlying store implementation.
|
||||
Save(r *http.Request, w http.ResponseWriter, s *Session) error
|
||||
}
|
||||
|
||||
// CookieStore ----------------------------------------------------------------
|
||||
|
||||
// NewCookieStore returns a new CookieStore.
|
||||
//
|
||||
// Keys are defined in pairs to allow key rotation, but the common case is
|
||||
// to set a single authentication key and optionally an encryption key.
|
||||
//
|
||||
// The first key in a pair is used for authentication and the second for
|
||||
// encryption. The encryption key can be set to nil or omitted in the last
|
||||
// pair, but the authentication key is required in all pairs.
|
||||
//
|
||||
// It is recommended to use an authentication key with 32 or 64 bytes.
|
||||
// The encryption key, if set, must be either 16, 24, or 32 bytes to select
|
||||
// AES-128, AES-192, or AES-256 modes.
|
||||
//
|
||||
// Use the convenience function securecookie.GenerateRandomKey() to create
|
||||
// strong keys.
|
||||
func NewCookieStore(keyPairs ...[]byte) *CookieStore {
|
||||
cs := &CookieStore{
|
||||
Codecs: securecookie.CodecsFromPairs(keyPairs...),
|
||||
Options: &Options{
|
||||
Path: "/",
|
||||
MaxAge: 86400 * 30,
|
||||
},
|
||||
}
|
||||
|
||||
cs.MaxAge(cs.Options.MaxAge)
|
||||
return cs
|
||||
}
|
||||
|
||||
// CookieStore stores sessions using secure cookies.
|
||||
type CookieStore struct {
|
||||
Codecs []securecookie.Codec
|
||||
Options *Options // default configuration
|
||||
}
|
||||
|
||||
// Get returns a session for the given name after adding it to the registry.
|
||||
//
|
||||
// It returns a new session if the sessions doesn't exist. Access IsNew on
|
||||
// the session to check if it is an existing session or a new one.
|
||||
//
|
||||
// It returns a new session and an error if the session exists but could
|
||||
// not be decoded.
|
||||
func (s *CookieStore) Get(r *http.Request, name string) (*Session, error) {
|
||||
return GetRegistry(r).Get(s, name)
|
||||
}
|
||||
|
||||
// New returns a session for the given name without adding it to the registry.
|
||||
//
|
||||
// The difference between New() and Get() is that calling New() twice will
|
||||
// decode the session data twice, while Get() registers and reuses the same
|
||||
// decoded session after the first call.
|
||||
func (s *CookieStore) New(r *http.Request, name string) (*Session, error) {
|
||||
session := NewSession(s, name)
|
||||
opts := *s.Options
|
||||
session.Options = &opts
|
||||
session.IsNew = true
|
||||
var err error
|
||||
if c, errCookie := r.Cookie(name); errCookie == nil {
|
||||
err = securecookie.DecodeMulti(name, c.Value, &session.Values,
|
||||
s.Codecs...)
|
||||
if err == nil {
|
||||
session.IsNew = false
|
||||
}
|
||||
}
|
||||
return session, err
|
||||
}
|
||||
|
||||
// Save adds a single session to the response.
|
||||
func (s *CookieStore) Save(r *http.Request, w http.ResponseWriter,
|
||||
session *Session) error {
|
||||
encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
|
||||
s.Codecs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
http.SetCookie(w, NewCookie(session.Name(), encoded, session.Options))
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaxAge sets the maximum age for the store and the underlying cookie
|
||||
// implementation. Individual sessions can be deleted by setting Options.MaxAge
|
||||
// = -1 for that session.
|
||||
func (s *CookieStore) MaxAge(age int) {
|
||||
s.Options.MaxAge = age
|
||||
|
||||
// Set the maxAge for each securecookie instance.
|
||||
for _, codec := range s.Codecs {
|
||||
if sc, ok := codec.(*securecookie.SecureCookie); ok {
|
||||
sc.MaxAge(age)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FilesystemStore ------------------------------------------------------------
|
||||
|
||||
var fileMutex sync.RWMutex
|
||||
|
||||
// NewFilesystemStore returns a new FilesystemStore.
|
||||
//
|
||||
// The path argument is the directory where sessions will be saved. If empty
|
||||
// it will use os.TempDir().
|
||||
//
|
||||
// See NewCookieStore() for a description of the other parameters.
|
||||
func NewFilesystemStore(path string, keyPairs ...[]byte) *FilesystemStore {
|
||||
if path == "" {
|
||||
path = os.TempDir()
|
||||
}
|
||||
fs := &FilesystemStore{
|
||||
Codecs: securecookie.CodecsFromPairs(keyPairs...),
|
||||
Options: &Options{
|
||||
Path: "/",
|
||||
MaxAge: 86400 * 30,
|
||||
},
|
||||
path: path,
|
||||
}
|
||||
|
||||
fs.MaxAge(fs.Options.MaxAge)
|
||||
return fs
|
||||
}
|
||||
|
||||
// FilesystemStore stores sessions in the filesystem.
|
||||
//
|
||||
// It also serves as a reference for custom stores.
|
||||
//
|
||||
// This store is still experimental and not well tested. Feedback is welcome.
|
||||
type FilesystemStore struct {
|
||||
Codecs []securecookie.Codec
|
||||
Options *Options // default configuration
|
||||
path string
|
||||
}
|
||||
|
||||
// MaxLength restricts the maximum length of new sessions to l.
|
||||
// If l is 0 there is no limit to the size of a session, use with caution.
|
||||
// The default for a new FilesystemStore is 4096.
|
||||
func (s *FilesystemStore) MaxLength(l int) {
|
||||
for _, c := range s.Codecs {
|
||||
if codec, ok := c.(*securecookie.SecureCookie); ok {
|
||||
codec.MaxLength(l)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get returns a session for the given name after adding it to the registry.
|
||||
//
|
||||
// See CookieStore.Get().
|
||||
func (s *FilesystemStore) Get(r *http.Request, name string) (*Session, error) {
|
||||
return GetRegistry(r).Get(s, name)
|
||||
}
|
||||
|
||||
// New returns a session for the given name without adding it to the registry.
|
||||
//
|
||||
// See CookieStore.New().
|
||||
func (s *FilesystemStore) New(r *http.Request, name string) (*Session, error) {
|
||||
session := NewSession(s, name)
|
||||
opts := *s.Options
|
||||
session.Options = &opts
|
||||
session.IsNew = true
|
||||
var err error
|
||||
if c, errCookie := r.Cookie(name); errCookie == nil {
|
||||
err = securecookie.DecodeMulti(name, c.Value, &session.ID, s.Codecs...)
|
||||
if err == nil {
|
||||
err = s.load(session)
|
||||
if err == nil {
|
||||
session.IsNew = false
|
||||
}
|
||||
}
|
||||
}
|
||||
return session, err
|
||||
}
|
||||
|
||||
// Save adds a single session to the response.
|
||||
//
|
||||
// If the Options.MaxAge of the session is <= 0 then the session file will be
|
||||
// deleted from the store path. With this process it enforces the properly
|
||||
// session cookie handling so no need to trust in the cookie management in the
|
||||
// web browser.
|
||||
func (s *FilesystemStore) Save(r *http.Request, w http.ResponseWriter,
|
||||
session *Session) error {
|
||||
// Delete if max-age is <= 0
|
||||
if session.Options.MaxAge <= 0 {
|
||||
if err := s.erase(session); err != nil {
|
||||
return err
|
||||
}
|
||||
http.SetCookie(w, NewCookie(session.Name(), "", session.Options))
|
||||
return nil
|
||||
}
|
||||
|
||||
if session.ID == "" {
|
||||
// Because the ID is used in the filename, encode it to
|
||||
// use alphanumeric characters only.
|
||||
session.ID = strings.TrimRight(
|
||||
base32.StdEncoding.EncodeToString(
|
||||
securecookie.GenerateRandomKey(32)), "=")
|
||||
}
|
||||
if err := s.save(session); err != nil {
|
||||
return err
|
||||
}
|
||||
encoded, err := securecookie.EncodeMulti(session.Name(), session.ID,
|
||||
s.Codecs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
http.SetCookie(w, NewCookie(session.Name(), encoded, session.Options))
|
||||
return nil
|
||||
}
|
||||
|
||||
// MaxAge sets the maximum age for the store and the underlying cookie
|
||||
// implementation. Individual sessions can be deleted by setting Options.MaxAge
|
||||
// = -1 for that session.
|
||||
func (s *FilesystemStore) MaxAge(age int) {
|
||||
s.Options.MaxAge = age
|
||||
|
||||
// Set the maxAge for each securecookie instance.
|
||||
for _, codec := range s.Codecs {
|
||||
if sc, ok := codec.(*securecookie.SecureCookie); ok {
|
||||
sc.MaxAge(age)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// save writes encoded session.Values to a file.
|
||||
func (s *FilesystemStore) save(session *Session) error {
|
||||
encoded, err := securecookie.EncodeMulti(session.Name(), session.Values,
|
||||
s.Codecs...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filename := filepath.Join(s.path, "session_"+session.ID)
|
||||
fileMutex.Lock()
|
||||
defer fileMutex.Unlock()
|
||||
return ioutil.WriteFile(filename, []byte(encoded), 0600)
|
||||
}
|
||||
|
||||
// load reads a file and decodes its content into session.Values.
|
||||
func (s *FilesystemStore) load(session *Session) error {
|
||||
filename := filepath.Join(s.path, "session_"+session.ID)
|
||||
fileMutex.RLock()
|
||||
defer fileMutex.RUnlock()
|
||||
fdata, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = securecookie.DecodeMulti(session.Name(), string(fdata),
|
||||
&session.Values, s.Codecs...); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// delete session file
|
||||
func (s *FilesystemStore) erase(session *Session) error {
|
||||
filename := filepath.Join(s.path, "session_"+session.ID)
|
||||
|
||||
fileMutex.RLock()
|
||||
defer fileMutex.RUnlock()
|
||||
|
||||
err := os.Remove(filename)
|
||||
return err
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue