diff options
Diffstat (limited to 'vendor/github.com/markbates/goth/providers')
14 files changed, 1899 insertions, 11 deletions
diff --git a/vendor/github.com/markbates/goth/providers/bitbucket/bitbucket.go b/vendor/github.com/markbates/goth/providers/bitbucket/bitbucket.go new file mode 100644 index 0000000000..06d9c923cb --- /dev/null +++ b/vendor/github.com/markbates/goth/providers/bitbucket/bitbucket.go @@ -0,0 +1,206 @@ +// Package bitbucket implements the OAuth2 protocol for authenticating users through Bitbucket. +package bitbucket + +import ( + "bytes" + "encoding/json" + "io" + "io/ioutil" + "net/http" + "net/url" + + "github.com/markbates/goth" + "golang.org/x/oauth2" + "fmt" +) + +const ( + authURL string = "https://bitbucket.org/site/oauth2/authorize" + tokenURL string = "https://bitbucket.org/site/oauth2/access_token" + endpointProfile string = "https://api.bitbucket.org/2.0/user" + endpointEmail string = "https://api.bitbucket.org/2.0/user/emails" +) + +// New creates a new Bitbucket provider, and sets up important connection details. +// You should always call `bitbucket.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: "bitbucket", + } + p.config = newConfig(p, scopes) + return p +} + +// Provider is the implementation of `goth.Provider` for accessing Bitbucket. +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 bitbucket package. +func (p *Provider) Debug(debug bool) {} + +// BeginAuth asks Bitbucket 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 Bitbucket 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(), + RefreshToken: sess.RefreshToken, + ExpiresAt: sess.ExpiresAt, + } + + 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 := goth.HTTPClientWithFallBack(p.Client()).Get(endpointProfile + "?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("%s responded with a %d trying to fetch user information", p.providerName, 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) + + response, err = goth.HTTPClientWithFallBack(p.Client()).Get(endpointEmail + "?access_token=" + url.QueryEscape(sess.AccessToken)) + if err != nil { + return user, err + } + defer response.Body.Close() + + bits, err = ioutil.ReadAll(response.Body) + if err != nil { + return user, err + } + + err = emailFromReader(bytes.NewReader(bits), &user) + return user, err +} + +func userFromReader(reader io.Reader, user *goth.User) error { + u := struct { + ID string `json:"uuid"` + Links struct { + Avatar struct { + URL string `json:"href"` + } `json:"avatar"` + } `json:"links"` + Email string `json:"email"` + Username string `json:"username"` + Name string `json:"display_name"` + Location string `json:"location"` + }{} + + err := json.NewDecoder(reader).Decode(&u) + if err != nil { + return err + } + + user.Name = u.Name + user.NickName = u.Username + user.AvatarURL = u.Links.Avatar.URL + user.UserID = u.ID + user.Location = u.Location + + return err +} + +func emailFromReader(reader io.Reader, user *goth.User) error { + e := struct { + Values []struct { + Email string `json:"email"` + } `json:"values"` + }{} + + err := json.NewDecoder(reader).Decode(&e) + if err != nil { + return err + } + + if len(e.Values) > 0 { + user.Email = e.Values[0].Email + } + + return err +} + +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 +} + +//RefreshTokenAvailable refresh token is provided by auth provider or not +func (p *Provider) RefreshTokenAvailable() bool { + return true +} + +//RefreshToken get new access token based on the refresh token +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + token := &oauth2.Token{RefreshToken: refreshToken} + ts := p.config.TokenSource(goth.ContextForClient(p.Client()), token) + newToken, err := ts.Token() + if err != nil { + return nil, err + } + return newToken, err +} diff --git a/vendor/github.com/markbates/goth/providers/bitbucket/session.go b/vendor/github.com/markbates/goth/providers/bitbucket/session.go new file mode 100644 index 0000000000..a65242151b --- /dev/null +++ b/vendor/github.com/markbates/goth/providers/bitbucket/session.go @@ -0,0 +1,61 @@ +package bitbucket + +import ( + "encoding/json" + "errors" + "strings" + "time" + + "github.com/markbates/goth" +) + +// Session stores data during the auth process with Bitbucket. +type Session struct { + AuthURL string + AccessToken string + RefreshToken string + ExpiresAt time.Time +} + +// GetAuthURL will return the URL set by calling the `BeginAuth` function on the Bitbucket provider. +func (s Session) GetAuthURL() (string, error) { + if s.AuthURL == "" { + return "", errors.New(goth.NoAuthUrlErrorMessage) + } + return s.AuthURL, nil +} + +// Authorize the session with Bitbucket 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 + s.RefreshToken = token.RefreshToken + s.ExpiresAt = token.Expiry + return token.AccessToken, err +} + +// Marshal the session into a string +func (s Session) Marshal() string { + b, _ := json.Marshal(s) + return string(b) +} + +// 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 +} + +func (s Session) String() string { + return s.Marshal() +} diff --git a/vendor/github.com/markbates/goth/providers/dropbox/dropbox.go b/vendor/github.com/markbates/goth/providers/dropbox/dropbox.go new file mode 100644 index 0000000000..61533d405e --- /dev/null +++ b/vendor/github.com/markbates/goth/providers/dropbox/dropbox.go @@ -0,0 +1,191 @@ +// Package dropbox implements the OAuth2 protocol for authenticating users through Dropbox. +package dropbox + +import ( + "encoding/json" + "errors" + "io" + "net/http" + "strings" + + "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" +) + +// Provider is the implementation of `goth.Provider` for accessing Dropbox. +type Provider struct { + ClientKey string + Secret string + CallbackURL string + HTTPClient *http.Client + config *oauth2.Config + providerName string +} + +// Session stores data during the auth process with Dropbox. +type Session struct { + AuthURL string + Token string +} + +// New creates a new Dropbox provider and sets up important connection details. +// You should always call `dropbox.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: "dropbox", + } + p.config = newConfig(p, scopes) + return p +} + +// 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 dropbox package. +func (p *Provider) Debug(debug bool) {} + +// BeginAuth asks Dropbox for an authentication end-point. +func (p *Provider) BeginAuth(state string) (goth.Session, error) { + return &Session{ + AuthURL: p.config.AuthCodeURL(state), + }, nil +} + +// FetchUser will go to Dropbox and access basic information about the user. +func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { + s := session.(*Session) + user := goth.User{ + AccessToken: s.Token, + 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) + } + + req, err := http.NewRequest("GET", accountURL, nil) + if err != nil { + return user, err + } + req.Header.Set("Authorization", "Bearer "+s.Token) + resp, err := p.Client().Do(req) + if err != nil { + return user, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return user, fmt.Errorf("%s responded with a %d trying to fetch user information", p.providerName, resp.StatusCode) + } + + err = userFromReader(resp.Body, &user) + return user, err +} + +// UnmarshalSession wil unmarshal a JSON string into a session. +func (p *Provider) UnmarshalSession(data string) (goth.Session, error) { + s := &Session{} + err := json.NewDecoder(strings.NewReader(data)).Decode(s) + return s, err +} + +// GetAuthURL gets the URL set by calling the `BeginAuth` function on the Dropbox provider. +func (s *Session) GetAuthURL() (string, error) { + if s.AuthURL == "" { + return "", errors.New("dropbox: missing AuthURL") + } + return s.AuthURL, nil +} + +// Authorize the session with Dropbox 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.Token = token.AccessToken + return token.AccessToken, nil +} + +// 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() +} + +func newConfig(p *Provider, scopes []string) *oauth2.Config { + c := &oauth2.Config{ + ClientID: p.ClientKey, + ClientSecret: p.Secret, + RedirectURL: p.CallbackURL, + Endpoint: oauth2.Endpoint{ + AuthURL: authURL, + TokenURL: tokenURL, + }, + } + return c +} + +func userFromReader(r io.Reader, user *goth.User) error { + u := struct { + Name string `json:"display_name"` + NameDetails struct { + NickName string `json:"familiar_name"` + } `json:"name_details"` + Location string `json:"country"` + Email string `json:"email"` + }{} + err := json.NewDecoder(r).Decode(&u) + if err != nil { + return err + } + user.Email = u.Email + user.Name = u.Name + user.NickName = u.NameDetails.NickName + user.UserID = u.Email // Dropbox doesn't provide a separate user ID + user.Location = u.Location + return nil +} + +//RefreshToken refresh token is not provided by dropbox +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + return nil, errors.New("Refresh token is not provided by dropbox") +} + +//RefreshTokenAvailable refresh token is not provided by dropbox +func (p *Provider) RefreshTokenAvailable() bool { + return false +} diff --git a/vendor/github.com/markbates/goth/providers/facebook/facebook.go b/vendor/github.com/markbates/goth/providers/facebook/facebook.go new file mode 100644 index 0000000000..e0cfdf1e34 --- /dev/null +++ b/vendor/github.com/markbates/goth/providers/facebook/facebook.go @@ -0,0 +1,195 @@ +// Package facebook implements the OAuth2 protocol for authenticating users through Facebook. +// This package can be used as a reference implementation of an OAuth2 provider for Goth. +package facebook + +import ( + "bytes" + "encoding/json" + "errors" + "io" + "io/ioutil" + "net/http" + "net/url" + + "github.com/markbates/goth" + "golang.org/x/oauth2" + "fmt" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" +) + +const ( + authURL string = "https://www.facebook.com/dialog/oauth" + tokenURL string = "https://graph.facebook.com/oauth/access_token" + endpointProfile string = "https://graph.facebook.com/me?fields=email,first_name,last_name,link,about,id,name,picture,location" +) + +// New creates a new Facebook provider, and sets up important connection details. +// You should always call `facebook.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: "facebook", + } + p.config = newConfig(p, scopes) + return p +} + +// Provider is the implementation of `goth.Provider` for accessing Facebook. +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 facebook package. +func (p *Provider) Debug(debug bool) {} + +// BeginAuth asks Facebook 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 Facebook 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(), + ExpiresAt: sess.ExpiresAt, + } + + 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) + } + + // always add appsecretProof to make calls more protected + // https://github.com/markbates/goth/issues/96 + // https://developers.facebook.com/docs/graph-api/securing-requests + hash := hmac.New(sha256.New, []byte(p.Secret)) + hash.Write([]byte(sess.AccessToken)) + appsecretProof := hex.EncodeToString(hash.Sum(nil)) + + response, err := p.Client().Get(endpointProfile + "&access_token=" + url.QueryEscape(sess.AccessToken) + "&appsecret_proof=" + appsecretProof) + if err != nil { + return user, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return user, fmt.Errorf("%s responded with a %d trying to fetch user information", p.providerName, 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) + return user, err +} + +func userFromReader(reader io.Reader, user *goth.User) error { + u := struct { + ID string `json:"id"` + Email string `json:"email"` + About string `json:"about"` + Name string `json:"name"` + FirstName string `json:"first_name"` + LastName string `json:"last_name"` + Link string `json:"link"` + Picture struct { + Data struct { + URL string `json:"url"` + } `json:"data"` + } `json:"picture"` + Location struct { + Name string `json:"name"` + } `json:"location"` + }{} + + err := json.NewDecoder(reader).Decode(&u) + if err != nil { + return err + } + + user.Name = u.Name + user.FirstName = u.FirstName + user.LastName = u.LastName + user.NickName = u.Name + user.Email = u.Email + user.Description = u.About + user.AvatarURL = u.Picture.Data.URL + user.UserID = u.ID + user.Location = u.Location.Name + + return err +} + +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{ + "email", + }, + } + + defaultScopes := map[string]struct{}{ + "email": {}, + } + + for _, scope := range scopes { + if _, exists := defaultScopes[scope]; !exists { + c.Scopes = append(c.Scopes, scope) + } + } + + return c +} + +//RefreshToken refresh token is not provided by facebook +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + return nil, errors.New("Refresh token is not provided by facebook") +} + +//RefreshTokenAvailable refresh token is not provided by facebook +func (p *Provider) RefreshTokenAvailable() bool { + return false +} diff --git a/vendor/github.com/markbates/goth/providers/facebook/session.go b/vendor/github.com/markbates/goth/providers/facebook/session.go new file mode 100644 index 0000000000..5cdcca443a --- /dev/null +++ b/vendor/github.com/markbates/goth/providers/facebook/session.go @@ -0,0 +1,59 @@ +package facebook + +import ( + "encoding/json" + "errors" + "strings" + "time" + + "github.com/markbates/goth" +) + +// Session stores data during the auth process with Facebook. +type Session struct { + AuthURL string + AccessToken string + ExpiresAt time.Time +} + +// GetAuthURL will return the URL set by calling the `BeginAuth` function on the Facebook provider. +func (s Session) GetAuthURL() (string, error) { + if s.AuthURL == "" { + return "", errors.New(goth.NoAuthUrlErrorMessage) + } + return s.AuthURL, nil +} + +// Authorize the session with Facebook 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 + s.ExpiresAt = token.Expiry + 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 +} diff --git a/vendor/github.com/markbates/goth/providers/github/github.go b/vendor/github.com/markbates/goth/providers/github/github.go index 866150e63a..b3c29b9670 100644 --- a/vendor/github.com/markbates/goth/providers/github/github.go +++ b/vendor/github.com/markbates/goth/providers/github/github.go @@ -37,13 +37,20 @@ var ( // 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 { + return NewCustomisedURL(clientKey, secret, callbackURL, AuthURL, TokenURL, ProfileURL, EmailURL, scopes...) +} + +// NewCustomisedURL is similar to New(...) but can be used to set custom URLs to connect to +func NewCustomisedURL(clientKey, secret, callbackURL, authURL, tokenURL, profileURL, emailURL string, scopes ...string) *Provider { p := &Provider{ - ClientKey: clientKey, - Secret: secret, - CallbackURL: callbackURL, - providerName: "github", - } - p.config = newConfig(p, scopes) + ClientKey: clientKey, + Secret: secret, + CallbackURL: callbackURL, + providerName: "github", + profileURL: profileURL, + emailURL: emailURL, + } + p.config = newConfig(p, authURL, tokenURL, scopes) return p } @@ -55,6 +62,8 @@ type Provider struct { HTTPClient *http.Client config *oauth2.Config providerName string + profileURL string + emailURL string } // Name is the name used to retrieve this provider later. @@ -96,7 +105,7 @@ func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { 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)) + response, err := p.Client().Get(p.profileURL + "?access_token=" + url.QueryEscape(sess.AccessToken)) if err != nil { return user, err } @@ -163,7 +172,7 @@ func userFromReader(reader io.Reader, user *goth.User) error { } func getPrivateMail(p *Provider, sess *Session) (email string, err error) { - response, err := p.Client().Get(EmailURL + "?access_token=" + url.QueryEscape(sess.AccessToken)) + response, err := p.Client().Get(p.emailURL + "?access_token=" + url.QueryEscape(sess.AccessToken)) if err != nil { if response != nil { response.Body.Close() @@ -194,14 +203,14 @@ func getPrivateMail(p *Provider, sess *Session) (email string, err error) { return } -func newConfig(provider *Provider, scopes []string) *oauth2.Config { +func newConfig(provider *Provider, authURL, tokenURL string, scopes []string) *oauth2.Config { c := &oauth2.Config{ ClientID: provider.ClientKey, ClientSecret: provider.Secret, RedirectURL: provider.CallbackURL, Endpoint: oauth2.Endpoint{ - AuthURL: AuthURL, - TokenURL: TokenURL, + AuthURL: authURL, + TokenURL: tokenURL, }, Scopes: []string{}, } diff --git a/vendor/github.com/markbates/goth/providers/gitlab/gitlab.go b/vendor/github.com/markbates/goth/providers/gitlab/gitlab.go new file mode 100644 index 0000000000..fe188c01a9 --- /dev/null +++ b/vendor/github.com/markbates/goth/providers/gitlab/gitlab.go @@ -0,0 +1,187 @@ +// Package gitlab implements the OAuth2 protocol for authenticating users through gitlab. +// This package can be used as a reference implementation of an OAuth2 provider for Goth. +package gitlab + +import ( + "bytes" + "encoding/json" + "io" + "io/ioutil" + "net/http" + "net/url" + "strconv" + + "github.com/markbates/goth" + "golang.org/x/oauth2" + "fmt" +) + +// These vars define the Authentication, Token, and Profile URLS for Gitlab. If +// using Gitlab CE or EE, you should change these values before calling New. +// +// Examples: +// gitlab.AuthURL = "https://gitlab.acme.com/oauth/authorize +// gitlab.TokenURL = "https://gitlab.acme.com/oauth/token +// gitlab.ProfileURL = "https://gitlab.acme.com/api/v3/user +var ( + AuthURL = "https://gitlab.com/oauth/authorize" + TokenURL = "https://gitlab.com/oauth/token" + ProfileURL = "https://gitlab.com/api/v3/user" +) + +// Provider is the implementation of `goth.Provider` for accessing Gitlab. +type Provider struct { + ClientKey string + Secret string + CallbackURL string + HTTPClient *http.Client + config *oauth2.Config + providerName string + authURL string + tokenURL string + profileURL string +} + +// New creates a new Gitlab provider and sets up important connection details. +// You should always call `gitlab.New` to get a new provider. Never try to +// create one manually. +func New(clientKey, secret, callbackURL string, scopes ...string) *Provider { + return NewCustomisedURL(clientKey, secret, callbackURL, AuthURL, TokenURL, ProfileURL, scopes...) +} + +// NewCustomisedURL is similar to New(...) but can be used to set custom URLs to connect to +func NewCustomisedURL(clientKey, secret, callbackURL, authURL, tokenURL, profileURL string, scopes ...string) *Provider { + p := &Provider{ + ClientKey: clientKey, + Secret: secret, + CallbackURL: callbackURL, + providerName: "gitlab", + profileURL: profileURL, + } + p.config = newConfig(p, authURL, tokenURL, scopes) + return p +} + +// 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 gitlab package. +func (p *Provider) Debug(debug bool) {} + +// BeginAuth asks Gitlab for an authentication end-point. +func (p *Provider) BeginAuth(state string) (goth.Session, error) { + return &Session{ + AuthURL: p.config.AuthCodeURL(state), + }, nil +} + +// FetchUser will go to Gitlab 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(), + RefreshToken: sess.RefreshToken, + ExpiresAt: sess.ExpiresAt, + } + + 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(p.profileURL + "?access_token=" + url.QueryEscape(sess.AccessToken)) + if err != nil { + if response != nil { + response.Body.Close() + } + return user, err + } + + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return user, fmt.Errorf("%s responded with a %d trying to fetch user information", p.providerName, 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) + + return user, err +} + +func newConfig(provider *Provider, authURL, tokenURL string, 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{}, + } + + if len(scopes) > 0 { + for _, scope := range scopes { + c.Scopes = append(c.Scopes, scope) + } + } + return c +} + +func userFromReader(r io.Reader, user *goth.User) error { + u := struct { + Name string `json:"name"` + Email string `json:"email"` + NickName string `json:"username"` + ID int `json:"id"` + AvatarURL string `json:"avatar_url"` + }{} + err := json.NewDecoder(r).Decode(&u) + if err != nil { + return err + } + user.Email = u.Email + user.Name = u.Name + user.NickName = u.NickName + user.UserID = strconv.Itoa(u.ID) + user.AvatarURL = u.AvatarURL + return nil +} + +//RefreshTokenAvailable refresh token is provided by auth provider or not +func (p *Provider) RefreshTokenAvailable() bool { + return true +} + +//RefreshToken get new access token based on the refresh token +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + token := &oauth2.Token{RefreshToken: refreshToken} + ts := p.config.TokenSource(goth.ContextForClient(p.Client()), token) + newToken, err := ts.Token() + if err != nil { + return nil, err + } + return newToken, err +} diff --git a/vendor/github.com/markbates/goth/providers/gitlab/session.go b/vendor/github.com/markbates/goth/providers/gitlab/session.go new file mode 100644 index 0000000000..a2f90647c2 --- /dev/null +++ b/vendor/github.com/markbates/goth/providers/gitlab/session.go @@ -0,0 +1,63 @@ +package gitlab + +import ( + "encoding/json" + "errors" + "strings" + "time" + + "github.com/markbates/goth" +) + +// Session stores data during the auth process with Gitlab. +type Session struct { + AuthURL string + AccessToken string + RefreshToken string + ExpiresAt time.Time +} + +var _ goth.Session = &Session{} + +// GetAuthURL will return the URL set by calling the `BeginAuth` function on the Gitlab provider. +func (s Session) GetAuthURL() (string, error) { + if s.AuthURL == "" { + return "", errors.New(goth.NoAuthUrlErrorMessage) + } + return s.AuthURL, nil +} + +// Authorize the session with Gitlab 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 + s.RefreshToken = token.RefreshToken + s.ExpiresAt = token.Expiry + 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 wil unmarshal a JSON string into a session. +func (p *Provider) UnmarshalSession(data string) (goth.Session, error) { + s := &Session{} + err := json.NewDecoder(strings.NewReader(data)).Decode(s) + return s, err +} diff --git a/vendor/github.com/markbates/goth/providers/gplus/gplus.go b/vendor/github.com/markbates/goth/providers/gplus/gplus.go new file mode 100644 index 0000000000..06655c2f7f --- /dev/null +++ b/vendor/github.com/markbates/goth/providers/gplus/gplus.go @@ -0,0 +1,195 @@ +// Package gplus implements the OAuth2 protocol for authenticating users through Google+. +// This package can be used as a reference implementation of an OAuth2 provider for Goth. +package gplus + +import ( + "bytes" + "encoding/json" + "io" + "io/ioutil" + "net/http" + "net/url" + "strings" + + "github.com/markbates/goth" + "golang.org/x/oauth2" + "fmt" +) + +const ( + authURL string = "https://accounts.google.com/o/oauth2/auth?access_type=offline" + tokenURL string = "https://accounts.google.com/o/oauth2/token" + endpointProfile string = "https://www.googleapis.com/oauth2/v2/userinfo" +) + +// New creates a new Google+ provider, and sets up important connection details. +// You should always call `gplus.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: "gplus", + } + p.config = newConfig(p, scopes) + return p +} + +// Provider is the implementation of `goth.Provider` for accessing Google+. +type Provider struct { + ClientKey string + Secret string + CallbackURL string + HTTPClient *http.Client + config *oauth2.Config + prompt oauth2.AuthCodeOption + 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 gplus package. +func (p *Provider) Debug(debug bool) {} + +// BeginAuth asks Google+ for an authentication end-point. +func (p *Provider) BeginAuth(state string) (goth.Session, error) { + var opts []oauth2.AuthCodeOption + if p.prompt != nil { + opts = append(opts, p.prompt) + } + url := p.config.AuthCodeURL(state, opts...) + session := &Session{ + AuthURL: url, + } + return session, nil +} + +// FetchUser will go to Google+ 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(), + RefreshToken: sess.RefreshToken, + ExpiresAt: sess.ExpiresAt, + } + + 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(endpointProfile + "?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("%s responded with a %d trying to fetch user information", p.providerName, 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) + return user, err +} + +func userFromReader(reader io.Reader, user *goth.User) error { + u := struct { + ID string `json:"id"` + Email string `json:"email"` + Name string `json:"name"` + FirstName string `json:"given_name"` + LastName string `json:"family_name"` + Link string `json:"link"` + Picture string `json:"picture"` + }{} + + err := json.NewDecoder(reader).Decode(&u) + if err != nil { + return err + } + + user.Name = u.Name + user.FirstName = u.FirstName + user.LastName = u.LastName + user.NickName = u.Name + user.Email = u.Email + //user.Description = u.Bio + user.AvatarURL = u.Picture + user.UserID = u.ID + //user.Location = u.Location.Name + + return err +} + +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{}, + } + + if len(scopes) > 0 { + for _, scope := range scopes { + c.Scopes = append(c.Scopes, scope) + } + } else { + c.Scopes = []string{"profile", "email", "openid"} + } + return c +} + +//RefreshTokenAvailable refresh token is provided by auth provider or not +func (p *Provider) RefreshTokenAvailable() bool { + return true +} + +//RefreshToken get new access token based on the refresh token +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + token := &oauth2.Token{RefreshToken: refreshToken} + ts := p.config.TokenSource(goth.ContextForClient(p.Client()), token) + newToken, err := ts.Token() + if err != nil { + return nil, err + } + return newToken, err +} + +// SetPrompt sets the prompt values for the GPlus OAuth call. Use this to +// force users to choose and account every time by passing "select_account", +// for example. +// See https://developers.google.com/identity/protocols/OpenIDConnect#authenticationuriparameters +func (p *Provider) SetPrompt(prompt ...string) { + if len(prompt) == 0 { + return + } + p.prompt = oauth2.SetAuthURLParam("prompt", strings.Join(prompt, " ")) +} diff --git a/vendor/github.com/markbates/goth/providers/gplus/session.go b/vendor/github.com/markbates/goth/providers/gplus/session.go new file mode 100644 index 0000000000..9710031f4d --- /dev/null +++ b/vendor/github.com/markbates/goth/providers/gplus/session.go @@ -0,0 +1,61 @@ +package gplus + +import ( + "encoding/json" + "errors" + "strings" + "time" + + "github.com/markbates/goth" +) + +// Session stores data during the auth process with Google+. +type Session struct { + AuthURL string + AccessToken string + RefreshToken string + ExpiresAt time.Time +} + +// GetAuthURL will return the URL set by calling the `BeginAuth` function on the Google+ provider. +func (s Session) GetAuthURL() (string, error) { + if s.AuthURL == "" { + return "", errors.New(goth.NoAuthUrlErrorMessage) + } + return s.AuthURL, nil +} + +// Authorize the session with Google+ 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 + s.RefreshToken = token.RefreshToken + s.ExpiresAt = token.Expiry + 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 +} diff --git a/vendor/github.com/markbates/goth/providers/openidConnect/openidConnect.go b/vendor/github.com/markbates/goth/providers/openidConnect/openidConnect.go new file mode 100644 index 0000000000..7ffd11c607 --- /dev/null +++ b/vendor/github.com/markbates/goth/providers/openidConnect/openidConnect.go @@ -0,0 +1,384 @@ +package openidConnect + +import ( + "net/http" + "strings" + "fmt" + "encoding/json" + "encoding/base64" + "io/ioutil" + "errors" + "golang.org/x/oauth2" + "github.com/markbates/goth" + "time" + "bytes" +) + +const ( + // Standard Claims http://openid.net/specs/openid-connect-core-1_0.html#StandardClaims + // fixed, cannot be changed + subjectClaim = "sub" + expiryClaim = "exp" + audienceClaim = "aud" + issuerClaim = "iss" + + PreferredUsernameClaim = "preferred_username" + EmailClaim = "email" + NameClaim = "name" + NicknameClaim = "nickname" + PictureClaim = "picture" + GivenNameClaim = "given_name" + FamilyNameClaim = "family_name" + AddressClaim = "address" + + // Unused but available to set in Provider claims + MiddleNameClaim = "middle_name" + ProfileClaim = "profile" + WebsiteClaim = "website" + EmailVerifiedClaim = "email_verified" + GenderClaim = "gender" + BirthdateClaim = "birthdate" + ZoneinfoClaim = "zoneinfo" + LocaleClaim = "locale" + PhoneNumberClaim = "phone_number" + PhoneNumberVerifiedClaim = "phone_number_verified" + UpdatedAtClaim = "updated_at" + + clockSkew = 10 * time.Second +) + +// Provider is the implementation of `goth.Provider` for accessing OpenID Connect provider +type Provider struct { + ClientKey string + Secret string + CallbackURL string + HTTPClient *http.Client + config *oauth2.Config + openIDConfig *OpenIDConfig + providerName string + + UserIdClaims []string + NameClaims []string + NickNameClaims []string + EmailClaims []string + AvatarURLClaims []string + FirstNameClaims []string + LastNameClaims []string + LocationClaims []string + + SkipUserInfoRequest bool +} + +type OpenIDConfig struct { + AuthEndpoint string `json:"authorization_endpoint"` + TokenEndpoint string `json:"token_endpoint"` + UserInfoEndpoint string `json:"userinfo_endpoint"` + Issuer string `json:"issuer"` +} + +// New creates a new OpenID Connect provider, and sets up important connection details. +// You should always call `openidConnect.New` to get a new Provider. Never try to create +// one manually. +// New returns an implementation of an OpenID Connect Authorization Code Flow +// See http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth +// ID Token decryption is not (yet) supported +// UserInfo decryption is not (yet) supported +func New(clientKey, secret, callbackURL, openIDAutoDiscoveryURL string, scopes ...string) (*Provider, error) { + p := &Provider{ + ClientKey: clientKey, + 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}, + + providerName: "openid-connect", + } + + openIDConfig, err := getOpenIDConfig(p, openIDAutoDiscoveryURL) + if err != nil { + return nil, err + } + p.openIDConfig = openIDConfig + + p.config = newConfig(p, scopes, openIDConfig) + return p, nil +} + +// 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 openidConnect package. +func (p *Provider) Debug(debug bool) {} + +// BeginAuth asks the OpenID Connect provider 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 use the the id_token and access requested information about the user. +func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { + sess := session.(*Session) + + expiresAt := sess.ExpiresAt + + if sess.IDToken == "" { + return goth.User{}, fmt.Errorf("%s cannot get user information without id_token", p.providerName) + } + + // decode returned id token to get expiry + claims, err := decodeJWT(sess.IDToken) + + if err != nil { + return goth.User{}, fmt.Errorf("oauth2: error decoding JWT token: %v", err) + } + + expiry, err := p.validateClaims(claims) + if err != nil { + return goth.User{}, fmt.Errorf("oauth2: error validating JWT token: %v", err) + } + + if expiry.Before(expiresAt) { + expiresAt = expiry + } + + if err := p.getUserInfo(sess.AccessToken, claims); err != nil { + return goth.User{}, err + } + + user := goth.User{ + AccessToken: sess.AccessToken, + Provider: p.Name(), + RefreshToken: sess.RefreshToken, + ExpiresAt: expiresAt, + RawData: claims, + } + + p.userFromClaims(claims, &user) + return user, err +} + +//RefreshTokenAvailable refresh token is provided by auth provider or not +func (p *Provider) RefreshTokenAvailable() bool { + return true +} + +//RefreshToken get new access token based on the refresh token +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + token := &oauth2.Token{RefreshToken: refreshToken} + ts := p.config.TokenSource(oauth2.NoContext, token) + newToken, err := ts.Token() + if err != nil { + return nil, err + } + return newToken, err +} + +// validate according to standard, returns expiry +// http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation +func (p *Provider) validateClaims(claims map[string]interface{}) (time.Time, error) { + audience := getClaimValue(claims, []string{audienceClaim}) + if audience != p.ClientKey { + return time.Time{}, errors.New("audience in token does not match client key") + } + + issuer := getClaimValue(claims, []string{issuerClaim}) + if issuer != p.openIDConfig.Issuer { + return time.Time{}, errors.New("issuer in token does not match issuer in OpenIDConfig discovery") + } + + // expiry is required for JWT, not for UserInfoResponse + // is actually a int64, so force it in to that type + expiryClaim := int64(claims[expiryClaim].(float64)) + expiry := time.Unix(expiryClaim, 0) + if expiry.Add(clockSkew).Before(time.Now()) { + return time.Time{}, errors.New("user info JWT token is expired") + } + return expiry, nil +} + +func (p *Provider) userFromClaims(claims map[string]interface{}, user *goth.User) { + // required + user.UserID = getClaimValue(claims, p.UserIdClaims) + + user.Name = getClaimValue(claims, p.NameClaims) + user.NickName = getClaimValue(claims, p.NickNameClaims) + user.Email = getClaimValue(claims, p.EmailClaims) + user.AvatarURL = getClaimValue(claims, p.AvatarURLClaims) + user.FirstName = getClaimValue(claims, p.FirstNameClaims) + user.LastName = getClaimValue(claims, p.LastNameClaims) + user.Location = getClaimValue(claims, p.LocationClaims) +} + +func (p *Provider) getUserInfo(accessToken string, claims map[string]interface{}) error { + // skip if there is no UserInfoEndpoint or is explicitly disabled + if p.openIDConfig.UserInfoEndpoint == "" || p.SkipUserInfoRequest { + return nil + } + + userInfoClaims, err := p.fetchUserInfo(p.openIDConfig.UserInfoEndpoint, accessToken) + if err != nil { + return err + } + + // The sub (subject) Claim MUST always be returned in the UserInfo Response. + // http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse + userInfoSubject := getClaimValue(userInfoClaims, []string{subjectClaim}) + if userInfoSubject == "" { + return fmt.Errorf("userinfo response did not contain a 'sub' claim: %#v", userInfoClaims) + } + + // The sub Claim in the UserInfo Response MUST be verified to exactly match the sub Claim in the ID Token; + // if they do not match, the UserInfo Response values MUST NOT be used. + // http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse + subject := getClaimValue(claims, []string{subjectClaim}) + if userInfoSubject != subject { + return fmt.Errorf("userinfo 'sub' claim (%s) did not match id_token 'sub' claim (%s)", userInfoSubject, subject) + } + + // Merge in userinfo claims in case id_token claims contained some that userinfo did not + for k, v := range userInfoClaims { + claims[k] = v + } + + return nil +} + +// fetch and decode JSON from the given UserInfo URL +func (p *Provider) fetchUserInfo(url, accessToken string) (map[string]interface{}, error) { + req, _ := http.NewRequest("GET", url, nil) + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken)) + + resp, err := p.Client().Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("Non-200 response from UserInfo: %d, WWW-Authenticate=%s", resp.StatusCode, resp.Header.Get("WWW-Authenticate")) + } + + // The UserInfo Claims MUST be returned as the members of a JSON object + // http://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse + data, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + return unMarshal(data) +} + +func getOpenIDConfig(p *Provider, openIDAutoDiscoveryURL string) (*OpenIDConfig, error) { + res, err := p.Client().Get(openIDAutoDiscoveryURL) + if err != nil { + return nil, err + } + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + return nil, err + } + + openIDConfig := &OpenIDConfig{} + err = json.Unmarshal(body, openIDConfig) + if err != nil { + return nil, err + } + + return openIDConfig, nil +} + +func newConfig(provider *Provider, scopes []string, openIDConfig *OpenIDConfig) *oauth2.Config { + c := &oauth2.Config{ + ClientID: provider.ClientKey, + ClientSecret: provider.Secret, + RedirectURL: provider.CallbackURL, + Endpoint: oauth2.Endpoint{ + AuthURL: openIDConfig.AuthEndpoint, + TokenURL: openIDConfig.TokenEndpoint, + }, + Scopes: []string{}, + } + + if len(scopes) > 0 { + foundOpenIDScope := false + + for _, scope := range scopes { + if scope == "openid" { + foundOpenIDScope = true + } + c.Scopes = append(c.Scopes, scope) + } + + if !foundOpenIDScope { + c.Scopes = append(c.Scopes, "openid") + } + } else { + c.Scopes = []string{"openid"} + } + + return c +} + +func getClaimValue(data map[string]interface{}, claims []string) string { + for _, claim := range claims { + if value, ok := data[claim]; ok { + if stringValue, ok := value.(string); ok && len(stringValue) > 0 { + return stringValue + } + } + } + + return "" +} + +// decodeJWT decodes a JSON Web Token into a simple map +// http://openid.net/specs/draft-jones-json-web-token-07.html +func decodeJWT(jwt string) (map[string]interface{}, error) { + jwtParts := strings.Split(jwt, ".") + if len(jwtParts) != 3 { + return nil, errors.New("jws: invalid token received, not all parts available") + } + + // Re-pad, if needed + encodedPayload := jwtParts[1] + if l := len(encodedPayload) % 4; l != 0 { + encodedPayload += strings.Repeat("=", 4-l) + } + + decodedPayload, err := base64.StdEncoding.DecodeString(encodedPayload) + if err != nil { + return nil, err + } + + return unMarshal(decodedPayload) +} + +func unMarshal(payload []byte) (map[string]interface{}, error) { + data := make(map[string]interface{}) + + return data, json.NewDecoder(bytes.NewBuffer(payload)).Decode(&data) +} diff --git a/vendor/github.com/markbates/goth/providers/openidConnect/session.go b/vendor/github.com/markbates/goth/providers/openidConnect/session.go new file mode 100644 index 0000000000..a34584fdef --- /dev/null +++ b/vendor/github.com/markbates/goth/providers/openidConnect/session.go @@ -0,0 +1,63 @@ +package openidConnect + +import ( + "errors" + "github.com/markbates/goth" + "encoding/json" + "strings" + "time" + "golang.org/x/oauth2" +) + +// Session stores data during the auth process with the OpenID Connect provider. +type Session struct { + AuthURL string + AccessToken string + RefreshToken string + ExpiresAt time.Time + IDToken string +} + +// GetAuthURL will return the URL set by calling the `BeginAuth` function on the OpenID Connect provider. +func (s Session) GetAuthURL() (string, error) { + if s.AuthURL == "" { + return "", errors.New("an AuthURL has not be set") + } + return s.AuthURL, nil +} + +// Authorize the session with the OpenID Connect provider 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(oauth2.NoContext, params.Get("code")) + if err != nil { + return "", err + } + + if !token.Valid() { + return "", errors.New("Invalid token received from provider") + } + + s.AccessToken = token.AccessToken + s.RefreshToken = token.RefreshToken + s.ExpiresAt = token.Expiry + s.IDToken = token.Extra("id_token").(string) + 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 +} diff --git a/vendor/github.com/markbates/goth/providers/twitter/session.go b/vendor/github.com/markbates/goth/providers/twitter/session.go new file mode 100644 index 0000000000..049928ff26 --- /dev/null +++ b/vendor/github.com/markbates/goth/providers/twitter/session.go @@ -0,0 +1,54 @@ +package twitter + +import ( + "encoding/json" + "errors" + "strings" + + "github.com/markbates/goth" + "github.com/mrjones/oauth" +) + +// Session stores data during the auth process with Twitter. +type Session struct { + AuthURL string + AccessToken *oauth.AccessToken + RequestToken *oauth.RequestToken +} + +// GetAuthURL will return the URL set by calling the `BeginAuth` function on the Twitter provider. +func (s Session) GetAuthURL() (string, error) { + if s.AuthURL == "" { + return "", errors.New(goth.NoAuthUrlErrorMessage) + } + return s.AuthURL, nil +} + +// Authorize the session with Twitter 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) + accessToken, err := p.consumer.AuthorizeToken(s.RequestToken, params.Get("oauth_verifier")) + if err != nil { + return "", err + } + + s.AccessToken = accessToken + return accessToken.Token, 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 +} diff --git a/vendor/github.com/markbates/goth/providers/twitter/twitter.go b/vendor/github.com/markbates/goth/providers/twitter/twitter.go new file mode 100644 index 0000000000..3703f21974 --- /dev/null +++ b/vendor/github.com/markbates/goth/providers/twitter/twitter.go @@ -0,0 +1,160 @@ +// Package twitter implements the OAuth protocol for authenticating users through Twitter. +// This package can be used as a reference implementation of an OAuth provider for Goth. +package twitter + +import ( + "bytes" + "encoding/json" + "errors" + "io/ioutil" + "net/http" + + "github.com/markbates/goth" + "github.com/mrjones/oauth" + "golang.org/x/oauth2" + "fmt" +) + +var ( + requestURL = "https://api.twitter.com/oauth/request_token" + authorizeURL = "https://api.twitter.com/oauth/authorize" + authenticateURL = "https://api.twitter.com/oauth/authenticate" + tokenURL = "https://api.twitter.com/oauth/access_token" + endpointProfile = "https://api.twitter.com/1.1/account/verify_credentials.json" +) + +// New creates a new Twitter provider, and sets up important connection details. +// You should always call `twitter.New` to get a new Provider. Never try to create +// one manually. +// +// 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", + } + p.consumer = newConsumer(p, authorizeURL) + return p +} + +// NewAuthenticate is the almost same as New. +// 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", + } + p.consumer = newConsumer(p, authenticateURL) + return p +} + +// Provider is the implementation of `goth.Provider` for accessing Twitter. +type Provider struct { + ClientKey string + Secret string + CallbackURL string + HTTPClient *http.Client + debug bool + consumer *oauth.Consumer + 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 sets the logging of the OAuth client to verbose. +func (p *Provider) Debug(debug bool) { + p.debug = debug +} + +// BeginAuth asks Twitter for an authentication end-point and a request token for a session. +// Twitter does not support the "state" variable. +func (p *Provider) BeginAuth(state string) (goth.Session, error) { + requestToken, url, err := p.consumer.GetRequestTokenAndUrl(p.CallbackURL) + session := &Session{ + AuthURL: url, + RequestToken: requestToken, + } + return session, err +} + +// FetchUser will go to Twitter and access basic information about the user. +func (p *Provider) FetchUser(session goth.Session) (goth.User, error) { + sess := session.(*Session) + user := goth.User{ + Provider: p.Name(), + } + + if sess.AccessToken == nil { + // 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.consumer.Get( + endpointProfile, + map[string]string{"include_entities": "false", "skip_status": "true"}, + sess.AccessToken) + if err != nil { + return user, err + } + defer response.Body.Close() + + if response.StatusCode != http.StatusOK { + return user, fmt.Errorf("%s responded with a %d trying to fetch user information", p.providerName, response.StatusCode) + } + + bits, err := ioutil.ReadAll(response.Body) + err = json.NewDecoder(bytes.NewReader(bits)).Decode(&user.RawData) + if err != nil { + return user, err + } + + user.Name = user.RawData["name"].(string) + user.NickName = user.RawData["screen_name"].(string) + user.Description = user.RawData["description"].(string) + user.AvatarURL = user.RawData["profile_image_url"].(string) + user.UserID = user.RawData["id_str"].(string) + user.Location = user.RawData["location"].(string) + user.AccessToken = sess.AccessToken.Token + user.AccessTokenSecret = sess.AccessToken.Secret + return user, err +} + +func newConsumer(provider *Provider, authURL string) *oauth.Consumer { + c := oauth.NewConsumer( + provider.ClientKey, + provider.Secret, + oauth.ServiceProvider{ + RequestTokenUrl: requestURL, + AuthorizeTokenUrl: authURL, + AccessTokenUrl: tokenURL, + }) + + c.Debug(provider.debug) + return c +} + +//RefreshToken refresh token is not provided by twitter +func (p *Provider) RefreshToken(refreshToken string) (*oauth2.Token, error) { + return nil, errors.New("Refresh token is not provided by twitter") +} + +//RefreshTokenAvailable refresh token is not provided by twitter +func (p *Provider) RefreshTokenAvailable() bool { + return false +} |