diff options
author | Willem van Dreumel <willemvd@users.noreply.github.com> | 2017-02-22 08:14:37 +0100 |
---|---|---|
committer | Kim "BKC" Carlbäcker <kim.carlbacker@gmail.com> | 2017-02-22 08:14:37 +0100 |
commit | 01d957677f160e4b5e43ce043b05e246493b34ea (patch) | |
tree | e06e3849d874ce37f02b29666ada6069b78decd9 /vendor/github.com/markbates/goth/gothic | |
parent | fd941db246e66244ec81f43d74b8358c06173fd6 (diff) | |
download | gitea-01d957677f160e4b5e43ce043b05e246493b34ea.tar.gz gitea-01d957677f160e4b5e43ce043b05e246493b34ea.zip |
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)
Diffstat (limited to 'vendor/github.com/markbates/goth/gothic')
-rw-r--r-- | vendor/github.com/markbates/goth/gothic/gothic.go | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/vendor/github.com/markbates/goth/gothic/gothic.go b/vendor/github.com/markbates/goth/gothic/gothic.go new file mode 100644 index 0000000000..f6aaf2d117 --- /dev/null +++ b/vendor/github.com/markbates/goth/gothic/gothic.go @@ -0,0 +1,219 @@ +/* +Package gothic wraps common behaviour when using Goth. This makes it quick, and easy, to get up +and running with Goth. Of course, if you want complete control over how things flow, in regards +to the authentication process, feel free and use Goth directly. + +See https://github.com/markbates/goth/examples/main.go to see this in action. +*/ +package gothic + +import ( + "errors" + "fmt" + "net/http" + "os" + + "github.com/gorilla/mux" + "github.com/gorilla/sessions" + "github.com/markbates/goth" +) + +// SessionName is the key used to access the session store. +const SessionName = "_gothic_session" + +// Store can/should be set by applications using gothic. The default is a cookie store. +var Store sessions.Store +var defaultStore sessions.Store + +var keySet = false + +func init() { + key := []byte(os.Getenv("SESSION_SECRET")) + keySet = len(key) != 0 + Store = sessions.NewCookieStore([]byte(key)) + defaultStore = Store +} + +/* +BeginAuthHandler is a convienence handler for starting the authentication process. +It expects to be able to get the name of the provider from the query parameters +as either "provider" or ":provider". + +BeginAuthHandler will redirect the user to the appropriate authentication end-point +for the requested provider. + +See https://github.com/markbates/goth/examples/main.go to see this in action. +*/ +func BeginAuthHandler(res http.ResponseWriter, req *http.Request) { + url, err := GetAuthURL(res, req) + if err != nil { + res.WriteHeader(http.StatusBadRequest) + fmt.Fprintln(res, err) + return + } + + http.Redirect(res, req, url, http.StatusTemporaryRedirect) +} + +// SetState sets the state string associated with the given request. +// If no state string is associated with the request, one will be generated. +// This state is sent to the provider and can be retrieved during the +// callback. +var SetState = func(req *http.Request) string { + state := req.URL.Query().Get("state") + if len(state) > 0 { + return state + } + + return "state" + +} + +// GetState gets the state returned by the provider during the callback. +// This is used to prevent CSRF attacks, see +// http://tools.ietf.org/html/rfc6749#section-10.12 +var GetState = func(req *http.Request) string { + return req.URL.Query().Get("state") +} + +/* +GetAuthURL starts the authentication process with the requested provided. +It will return a URL that should be used to send users to. + +It expects to be able to get the name of the provider from the query parameters +as either "provider" or ":provider". + +I would recommend using the BeginAuthHandler instead of doing all of these steps +yourself, but that's entirely up to you. +*/ +func GetAuthURL(res http.ResponseWriter, req *http.Request) (string, error) { + + if !keySet && defaultStore == Store { + fmt.Println("goth/gothic: no SESSION_SECRET environment variable is set. The default cookie store is not available and any calls will fail. Ignore this warning if you are using a different store.") + } + + providerName, err := GetProviderName(req) + if err != nil { + return "", err + } + + provider, err := goth.GetProvider(providerName) + if err != nil { + return "", err + } + sess, err := provider.BeginAuth(SetState(req)) + if err != nil { + return "", err + } + + url, err := sess.GetAuthURL() + if err != nil { + return "", err + } + + err = storeInSession(providerName, sess.Marshal(), req, res) + + if err != nil { + return "", err + } + + return url, err +} + +/* +CompleteUserAuth does what it says on the tin. It completes the authentication +process and fetches all of the basic information about the user from the provider. + +It expects to be able to get the name of the provider from the query parameters +as either "provider" or ":provider". + +See https://github.com/markbates/goth/examples/main.go to see this in action. +*/ +var CompleteUserAuth = func(res http.ResponseWriter, req *http.Request) (goth.User, error) { + + if !keySet && defaultStore == Store { + fmt.Println("goth/gothic: no SESSION_SECRET environment variable is set. The default cookie store is not available and any calls will fail. Ignore this warning if you are using a different store.") + } + + providerName, err := GetProviderName(req) + if err != nil { + return goth.User{}, err + } + + provider, err := goth.GetProvider(providerName) + if err != nil { + return goth.User{}, err + } + + value, err := getFromSession(providerName, req) + if err != nil { + return goth.User{}, err + } + + sess, err := provider.UnmarshalSession(value) + if err != nil { + return goth.User{}, err + } + + user, err := provider.FetchUser(sess) + if err == nil { + // user can be found with existing session data + return user, err + } + + // get new token and retry fetch + _, err = sess.Authorize(provider, req.URL.Query()) + if err != nil { + return goth.User{}, err + } + + err = storeInSession(providerName, sess.Marshal(), req, res) + + if err != nil { + return goth.User{}, err + } + + return provider.FetchUser(sess) +} + +// GetProviderName is a function used to get the name of a provider +// for a given request. By default, this provider is fetched from +// the URL query string. If you provide it in a different way, +// assign your own function to this variable that returns the provider +// name for your request. +var GetProviderName = getProviderName + +func getProviderName(req *http.Request) (string, error) { + provider := req.URL.Query().Get("provider") + if provider == "" { + if p, ok := mux.Vars(req)["provider"]; ok { + return p, nil + } + } + if provider == "" { + provider = req.URL.Query().Get(":provider") + } + if provider == "" { + return provider, errors.New("you must select a provider") + } + return provider, nil +} + +func storeInSession(key string, value string, req *http.Request, res http.ResponseWriter) error { + session, _ := Store.Get(req, key + SessionName) + + session.Values[key] = value + + return session.Save(req, res) +} + +func getFromSession(key string, req *http.Request) (string, error) { + session, _ := Store.Get(req, key + SessionName) + + value := session.Values[key] + if value == nil { + return "", errors.New("could not find a matching session for this request") + } + + return value.(string), nil +}
\ No newline at end of file |