From 7b297808cee599f5ff58b9f6afa64d3ee980998e Mon Sep 17 00:00:00 2001 From: Lauris BH Date: Mon, 19 Feb 2018 07:10:51 +0200 Subject: [PATCH] Update markbates/goth library (#3533) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Lauris Bukšis-Haberkorns --- vendor/github.com/markbates/goth/README.md | 97 +++++----- .../markbates/goth/gothic/gothic.go | 168 +++++++++++++++--- .../goth/providers/bitbucket/bitbucket.go | 12 +- .../goth/providers/dropbox/dropbox.go | 21 +-- .../goth/providers/facebook/facebook.go | 16 +- .../markbates/goth/providers/gitlab/gitlab.go | 2 +- .../markbates/goth/providers/gplus/gplus.go | 10 +- .../providers/openidConnect/openidConnect.go | 32 ++-- .../goth/providers/openidConnect/session.go | 4 +- .../goth/providers/twitter/twitter.go | 24 +-- vendor/vendor.json | 58 +++--- 11 files changed, 284 insertions(+), 160 deletions(-) diff --git a/vendor/github.com/markbates/goth/README.md b/vendor/github.com/markbates/goth/README.md index b481683bcc..05b19fce5a 100644 --- a/vendor/github.com/markbates/goth/README.md +++ b/vendor/github.com/markbates/goth/README.md @@ -8,6 +8,10 @@ protocol providers, as long as they implement the `Provider` and `Session` inter This package was inspired by [https://github.com/intridea/omniauth](https://github.com/intridea/omniauth). +## Goth Needs a New Maintainer + +[https://blog.gobuffalo.io/goth-needs-a-new-maintainer-626cd47ca37b](https://blog.gobuffalo.io/goth-needs-a-new-maintainer-626cd47ca37b) - TL;DR: I, @markbates, won't be responding to any more issues, PRs, etc... for this package. A new maintainer needs to be found ASAP. Is this you? + ## Installation ```text @@ -18,6 +22,8 @@ $ go get github.com/markbates/goth * Amazon * Auth0 +* Azure AD +* Battle.net * Bitbucket * Box * Cloud Foundry @@ -26,6 +32,7 @@ $ go get github.com/markbates/goth * Digital Ocean * Discord * Dropbox +* Eve Online * Facebook * Fitbit * GitHub @@ -38,6 +45,7 @@ $ go get github.com/markbates/goth * Lastfm * Linkedin * Meetup +* MicrosoftOnline * OneDrive * OpenID Connect (auto discovery) * Paypal @@ -50,7 +58,9 @@ $ go get github.com/markbates/goth * Twitch * Twitter * Uber +* VK * Wepay +* Xero * Yahoo * Yammer @@ -71,17 +81,51 @@ $ go get github.com/markbates/goth ```text $ cd goth/examples $ go get -v -$ go build +$ 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 +To actually use the different providers, please make sure you set environment variables. Example given in the examples/main.go file + +## Security Notes + +By default, gothic uses a `CookieStore` from the `gorilla/sessions` package to store session data. + +As configured, this default store (`gothic.Store`) will generate cookies with `Options`: + +```go +&Options{ + Path: "/", + Domain: "", + MaxAge: 86400 * 30, + HttpOnly: true, + Secure: false, + } +``` + +To tailor these fields for your application, you can override the `gothic.Store` variable at startup. + +The follow snippet show one way to do this: + +```go +key := "" // Replace with your SESSION_SECRET or similar +maxAge := 86400 * 30 // 30 days +isProd := false // Set to true when serving over https + +store := sessions.NewCookieStore([]byte(key)) +store.MaxAge(maxAge) +store.Options.Path = "/" +store.Options.HttpOnly = true // HttpOnly should always be enabled +store.Options.Secure = isProd + +gothic.Store = store +``` ## Issues -Issues always stand a significantly better chance of getting fixed if the are accompanied by a +Issues always stand a significantly better chance of getting fixed if they are accompanied by a pull request. ## Contributing @@ -94,50 +138,3 @@ Would I love to see more providers? Certainly! Would you love to contribute one? 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 diff --git a/vendor/github.com/markbates/goth/gothic/gothic.go b/vendor/github.com/markbates/goth/gothic/gothic.go index f6aaf2d117..8b8e114940 100644 --- a/vendor/github.com/markbates/goth/gothic/gothic.go +++ b/vendor/github.com/markbates/goth/gothic/gothic.go @@ -8,10 +8,18 @@ See https://github.com/markbates/goth/examples/main.go to see this in action. package gothic import ( + "bytes" + "compress/gzip" + "encoding/base64" "errors" "fmt" + "io/ioutil" + "math/rand" "net/http" + "net/url" "os" + "strings" + "time" "github.com/gorilla/mux" "github.com/gorilla/sessions" @@ -27,15 +35,21 @@ var defaultStore sessions.Store var keySet = false +var gothicRand *rand.Rand + func init() { key := []byte(os.Getenv("SESSION_SECRET")) keySet = len(key) != 0 - Store = sessions.NewCookieStore([]byte(key)) + + cookieStore := sessions.NewCookieStore([]byte(key)) + cookieStore.Options.HttpOnly = true + Store = cookieStore defaultStore = Store + gothicRand = rand.New(rand.NewSource(time.Now().UnixNano())) } /* -BeginAuthHandler is a convienence handler for starting the authentication process. +BeginAuthHandler is a convenience 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". @@ -65,8 +79,16 @@ var SetState = func(req *http.Request) string { return state } - return "state" - + // If a state query param is not passed in, generate a random + // base64-encoded nonce so that the state on the auth URL + // is unguessable, preventing CSRF attacks, as described in + // + // https://auth0.com/docs/protocols/oauth2/oauth-state#keep-reading + nonceBytes := make([]byte, 64) + for i := 0; i < 64; i++ { + nonceBytes[i] = byte(gothicRand.Int63() % 256) + } + return base64.URLEncoding.EncodeToString(nonceBytes) } // GetState gets the state returned by the provider during the callback. @@ -87,7 +109,6 @@ 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.") } @@ -130,7 +151,7 @@ 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) { - + defer Logout(res, req) 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.") } @@ -155,6 +176,11 @@ var CompleteUserAuth = func(res http.ResponseWriter, req *http.Request) (goth.Us return goth.User{}, err } + err = validateState(req, sess) + if err != nil { + return goth.User{}, err + } + user, err := provider.FetchUser(sess) if err == nil { // user can be found with existing session data @@ -173,7 +199,43 @@ var CompleteUserAuth = func(res http.ResponseWriter, req *http.Request) (goth.Us return goth.User{}, err } - return provider.FetchUser(sess) + gu, err := provider.FetchUser(sess) + return gu, err +} + +// validateState ensures that the state token param from the original +// AuthURL matches the one included in the current (callback) request. +func validateState(req *http.Request, sess goth.Session) error { + rawAuthURL, err := sess.GetAuthURL() + if err != nil { + return err + } + + authURL, err := url.Parse(rawAuthURL) + if err != nil { + return err + } + + originalState := authURL.Query().Get("state") + if originalState != "" && (originalState != req.URL.Query().Get("state")) { + return errors.New("state token mismatch") + } + return nil +} + +// Logout invalidates a user session. +func Logout(res http.ResponseWriter, req *http.Request) error { + session, err := Store.Get(req, SessionName) + if err != nil { + return err + } + session.Options.MaxAge = -1 + session.Values = make(map[interface{}]interface{}) + err = session.Save(req, res) + if err != nil { + return errors.New("Could not delete user session ") + } + return nil } // GetProviderName is a function used to get the name of a provider @@ -184,36 +246,96 @@ var CompleteUserAuth = func(res http.ResponseWriter, req *http.Request) (goth.Us 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 { + + // get all the used providers + providers := goth.GetProviders() + + // loop over the used providers, if we already have a valid session for any provider (ie. user is already logged-in with a provider), then return that provider name + for _, provider := range providers { + p := provider.Name() + session, _ := Store.Get(req, p+SessionName) + value := session.Values[p] + if _, ok := value.(string); ok { return p, nil } } - if provider == "" { - provider = req.URL.Query().Get(":provider") + + // try to get it from the url param "provider" + if p := req.URL.Query().Get("provider"); p != "" { + return p, nil } - if provider == "" { - return provider, errors.New("you must select a provider") + + // try to get it from the url param ":provider" + if p := req.URL.Query().Get(":provider"); p != "" { + return p, nil } - return provider, nil + + // try to get it from the context's value of "provider" key + if p, ok := mux.Vars(req)["provider"]; ok { + return p, nil + } + + // try to get it from the go-context's value of "provider" key + if p, ok := req.Context().Value("provider").(string); ok { + return p, nil + } + + // if not found then return an empty string with the corresponding error + return "", errors.New("you must select a provider") } func storeInSession(key string, value string, req *http.Request, res http.ResponseWriter) error { - session, _ := Store.Get(req, key + SessionName) + session, _ := Store.Get(req, SessionName) - session.Values[key] = value + if err := updateSessionValue(session, key, value); err != nil { + return err + } 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 { + session, _ := Store.Get(req, SessionName) + value, err := getSessionValue(session, key) + if err != nil { return "", errors.New("could not find a matching session for this request") } - return value.(string), nil -} \ No newline at end of file + return value, nil +} + +func getSessionValue(session *sessions.Session, key string) (string, error) { + value := session.Values[key] + if value == nil { + return "", fmt.Errorf("could not find a matching session for this request") + } + + rdata := strings.NewReader(value.(string)) + r, err := gzip.NewReader(rdata) + if err != nil { + return "", err + } + s, err := ioutil.ReadAll(r) + if err != nil { + return "", err + } + + return string(s), nil +} + +func updateSessionValue(session *sessions.Session, key, value string) error { + var b bytes.Buffer + gz := gzip.NewWriter(&b) + if _, err := gz.Write([]byte(value)); err != nil { + return err + } + if err := gz.Flush(); err != nil { + return err + } + if err := gz.Close(); err != nil { + return err + } + + session.Values[key] = b.String() + return nil +} diff --git a/vendor/github.com/markbates/goth/providers/bitbucket/bitbucket.go b/vendor/github.com/markbates/goth/providers/bitbucket/bitbucket.go index 06d9c923cb..c19734770d 100644 --- a/vendor/github.com/markbates/goth/providers/bitbucket/bitbucket.go +++ b/vendor/github.com/markbates/goth/providers/bitbucket/bitbucket.go @@ -9,9 +9,9 @@ import ( "net/http" "net/url" + "fmt" "github.com/markbates/goth" "golang.org/x/oauth2" - "fmt" ) const ( @@ -26,10 +26,10 @@ const ( // one manually. func New(clientKey, secret, callbackURL string, scopes ...string) *Provider { p := &Provider{ - ClientKey: clientKey, - Secret: secret, - CallbackURL: callbackURL, - providerName: "bitbucket", + ClientKey: clientKey, + Secret: secret, + CallbackURL: callbackURL, + providerName: "bitbucket", } p.config = newConfig(p, scopes) return p @@ -125,7 +125,7 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { func userFromReader(reader io.Reader, user *goth.User) error { u := struct { - ID string `json:"uuid"` + ID string `json:"uuid"` Links struct { Avatar struct { URL string `json:"href"` diff --git a/vendor/github.com/markbates/goth/providers/dropbox/dropbox.go b/vendor/github.com/markbates/goth/providers/dropbox/dropbox.go index 61533d405e..262905806b 100644 --- a/vendor/github.com/markbates/goth/providers/dropbox/dropbox.go +++ b/vendor/github.com/markbates/goth/providers/dropbox/dropbox.go @@ -8,15 +8,16 @@ import ( "net/http" "strings" + "fmt" + "github.com/markbates/goth" "golang.org/x/oauth2" - "fmt" ) const ( - authURL = "https://www.dropbox.com/1/oauth2/authorize" - tokenURL = "https://api.dropbox.com/1/oauth2/token" - accountURL = "https://api.dropbox.com/1/account/info" + authURL = "https://www.dropbox.com/oauth2/authorize" + tokenURL = "https://api.dropbox.com/oauth2/token" + accountURL = "https://api.dropbox.com/2/users/get_current_account" ) // Provider is the implementation of `goth.Provider` for accessing Dropbox. @@ -40,10 +41,10 @@ type Session struct { // create one manually. func New(clientKey, secret, callbackURL string, scopes ...string) *Provider { p := &Provider{ - ClientKey: clientKey, - Secret: secret, - CallbackURL: callbackURL, - providerName: "dropbox", + ClientKey: clientKey, + Secret: secret, + CallbackURL: callbackURL, + providerName: "dropbox", } p.config = newConfig(p, scopes) return p @@ -86,7 +87,7 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { return user, fmt.Errorf("%s cannot get user information without accessToken", p.providerName) } - req, err := http.NewRequest("GET", accountURL, nil) + req, err := http.NewRequest("POST", accountURL, nil) if err != nil { return user, err } @@ -161,7 +162,7 @@ func newConfig(p *Provider, scopes []string) *oauth2.Config { func userFromReader(r io.Reader, user *goth.User) error { u := struct { - Name string `json:"display_name"` + Name string `json:"display_name"` NameDetails struct { NickName string `json:"familiar_name"` } `json:"name_details"` diff --git a/vendor/github.com/markbates/goth/providers/facebook/facebook.go b/vendor/github.com/markbates/goth/providers/facebook/facebook.go index e0cfdf1e34..266bbe2208 100644 --- a/vendor/github.com/markbates/goth/providers/facebook/facebook.go +++ b/vendor/github.com/markbates/goth/providers/facebook/facebook.go @@ -11,12 +11,12 @@ import ( "net/http" "net/url" - "github.com/markbates/goth" - "golang.org/x/oauth2" - "fmt" "crypto/hmac" "crypto/sha256" "encoding/hex" + "fmt" + "github.com/markbates/goth" + "golang.org/x/oauth2" ) const ( @@ -30,10 +30,10 @@ const ( // one manually. func New(clientKey, secret, callbackURL string, scopes ...string) *Provider { p := &Provider{ - ClientKey: clientKey, - Secret: secret, - CallbackURL: callbackURL, - providerName: "facebook", + ClientKey: clientKey, + Secret: secret, + CallbackURL: callbackURL, + providerName: "facebook", } p.config = newConfig(p, scopes) return p @@ -129,7 +129,7 @@ func userFromReader(reader io.Reader, user *goth.User) error { FirstName string `json:"first_name"` LastName string `json:"last_name"` Link string `json:"link"` - Picture struct { + Picture struct { Data struct { URL string `json:"url"` } `json:"data"` diff --git a/vendor/github.com/markbates/goth/providers/gitlab/gitlab.go b/vendor/github.com/markbates/goth/providers/gitlab/gitlab.go index fe188c01a9..533632e7fd 100644 --- a/vendor/github.com/markbates/goth/providers/gitlab/gitlab.go +++ b/vendor/github.com/markbates/goth/providers/gitlab/gitlab.go @@ -11,9 +11,9 @@ import ( "net/url" "strconv" + "fmt" "github.com/markbates/goth" "golang.org/x/oauth2" - "fmt" ) // These vars define the Authentication, Token, and Profile URLS for Gitlab. If diff --git a/vendor/github.com/markbates/goth/providers/gplus/gplus.go b/vendor/github.com/markbates/goth/providers/gplus/gplus.go index 06655c2f7f..4726aa349e 100644 --- a/vendor/github.com/markbates/goth/providers/gplus/gplus.go +++ b/vendor/github.com/markbates/goth/providers/gplus/gplus.go @@ -11,9 +11,9 @@ import ( "net/url" "strings" + "fmt" "github.com/markbates/goth" "golang.org/x/oauth2" - "fmt" ) const ( @@ -27,10 +27,10 @@ const ( // one manually. func New(clientKey, secret, callbackURL string, scopes ...string) *Provider { p := &Provider{ - ClientKey: clientKey, - Secret: secret, - CallbackURL: callbackURL, - providerName: "gplus", + ClientKey: clientKey, + Secret: secret, + CallbackURL: callbackURL, + providerName: "gplus", } p.config = newConfig(p, scopes) return p diff --git a/vendor/github.com/markbates/goth/providers/openidConnect/openidConnect.go b/vendor/github.com/markbates/goth/providers/openidConnect/openidConnect.go index 7ffd11c607..44419ba15f 100644 --- a/vendor/github.com/markbates/goth/providers/openidConnect/openidConnect.go +++ b/vendor/github.com/markbates/goth/providers/openidConnect/openidConnect.go @@ -1,17 +1,17 @@ package openidConnect import ( + "bytes" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "github.com/markbates/goth" + "golang.org/x/oauth2" + "io/ioutil" "net/http" "strings" - "fmt" - "encoding/json" - "encoding/base64" - "io/ioutil" - "errors" - "golang.org/x/oauth2" - "github.com/markbates/goth" "time" - "bytes" ) const ( @@ -89,14 +89,14 @@ func New(clientKey, secret, callbackURL, openIDAutoDiscoveryURL string, scopes . Secret: secret, CallbackURL: callbackURL, - UserIdClaims: []string{subjectClaim}, - NameClaims: []string{NameClaim}, - NickNameClaims: []string{NicknameClaim, PreferredUsernameClaim}, - EmailClaims: []string{EmailClaim}, - AvatarURLClaims:[]string{PictureClaim}, - FirstNameClaims:[]string{GivenNameClaim}, - LastNameClaims: []string{FamilyNameClaim}, - LocationClaims: []string{AddressClaim}, + UserIdClaims: []string{subjectClaim}, + NameClaims: []string{NameClaim}, + NickNameClaims: []string{NicknameClaim, PreferredUsernameClaim}, + EmailClaims: []string{EmailClaim}, + AvatarURLClaims: []string{PictureClaim}, + FirstNameClaims: []string{GivenNameClaim}, + LastNameClaims: []string{FamilyNameClaim}, + LocationClaims: []string{AddressClaim}, providerName: "openid-connect", } diff --git a/vendor/github.com/markbates/goth/providers/openidConnect/session.go b/vendor/github.com/markbates/goth/providers/openidConnect/session.go index a34584fdef..d223cf8752 100644 --- a/vendor/github.com/markbates/goth/providers/openidConnect/session.go +++ b/vendor/github.com/markbates/goth/providers/openidConnect/session.go @@ -1,12 +1,12 @@ package openidConnect import ( + "encoding/json" "errors" "github.com/markbates/goth" - "encoding/json" + "golang.org/x/oauth2" "strings" "time" - "golang.org/x/oauth2" ) // Session stores data during the auth process with the OpenID Connect provider. diff --git a/vendor/github.com/markbates/goth/providers/twitter/twitter.go b/vendor/github.com/markbates/goth/providers/twitter/twitter.go index 3703f21974..2ce20e780e 100644 --- a/vendor/github.com/markbates/goth/providers/twitter/twitter.go +++ b/vendor/github.com/markbates/goth/providers/twitter/twitter.go @@ -9,10 +9,11 @@ import ( "io/ioutil" "net/http" + "fmt" + "github.com/markbates/goth" "github.com/mrjones/oauth" "golang.org/x/oauth2" - "fmt" ) var ( @@ -30,10 +31,10 @@ var ( // If you'd like to use authenticate instead of authorize, use NewAuthenticate instead. func New(clientKey, secret, callbackURL string) *Provider { p := &Provider{ - ClientKey: clientKey, - Secret: secret, - CallbackURL: callbackURL, - providerName: "twitter", + ClientKey: clientKey, + Secret: secret, + CallbackURL: callbackURL, + providerName: "twitter", } p.consumer = newConsumer(p, authorizeURL) return p @@ -43,10 +44,10 @@ func New(clientKey, secret, callbackURL string) *Provider { // NewAuthenticate uses the authenticate URL instead of the authorize URL. func NewAuthenticate(clientKey, secret, callbackURL string) *Provider { p := &Provider{ - ClientKey: clientKey, - Secret: secret, - CallbackURL: callbackURL, - providerName: "twitter", + ClientKey: clientKey, + Secret: secret, + CallbackURL: callbackURL, + providerName: "twitter", } p.consumer = newConsumer(p, authenticateURL) return p @@ -107,7 +108,7 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { response, err := p.consumer.Get( endpointProfile, - map[string]string{"include_entities": "false", "skip_status": "true"}, + map[string]string{"include_entities": "false", "skip_status": "true", "include_email": "true"}, sess.AccessToken) if err != nil { return user, err @@ -126,6 +127,9 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { user.Name = user.RawData["name"].(string) user.NickName = user.RawData["screen_name"].(string) + if user.RawData["email"] != nil { + user.Email = user.RawData["email"].(string) + } user.Description = user.RawData["description"].(string) user.AvatarURL = user.RawData["profile_image_url"].(string) user.UserID = user.RawData["id_str"].(string) diff --git a/vendor/vendor.json b/vendor/vendor.json index 7397cac40a..c6a10a585b 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -666,64 +666,64 @@ "revisionTime": "2017-10-25T03:15:54Z" }, { - "checksumSHA1": "O3KUfEXQPfdQ+tCMpP2RAIRJJqY=", + "checksumSHA1": "q9MD1ienC+kmKq5i51oAktQEV1E=", "path": "github.com/markbates/goth", - "revision": "90362394a367f9d77730911973462a53d69662ba", - "revisionTime": "2017-02-23T14:12:10Z" + "revision": "bc7deaf077a50416cf3a23aa5903d2a7b5a30ada", + "revisionTime": "2018-02-15T02:27:40Z" }, { - "checksumSHA1": "MkFKwLV3icyUo4oP0BgEs+7+R1Y=", + "checksumSHA1": "+nosptSgGb2qCAR6CSHV2avwmNg=", "path": "github.com/markbates/goth/gothic", - "revision": "90362394a367f9d77730911973462a53d69662ba", - "revisionTime": "2017-02-23T14:12:10Z" + "revision": "bc7deaf077a50416cf3a23aa5903d2a7b5a30ada", + "revisionTime": "2018-02-15T02:27:40Z" }, { - "checksumSHA1": "crNSlQADjX6hcxykON2tFCqY4iw=", + "checksumSHA1": "pJ+Cws/TU22K6tZ/ALFOvvH1K5U=", "path": "github.com/markbates/goth/providers/bitbucket", - "revision": "90362394a367f9d77730911973462a53d69662ba", - "revisionTime": "2017-02-23T14:12:10Z" + "revision": "bc7deaf077a50416cf3a23aa5903d2a7b5a30ada", + "revisionTime": "2018-02-15T02:27:40Z" }, { - "checksumSHA1": "1Kp4DKkJNVn135Xg8H4a6CFBNy8=", + "checksumSHA1": "bKokLof0Pkk5nEhW8NdbfcVzuqk=", "path": "github.com/markbates/goth/providers/dropbox", - "revision": "90362394a367f9d77730911973462a53d69662ba", - "revisionTime": "2017-02-23T14:12:10Z" + "revision": "bc7deaf077a50416cf3a23aa5903d2a7b5a30ada", + "revisionTime": "2018-02-15T02:27:40Z" }, { - "checksumSHA1": "cGs1da29iOBJh5EAH0icKDbN8CA=", + "checksumSHA1": "VzbroIA9R00Ig3iGnOlZLU7d4ls=", "path": "github.com/markbates/goth/providers/facebook", - "revision": "90362394a367f9d77730911973462a53d69662ba", - "revisionTime": "2017-02-23T14:12:10Z" + "revision": "bc7deaf077a50416cf3a23aa5903d2a7b5a30ada", + "revisionTime": "2018-02-15T02:27:40Z" }, { "checksumSHA1": "P6nBZ850aaekpOcoXNdRhK86bH8=", "path": "github.com/markbates/goth/providers/github", - "revision": "90362394a367f9d77730911973462a53d69662ba", - "revisionTime": "2017-02-23T14:12:10Z" + "revision": "bc7deaf077a50416cf3a23aa5903d2a7b5a30ada", + "revisionTime": "2018-02-15T02:27:40Z" }, { - "checksumSHA1": "o/109paSRy9HqV87gR4zUZMMSzs=", + "checksumSHA1": "ld488t+yGoTwtmiCSSggEX4fxVk=", "path": "github.com/markbates/goth/providers/gitlab", - "revision": "90362394a367f9d77730911973462a53d69662ba", - "revisionTime": "2017-02-23T14:12:10Z" + "revision": "bc7deaf077a50416cf3a23aa5903d2a7b5a30ada", + "revisionTime": "2018-02-15T02:27:40Z" }, { - "checksumSHA1": "cX6kR9y94BWFZvI/7UFrsFsP3FQ=", + "checksumSHA1": "qXEulD7vnwY9hFrxh91Pm5YrvTM=", "path": "github.com/markbates/goth/providers/gplus", - "revision": "90362394a367f9d77730911973462a53d69662ba", - "revisionTime": "2017-02-23T14:12:10Z" + "revision": "bc7deaf077a50416cf3a23aa5903d2a7b5a30ada", + "revisionTime": "2018-02-15T02:27:40Z" }, { - "checksumSHA1": "sMYKhqAUZXM1+T/TjlMhWh8Vveo=", + "checksumSHA1": "wsOBzyp4LKDhfCPmX1LLP7T0S3U=", "path": "github.com/markbates/goth/providers/openidConnect", - "revision": "90362394a367f9d77730911973462a53d69662ba", - "revisionTime": "2017-02-23T14:12:10Z" + "revision": "bc7deaf077a50416cf3a23aa5903d2a7b5a30ada", + "revisionTime": "2018-02-15T02:27:40Z" }, { - "checksumSHA1": "1w0V6jYXaGlEtZcMeYTOAAucvgw=", + "checksumSHA1": "o6RqMbbE8QNZhNT9TsAIRMPI8tg=", "path": "github.com/markbates/goth/providers/twitter", - "revision": "90362394a367f9d77730911973462a53d69662ba", - "revisionTime": "2017-02-23T14:12:10Z" + "revision": "bc7deaf077a50416cf3a23aa5903d2a7b5a30ada", + "revisionTime": "2018-02-15T02:27:40Z" }, { "checksumSHA1": "61HNjGetaBoMp8HBOpuEZRSim8g=",