mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 06:38:31 +00:00 
			
		
		
		
	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:
		
				
					committed by
					
						 Kim "BKC" Carlbäcker
						Kim "BKC" Carlbäcker
					
				
			
			
				
	
			
			
			
						parent
						
							fd941db246
						
					
				
				
					commit
					01d957677f
				
			
							
								
								
									
										22
									
								
								vendor/github.com/markbates/goth/LICENSE.txt
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/markbates/goth/LICENSE.txt
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | ||||
| Copyright (c) 2014 Mark Bates | ||||
|  | ||||
| MIT License | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining | ||||
| a copy of this software and associated documentation files (the | ||||
| "Software"), to deal in the Software without restriction, including | ||||
| without limitation the rights to use, copy, modify, merge, publish, | ||||
| distribute, sublicense, and/or sell copies of the Software, and to | ||||
| permit persons to whom the Software is furnished to do so, subject to | ||||
| the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be | ||||
| included in all copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||||
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||||
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||||
| NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||||
| LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||||
| OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||||
| WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
							
								
								
									
										143
									
								
								vendor/github.com/markbates/goth/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								vendor/github.com/markbates/goth/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,143 @@ | ||||
| # Goth: Multi-Provider Authentication for Go [](https://godoc.org/github.com/markbates/goth) [](https://travis-ci.org/markbates/goth) | ||||
|  | ||||
| Package goth provides a simple, clean, and idiomatic way to write authentication | ||||
| packages for Go web applications. | ||||
|  | ||||
| Unlike other similar packages, Goth, lets you write OAuth, OAuth2, or any other | ||||
| protocol providers, as long as they implement the `Provider` and `Session` interfaces. | ||||
|  | ||||
| This package was inspired by [https://github.com/intridea/omniauth](https://github.com/intridea/omniauth). | ||||
|  | ||||
| ## Installation | ||||
|  | ||||
| ```text | ||||
| $ go get github.com/markbates/goth | ||||
| ``` | ||||
|  | ||||
| ## Supported Providers | ||||
|  | ||||
| * Amazon | ||||
| * Auth0 | ||||
| * Bitbucket | ||||
| * Box | ||||
| * Cloud Foundry | ||||
| * Dailymotion | ||||
| * Deezer | ||||
| * Digital Ocean | ||||
| * Discord | ||||
| * Dropbox | ||||
| * Facebook | ||||
| * Fitbit | ||||
| * GitHub | ||||
| * Gitlab | ||||
| * Google+ | ||||
| * Heroku | ||||
| * InfluxCloud | ||||
| * Instagram | ||||
| * Intercom | ||||
| * Lastfm | ||||
| * Linkedin | ||||
| * Meetup | ||||
| * OneDrive | ||||
| * OpenID Connect (auto discovery) | ||||
| * Paypal | ||||
| * SalesForce | ||||
| * Slack | ||||
| * Soundcloud | ||||
| * Spotify | ||||
| * Steam | ||||
| * Stripe | ||||
| * Twitch | ||||
| * Twitter | ||||
| * Uber | ||||
| * Wepay | ||||
| * Yahoo | ||||
| * Yammer | ||||
|  | ||||
| ## Examples | ||||
|  | ||||
| See the [examples](examples) folder for a working application that lets users authenticate | ||||
| through Twitter, Facebook, Google Plus etc. | ||||
|  | ||||
| To run the example either clone the source from GitHub | ||||
|  | ||||
| ```text | ||||
| $ git clone git@github.com:markbates/goth.git | ||||
| ``` | ||||
| or use | ||||
| ```text | ||||
| $ go get github.com/markbates/goth | ||||
| ``` | ||||
| ```text | ||||
| $ cd goth/examples | ||||
| $ go get -v | ||||
| $ go build  | ||||
| $ ./examples | ||||
| ``` | ||||
|  | ||||
| Now open up your browser and go to [http://localhost:3000](http://localhost:3000) to see the example. | ||||
|  | ||||
| To actually use the different providers, please make sure you configure them given the system environments as defined in the examples/main.go file | ||||
|  | ||||
| ## Issues | ||||
|  | ||||
| Issues always stand a significantly better chance of getting fixed if the are accompanied by a | ||||
| pull request. | ||||
|  | ||||
| ## Contributing | ||||
|  | ||||
| Would I love to see more providers? Certainly! Would you love to contribute one? Hopefully, yes! | ||||
|  | ||||
| 1. Fork it | ||||
| 2. Create your feature branch (git checkout -b my-new-feature) | ||||
| 3. Write Tests! | ||||
| 4. Commit your changes (git commit -am 'Add some feature') | ||||
| 5. Push to the branch (git push origin my-new-feature) | ||||
| 6. Create new Pull Request | ||||
|  | ||||
| ## Contributors | ||||
|  | ||||
| * Mark Bates | ||||
| * Tyler Bunnell | ||||
| * Corey McGrillis | ||||
| * willemvd | ||||
| * Rakesh Goyal | ||||
| * Andy Grunwald | ||||
| * Glenn Walker | ||||
| * Kevin Fitzpatrick | ||||
| * Ben Tranter | ||||
| * Sharad Ganapathy | ||||
| * Andrew Chilton | ||||
| * sharadgana | ||||
| * Aurorae | ||||
| * Craig P Jolicoeur | ||||
| * Zac Bergquist | ||||
| * Geoff Franks | ||||
| * Raphael Geronimi | ||||
| * Noah Shibley | ||||
| * lumost | ||||
| * oov | ||||
| * Felix Lamouroux | ||||
| * Rafael Quintela | ||||
| * Tyler | ||||
| * DenSm | ||||
| * Samy KACIMI | ||||
| * dante gray | ||||
| * Noah | ||||
| * Jacob Walker | ||||
| * Marin Martinic | ||||
| * Roy | ||||
| * Omni Adams | ||||
| * Sasa Brankovic | ||||
| * dkhamsing | ||||
| * Dante Swift | ||||
| * Attila Domokos | ||||
| * Albin Gilles | ||||
| * Syed Zubairuddin | ||||
| * Johnny Boursiquot | ||||
| * Jerome Touffe-Blin | ||||
| * bryanl | ||||
| * Masanobu YOSHIOKA | ||||
| * Jonathan Hall | ||||
| * HaiMing.Yin | ||||
| * Sairam Kunala | ||||
							
								
								
									
										10
									
								
								vendor/github.com/markbates/goth/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								vendor/github.com/markbates/goth/doc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| /* | ||||
| Package goth provides a simple, clean, and idiomatic way to write authentication | ||||
| packages for Go web applications. | ||||
|  | ||||
| This package was inspired by https://github.com/intridea/omniauth. | ||||
|  | ||||
| See the examples folder for a working application that lets users authenticate | ||||
| through Twitter or Facebook. | ||||
| */ | ||||
| package goth | ||||
							
								
								
									
										219
									
								
								vendor/github.com/markbates/goth/gothic/gothic.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								vendor/github.com/markbates/goth/gothic/gothic.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
							
								
								
									
										75
									
								
								vendor/github.com/markbates/goth/provider.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								vendor/github.com/markbates/goth/provider.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,75 @@ | ||||
| package goth | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
|  | ||||
| 	"golang.org/x/net/context" | ||||
| 	"golang.org/x/oauth2" | ||||
| ) | ||||
|  | ||||
| // Provider needs to be implemented for each 3rd party authentication provider | ||||
| // e.g. Facebook, Twitter, etc... | ||||
| type Provider interface { | ||||
| 	Name() string | ||||
| 	SetName(name string) | ||||
| 	BeginAuth(state string) (Session, error) | ||||
| 	UnmarshalSession(string) (Session, error) | ||||
| 	FetchUser(Session) (User, error) | ||||
| 	Debug(bool) | ||||
| 	RefreshToken(refreshToken string) (*oauth2.Token, error) //Get new access token based on the refresh token | ||||
| 	RefreshTokenAvailable() bool                             //Refresh token is provided by auth provider or not | ||||
| } | ||||
|  | ||||
| const NoAuthUrlErrorMessage = "an AuthURL has not been set" | ||||
|  | ||||
| // Providers is list of known/available providers. | ||||
| type Providers map[string]Provider | ||||
|  | ||||
| var providers = Providers{} | ||||
|  | ||||
| // UseProviders adds a list of available providers for use with Goth. | ||||
| // Can be called multiple times. If you pass the same provider more | ||||
| // than once, the last will be used. | ||||
| func UseProviders(viders ...Provider) { | ||||
| 	for _, provider := range viders { | ||||
| 		providers[provider.Name()] = provider | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetProviders returns a list of all the providers currently in use. | ||||
| func GetProviders() Providers { | ||||
| 	return providers | ||||
| } | ||||
|  | ||||
| // GetProvider returns a previously created provider. If Goth has not | ||||
| // been told to use the named provider it will return an error. | ||||
| func GetProvider(name string) (Provider, error) { | ||||
| 	provider := providers[name] | ||||
| 	if provider == nil { | ||||
| 		return nil, fmt.Errorf("no provider for %s exists", name) | ||||
| 	} | ||||
| 	return provider, nil | ||||
| } | ||||
|  | ||||
| // ClearProviders will remove all providers currently in use. | ||||
| // This is useful, mostly, for testing purposes. | ||||
| func ClearProviders() { | ||||
| 	providers = Providers{} | ||||
| } | ||||
|  | ||||
| // ContextForClient provides a context for use with oauth2. | ||||
| func ContextForClient(h *http.Client) context.Context { | ||||
| 	if h == nil { | ||||
| 		return oauth2.NoContext | ||||
| 	} | ||||
| 	return context.WithValue(oauth2.NoContext, oauth2.HTTPClient, h) | ||||
| } | ||||
|  | ||||
| // HTTPClientWithFallBack to be used in all fetch operations. | ||||
| func HTTPClientWithFallBack(h *http.Client) *http.Client { | ||||
| 	if h != nil { | ||||
| 		return h | ||||
| 	} | ||||
| 	return http.DefaultClient | ||||
| } | ||||
							
								
								
									
										224
									
								
								vendor/github.com/markbates/goth/providers/github/github.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								vendor/github.com/markbates/goth/providers/github/github.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,224 @@ | ||||
| // Package github implements the OAuth2 protocol for authenticating users through Github. | ||||
| // This package can be used as a reference implementation of an OAuth2 provider for Goth. | ||||
| package github | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"io/ioutil" | ||||
| 	"net/http" | ||||
| 	"net/url" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/markbates/goth" | ||||
| 	"golang.org/x/oauth2" | ||||
| ) | ||||
|  | ||||
| // These vars define the Authentication, Token, and API URLS for GitHub. If | ||||
| // using GitHub enterprise you should change these values before calling New. | ||||
| // | ||||
| // Examples: | ||||
| //	github.AuthURL = "https://github.acme.com/login/oauth/authorize | ||||
| //	github.TokenURL = "https://github.acme.com/login/oauth/access_token | ||||
| //	github.ProfileURL = "https://github.acme.com/api/v3/user | ||||
| //	github.EmailURL = "https://github.acme.com/api/v3/user/emails | ||||
| var ( | ||||
| 	AuthURL    = "https://github.com/login/oauth/authorize" | ||||
| 	TokenURL   = "https://github.com/login/oauth/access_token" | ||||
| 	ProfileURL = "https://api.github.com/user" | ||||
| 	EmailURL   = "https://api.github.com/user/emails" | ||||
| ) | ||||
|  | ||||
| // New creates a new Github provider, and sets up important connection details. | ||||
| // You should always call `github.New` to get a new Provider. Never try to create | ||||
| // one manually. | ||||
| func New(clientKey, secret, callbackURL string, scopes ...string) *Provider { | ||||
| 	p := &Provider{ | ||||
| 		ClientKey:           clientKey, | ||||
| 		Secret:              secret, | ||||
| 		CallbackURL:         callbackURL, | ||||
| 		providerName:        "github", | ||||
| 	} | ||||
| 	p.config = newConfig(p, scopes) | ||||
| 	return p | ||||
| } | ||||
|  | ||||
| // Provider is the implementation of `goth.Provider` for accessing Github. | ||||
| type Provider struct { | ||||
| 	ClientKey    string | ||||
| 	Secret       string | ||||
| 	CallbackURL  string | ||||
| 	HTTPClient   *http.Client | ||||
| 	config       *oauth2.Config | ||||
| 	providerName string | ||||
| } | ||||
|  | ||||
| // Name is the name used to retrieve this provider later. | ||||
| func (p *Provider) Name() string { | ||||
| 	return p.providerName | ||||
| } | ||||
|  | ||||
| // SetName is to update the name of the provider (needed in case of multiple providers of 1 type) | ||||
| func (p *Provider) SetName(name string) { | ||||
| 	p.providerName = name | ||||
| } | ||||
|  | ||||
| func (p *Provider) Client() *http.Client { | ||||
| 	return goth.HTTPClientWithFallBack(p.HTTPClient) | ||||
| } | ||||
|  | ||||
| // Debug is a no-op for the github package. | ||||
| func (p *Provider) Debug(debug bool) {} | ||||
|  | ||||
| // BeginAuth asks Github for an authentication end-point. | ||||
| func (p *Provider) BeginAuth(state string) (goth.Session, error) { | ||||
| 	url := p.config.AuthCodeURL(state) | ||||
| 	session := &Session{ | ||||
| 		AuthURL: url, | ||||
| 	} | ||||
| 	return session, nil | ||||
| } | ||||
|  | ||||
| // FetchUser will go to Github and access basic information about the user. | ||||
| func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { | ||||
| 	sess := session.(*Session) | ||||
| 	user := goth.User{ | ||||
| 		AccessToken: sess.AccessToken, | ||||
| 		Provider:    p.Name(), | ||||
| 	} | ||||
|  | ||||
| 	if user.AccessToken == "" { | ||||
| 		// data is not yet retrieved since accessToken is still empty | ||||
| 		return user, fmt.Errorf("%s cannot get user information without accessToken", p.providerName) | ||||
| 	} | ||||
|  | ||||
| 	response, err := p.Client().Get(ProfileURL + "?access_token=" + url.QueryEscape(sess.AccessToken)) | ||||
| 	if err != nil { | ||||
| 		return user, err | ||||
| 	} | ||||
| 	defer response.Body.Close() | ||||
|  | ||||
| 	if response.StatusCode != http.StatusOK { | ||||
| 		return user, fmt.Errorf("GitHub API responded with a %d trying to fetch user information", response.StatusCode) | ||||
| 	} | ||||
|  | ||||
| 	bits, err := ioutil.ReadAll(response.Body) | ||||
| 	if err != nil { | ||||
| 		return user, err | ||||
| 	} | ||||
|  | ||||
| 	err = json.NewDecoder(bytes.NewReader(bits)).Decode(&user.RawData) | ||||
| 	if err != nil { | ||||
| 		return user, err | ||||
| 	} | ||||
|  | ||||
| 	err = userFromReader(bytes.NewReader(bits), &user) | ||||
| 	if err != nil { | ||||
| 		return user, err | ||||
| 	} | ||||
|  | ||||
| 	if user.Email == "" { | ||||
| 		for _, scope := range p.config.Scopes { | ||||
| 			if strings.TrimSpace(scope) == "user" || strings.TrimSpace(scope) == "user:email" { | ||||
| 				user.Email, err = getPrivateMail(p, sess) | ||||
| 				if err != nil { | ||||
| 					return user, err | ||||
| 				} | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return user, err | ||||
| } | ||||
|  | ||||
| func userFromReader(reader io.Reader, user *goth.User) error { | ||||
| 	u := struct { | ||||
| 		ID       int    `json:"id"` | ||||
| 		Email    string `json:"email"` | ||||
| 		Bio      string `json:"bio"` | ||||
| 		Name     string `json:"name"` | ||||
| 		Login    string `json:"login"` | ||||
| 		Picture  string `json:"avatar_url"` | ||||
| 		Location string `json:"location"` | ||||
| 	}{} | ||||
|  | ||||
| 	err := json.NewDecoder(reader).Decode(&u) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	user.Name = u.Name | ||||
| 	user.NickName = u.Login | ||||
| 	user.Email = u.Email | ||||
| 	user.Description = u.Bio | ||||
| 	user.AvatarURL = u.Picture | ||||
| 	user.UserID = strconv.Itoa(u.ID) | ||||
| 	user.Location = u.Location | ||||
|  | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func getPrivateMail(p *Provider, sess *Session) (email string, err error) { | ||||
| 	response, err := p.Client().Get(EmailURL + "?access_token=" + url.QueryEscape(sess.AccessToken)) | ||||
| 	if err != nil { | ||||
| 		if response != nil { | ||||
| 			response.Body.Close() | ||||
| 		} | ||||
| 		return email, err | ||||
| 	} | ||||
| 	defer response.Body.Close() | ||||
|  | ||||
| 	if response.StatusCode != http.StatusOK { | ||||
| 		return email, fmt.Errorf("GitHub API responded with a %d trying to fetch user email", response.StatusCode) | ||||
| 	} | ||||
|  | ||||
| 	var mailList = []struct { | ||||
| 		Email    string `json:"email"` | ||||
| 		Primary  bool   `json:"primary"` | ||||
| 		Verified bool   `json:"verified"` | ||||
| 	}{} | ||||
| 	err = json.NewDecoder(response.Body).Decode(&mailList) | ||||
| 	if err != nil { | ||||
| 		return email, err | ||||
| 	} | ||||
| 	for _, v := range mailList { | ||||
| 		if v.Primary && v.Verified { | ||||
| 			return v.Email, nil | ||||
| 		} | ||||
| 	} | ||||
| 	// can't get primary email - shouldn't be possible | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func newConfig(provider *Provider, scopes []string) *oauth2.Config { | ||||
| 	c := &oauth2.Config{ | ||||
| 		ClientID:     provider.ClientKey, | ||||
| 		ClientSecret: provider.Secret, | ||||
| 		RedirectURL:  provider.CallbackURL, | ||||
| 		Endpoint: oauth2.Endpoint{ | ||||
| 			AuthURL:  AuthURL, | ||||
| 			TokenURL: TokenURL, | ||||
| 		}, | ||||
| 		Scopes: []string{}, | ||||
| 	} | ||||
|  | ||||
| 	for _, scope := range scopes { | ||||
| 		c.Scopes = append(c.Scopes, scope) | ||||
| 	} | ||||
|  | ||||
| 	return c | ||||
| } | ||||
|  | ||||
| //RefreshToken refresh token is not provided by github | ||||
| func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { | ||||
| 	return nil, errors.New("Refresh token is not provided by github") | ||||
| } | ||||
|  | ||||
| //RefreshTokenAvailable refresh token is not provided by github | ||||
| func (p *Provider) RefreshTokenAvailable() bool { | ||||
| 	return false | ||||
| } | ||||
							
								
								
									
										56
									
								
								vendor/github.com/markbates/goth/providers/github/session.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								vendor/github.com/markbates/goth/providers/github/session.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| package github | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"errors" | ||||
| 	"strings" | ||||
|  | ||||
| 	"github.com/markbates/goth" | ||||
| ) | ||||
|  | ||||
| // Session stores data during the auth process with Github. | ||||
| type Session struct { | ||||
| 	AuthURL     string | ||||
| 	AccessToken string | ||||
| } | ||||
|  | ||||
| // GetAuthURL will return the URL set by calling the `BeginAuth` function on the Github provider. | ||||
| func (s Session) GetAuthURL() (string, error) { | ||||
| 	if s.AuthURL == "" { | ||||
| 		return "", errors.New(goth.NoAuthUrlErrorMessage) | ||||
| 	} | ||||
| 	return s.AuthURL, nil | ||||
| } | ||||
|  | ||||
| // Authorize the session with Github and return the access token to be stored for future use. | ||||
| func (s *Session) Authorize(provider goth.Provider, params goth.Params) (string, error) { | ||||
| 	p := provider.(*Provider) | ||||
| 	token, err := p.config.Exchange(goth.ContextForClient(p.Client()), params.Get("code")) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	if !token.Valid() { | ||||
| 		return "", errors.New("Invalid token received from provider") | ||||
| 	} | ||||
|  | ||||
| 	s.AccessToken = token.AccessToken | ||||
| 	return token.AccessToken, err | ||||
| } | ||||
|  | ||||
| // Marshal the session into a string | ||||
| func (s Session) Marshal() string { | ||||
| 	b, _ := json.Marshal(s) | ||||
| 	return string(b) | ||||
| } | ||||
|  | ||||
| func (s Session) String() string { | ||||
| 	return s.Marshal() | ||||
| } | ||||
|  | ||||
| // UnmarshalSession will unmarshal a JSON string into a session. | ||||
| func (p *Provider) UnmarshalSession(data string) (goth.Session, error) { | ||||
| 	sess := &Session{} | ||||
| 	err := json.NewDecoder(strings.NewReader(data)).Decode(sess) | ||||
| 	return sess, err | ||||
| } | ||||
							
								
								
									
										21
									
								
								vendor/github.com/markbates/goth/session.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/markbates/goth/session.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| package goth | ||||
|  | ||||
| // Params is used to pass data to sessions for authorization. An existing | ||||
| // implementation, and the one most likely to be used, is `url.Values`. | ||||
| type Params interface { | ||||
| 	Get(string) string | ||||
| } | ||||
|  | ||||
| // Session needs to be implemented as part of the provider package. | ||||
| // It will be marshaled and persisted between requests to "tie" | ||||
| // the start and the end of the authorization process with a | ||||
| // 3rd party provider. | ||||
| type Session interface { | ||||
| 	// GetAuthURL returns the URL for the authentication end-point for the provider. | ||||
| 	GetAuthURL() (string, error) | ||||
| 	// Marshal generates a string representation of the Session for storing between requests. | ||||
| 	Marshal() string | ||||
| 	// Authorize should validate the data from the provider and return an access token | ||||
| 	// that can be stored for later access to the provider. | ||||
| 	Authorize(Provider, Params) (string, error) | ||||
| } | ||||
							
								
								
									
										30
									
								
								vendor/github.com/markbates/goth/user.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								vendor/github.com/markbates/goth/user.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| package goth | ||||
|  | ||||
| import ( | ||||
| 	"encoding/gob" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	gob.Register(User{}) | ||||
| } | ||||
|  | ||||
| // User contains the information common amongst most OAuth and OAuth2 providers. | ||||
| // All of the "raw" datafrom the provider can be found in the `RawData` field. | ||||
| type User struct { | ||||
| 	RawData           map[string]interface{} | ||||
| 	Provider          string | ||||
| 	Email             string | ||||
| 	Name              string | ||||
| 	FirstName         string | ||||
| 	LastName          string | ||||
| 	NickName          string | ||||
| 	Description       string | ||||
| 	UserID            string | ||||
| 	AvatarURL         string | ||||
| 	Location          string | ||||
| 	AccessToken       string | ||||
| 	AccessTokenSecret string | ||||
| 	RefreshToken      string | ||||
| 	ExpiresAt         time.Time | ||||
| } | ||||
		Reference in New Issue
	
	Block a user