Oauth2 consumer (#679)
* initial stuff for oauth2 login, fails on: * login button on the signIn page to start the OAuth2 flow and a callback for each provider Only GitHub is implemented for now * show login button only when the OAuth2 consumer is configured (and activated) * create macaron group for oauth2 urls * prevent net/http in modules (other then oauth2) * use a new data sessions oauth2 folder for storing the oauth2 session data * add missing 2FA when this is enabled on the user * add password option for OAuth2 user , for use with git over http and login to the GUI * add tip for registering a GitHub OAuth application * at startup of Gitea register all configured providers and also on adding/deleting of new providers * custom handling of errors in oauth2 request init + show better tip * add ExternalLoginUser model and migration script to add it to database * link a external account to an existing account (still need to handle wrong login and signup) and remove if user is removed * remove the linked external account from the user his settings * if user is unknown we allow him to register a new account or link it to some existing account * sign up with button on signin page (als change OAuth2Provider structure so we can store basic stuff about providers) * from gorilla/sessions docs: "Important Note: If you aren't using gorilla/mux, you need to wrap your handlers with context.ClearHandler as or else you will leak memory!" (we're using gorilla/sessions for storing oauth2 sessions) * use updated goth lib that now supports getting the OAuth2 user if the AccessToken is still valid instead of re-authenticating (prevent flooding the OAuth2 provider)
This commit is contained in:
parent
fd941db246
commit
01d957677f
76 changed files with 7275 additions and 137 deletions
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
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue