mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-24 08:02:18 +02:00
some initial work on #1147 to enable Bitbucket with oauth2
This commit is contained in:
parent
e5065da888
commit
75cca2807d
320
remote/bitbucket/bitbucket.go
Normal file
320
remote/bitbucket/bitbucket.go
Normal file
@ -0,0 +1,320 @@
|
|||||||
|
package bitbucket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/drone/drone/shared/envconfig"
|
||||||
|
"github.com/drone/drone/shared/httputil"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/bitbucket"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Bitbucket struct {
|
||||||
|
Client string
|
||||||
|
Secret string
|
||||||
|
Orgs []string
|
||||||
|
Open bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func Load(env envconfig.Env) *Bitbucket {
|
||||||
|
config := env.String("REMOTE_CONFIG", "")
|
||||||
|
|
||||||
|
// parse the remote DSN configuration string
|
||||||
|
url_, err := url.Parse(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("unable to parse remote dsn. %s", err)
|
||||||
|
}
|
||||||
|
params := url_.Query()
|
||||||
|
url_.Path = ""
|
||||||
|
url_.RawQuery = ""
|
||||||
|
|
||||||
|
// create the Githbub remote using parameters from
|
||||||
|
// the parsed DSN configuration string.
|
||||||
|
bitbucket := Bitbucket{}
|
||||||
|
bitbucket.Client = params.Get("client_id")
|
||||||
|
bitbucket.Secret = params.Get("client_secret")
|
||||||
|
bitbucket.Orgs = params["orgs"]
|
||||||
|
bitbucket.Open, _ = strconv.ParseBool(params.Get("open"))
|
||||||
|
|
||||||
|
return &bitbucket
|
||||||
|
}
|
||||||
|
|
||||||
|
// Login authenticates the session and returns the
|
||||||
|
// remote user details.
|
||||||
|
func (bb *Bitbucket) Login(res http.ResponseWriter, req *http.Request) (*model.User, bool, error) {
|
||||||
|
|
||||||
|
config := &oauth2.Config{
|
||||||
|
ClientID: bb.Client,
|
||||||
|
ClientSecret: bb.Secret,
|
||||||
|
Endpoint: bitbucket.Endpoint,
|
||||||
|
RedirectURL: fmt.Sprintf("%s/authorize", httputil.GetURL(req)),
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the OAuth code
|
||||||
|
var code = req.FormValue("code")
|
||||||
|
if len(code) == 0 {
|
||||||
|
http.Redirect(res, req, config.AuthCodeURL("drone"), http.StatusSeeOther)
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var token, err = config.Exchange(oauth2.NoContext, code)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("Error exchanging token. %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := NewClient(config.Client(oauth2.NoContext, token))
|
||||||
|
curr, err := client.FindCurrent()
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// convers the current bitbucket user to the
|
||||||
|
// common drone user structure.
|
||||||
|
user := model.User{}
|
||||||
|
user.Login = curr.Login
|
||||||
|
user.Token = token.AccessToken
|
||||||
|
user.Secret = token.RefreshToken
|
||||||
|
user.Avatar = curr.Links.Avatar.Href
|
||||||
|
|
||||||
|
// gets the primary, confirmed email from bitbucket
|
||||||
|
emails, err := client.ListEmail()
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
for _, email := range emails.Values {
|
||||||
|
if email.IsPrimary && email.IsConfirmed {
|
||||||
|
user.Email = email.Email
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the installation is restricted to a subset
|
||||||
|
// of organizations, get the orgs and verify the
|
||||||
|
// user is a member.
|
||||||
|
if len(bb.Orgs) != 0 {
|
||||||
|
resp, err := client.ListTeams(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var member bool
|
||||||
|
for _, team := range resp.Values {
|
||||||
|
for _, team_ := range bb.Orgs {
|
||||||
|
if team.Login == team_ {
|
||||||
|
member = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !member {
|
||||||
|
return nil, false, fmt.Errorf("User does not belong to correct org. Must belong to %v", bb.Orgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &user, bb.Open, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auth authenticates the session and returns the remote user
|
||||||
|
// login for the given token and secret
|
||||||
|
func (bb *Bitbucket) Auth(token, secret string) (string, error) {
|
||||||
|
token_ := oauth2.Token{AccessToken: token, RefreshToken: secret}
|
||||||
|
client := NewClientToken(bb.Client, bb.Secret, &token_)
|
||||||
|
|
||||||
|
user, err := client.FindCurrent()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return user.Login, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh refreshes an oauth token and expiration for the given
|
||||||
|
// user. It returns true if the token was refreshed, false if the
|
||||||
|
// token was not refreshed, and error if it failed to refersh.
|
||||||
|
func (bb *Bitbucket) Refresh(user *model.User) (bool, error) {
|
||||||
|
config := &oauth2.Config{
|
||||||
|
ClientID: bb.Client,
|
||||||
|
ClientSecret: bb.Secret,
|
||||||
|
Endpoint: bitbucket.Endpoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a token source with just the refresh token.
|
||||||
|
// this will ensure an access token is automatically
|
||||||
|
// requested.
|
||||||
|
source := config.TokenSource(
|
||||||
|
oauth2.NoContext, &oauth2.Token{RefreshToken: user.Secret})
|
||||||
|
|
||||||
|
// requesting the token automatically refreshes and
|
||||||
|
// returns a new access token.
|
||||||
|
token, err := source.Token()
|
||||||
|
if err != nil || len(token.AccessToken) == 0 {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// update the user to include tne new access token
|
||||||
|
user.Token = token.AccessToken
|
||||||
|
user.Secret = token.RefreshToken
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repo fetches the named repository from the remote system.
|
||||||
|
func (bb *Bitbucket) Repo(u *model.User, owner, name string) (*model.Repo, error) {
|
||||||
|
token := oauth2.Token{AccessToken: u.Token, RefreshToken: u.Secret}
|
||||||
|
client := NewClientToken(bb.Client, bb.Secret, &token)
|
||||||
|
|
||||||
|
repo, err := client.FindRepo(owner, name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return convertRepo(repo), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Repos fetches a list of repos from the remote system.
|
||||||
|
func (bb *Bitbucket) Repos(u *model.User) ([]*model.RepoLite, error) {
|
||||||
|
token := oauth2.Token{AccessToken: u.Token, RefreshToken: u.Secret}
|
||||||
|
client := NewClientToken(bb.Client, bb.Secret, &token)
|
||||||
|
|
||||||
|
// var accounts = []string{u.Login}
|
||||||
|
var repos []*model.RepoLite
|
||||||
|
var page = 1
|
||||||
|
// for {
|
||||||
|
// resp, err := client.ListTeams(&ListOpts{Page: page})
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
|
||||||
|
// for _, team := range resp.Values {
|
||||||
|
// accounts = append(accounts, team.Login)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// if resp.Page == resp.Pages {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
page = 1
|
||||||
|
for {
|
||||||
|
resp, err := client.ListRepos(u.Login, &ListOpts{Page: page})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, repo := range resp.Values {
|
||||||
|
repos = append(repos, convertRepoLite(&repo))
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Page == resp.Pages {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return repos, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Perm fetches the named repository permissions from
|
||||||
|
// the remote system for the specified user.
|
||||||
|
func (bb *Bitbucket) Perm(u *model.User, owner, name string) (*model.Perm, error) {
|
||||||
|
token := oauth2.Token{AccessToken: u.Token, RefreshToken: u.Secret}
|
||||||
|
client := NewClientToken(bb.Client, bb.Secret, &token)
|
||||||
|
|
||||||
|
perms := new(model.Perm)
|
||||||
|
_, err := client.FindRepo(owner, name)
|
||||||
|
if err != nil {
|
||||||
|
return perms, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we've gotten this far we know that the user at
|
||||||
|
// least has read access to the repository.
|
||||||
|
perms.Pull = true
|
||||||
|
|
||||||
|
// if the user has access to the repository hooks we
|
||||||
|
// can deduce that the user has push and admin access.
|
||||||
|
_, err = client.ListHooks(owner, name, nil)
|
||||||
|
if err == nil {
|
||||||
|
perms.Push = true
|
||||||
|
perms.Admin = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return perms, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Script fetches the build script (.drone.yml) from the remote
|
||||||
|
// repository and returns in string format.
|
||||||
|
func (bb *Bitbucket) Script(u *model.User, r *model.Repo, b *model.Build) ([]byte, []byte, error) {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status sends the commit status to the remote system.
|
||||||
|
// An example would be the GitHub pull request status.
|
||||||
|
func (bb *Bitbucket) Status(u *model.User, r *model.Repo, b *model.Build, link string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Netrc returns a .netrc file that can be used to clone
|
||||||
|
// private repositories from a remote system.
|
||||||
|
func (bb *Bitbucket) Netrc(u *model.User, r *model.Repo) (*model.Netrc, error) {
|
||||||
|
netrc := &model.Netrc{}
|
||||||
|
netrc.Login = "x-token-auth"
|
||||||
|
netrc.Password = u.Token
|
||||||
|
netrc.Machine = "bitbucket.org"
|
||||||
|
return netrc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activate activates a repository by creating the post-commit hook and
|
||||||
|
// adding the SSH deploy key, if applicable.
|
||||||
|
func (bb *Bitbucket) Activate(u *model.User, r *model.Repo, k *model.Key, link string) error {
|
||||||
|
|
||||||
|
// "repo:push"
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deactivate removes a repository by removing all the post-commit hooks
|
||||||
|
// which are equal to link and removing the SSH deploy key.
|
||||||
|
func (bb *Bitbucket) Deactivate(u *model.User, r *model.Repo, link string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hook parses the post-commit hook from the Request body
|
||||||
|
// and returns the required data in a standard format.
|
||||||
|
func (bb *Bitbucket) Hook(r *http.Request) (*model.Repo, *model.Build, error) {
|
||||||
|
return nil, nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertRepo(from *Repo) *model.Repo {
|
||||||
|
repo := &model.Repo{}
|
||||||
|
repo.Owner = from.Owner.Login
|
||||||
|
repo.Name = from.Name
|
||||||
|
repo.FullName = from.FullName
|
||||||
|
repo.Link = from.Links.Html.Href
|
||||||
|
repo.IsPrivate = from.IsPrivate
|
||||||
|
repo.Avatar = from.Owner.Links.Avatar.Href
|
||||||
|
repo.Branch = "master"
|
||||||
|
repo.Clone = fmt.Sprintf("https://bitbucket.org/%s.git", repo.FullName)
|
||||||
|
|
||||||
|
// above we manually constructed the repository clone url.
|
||||||
|
// below we will iterate through the list of clone links and
|
||||||
|
// attempt to instead use the clone url provided by bitbucket.
|
||||||
|
for _, link := range from.Links.Clone {
|
||||||
|
if link.Name == "https" {
|
||||||
|
repo.Clone = link.Href
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return repo
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertRepoLite(from *Repo) *model.RepoLite {
|
||||||
|
repo := &model.RepoLite{}
|
||||||
|
repo.Owner = from.Owner.Login
|
||||||
|
repo.Name = from.Name
|
||||||
|
repo.FullName = from.FullName
|
||||||
|
repo.Avatar = from.Owner.Links.Avatar.Href
|
||||||
|
return repo
|
||||||
|
}
|
155
remote/bitbucket/client.go
Normal file
155
remote/bitbucket/client.go
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
package bitbucket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/bitbucket"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
get = "GET"
|
||||||
|
put = "PUT"
|
||||||
|
post = "POST"
|
||||||
|
del = "DELETE"
|
||||||
|
)
|
||||||
|
|
||||||
|
const api = "https://api.bitbucket.org"
|
||||||
|
|
||||||
|
type Client struct {
|
||||||
|
*http.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient(client *http.Client) *Client {
|
||||||
|
return &Client{client}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClientToken(client, secret string, token *oauth2.Token) *Client {
|
||||||
|
config := &oauth2.Config{
|
||||||
|
ClientID: client,
|
||||||
|
ClientSecret: secret,
|
||||||
|
Endpoint: bitbucket.Endpoint,
|
||||||
|
}
|
||||||
|
return NewClient(config.Client(oauth2.NoContext, token))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) FindCurrent() (*Account, error) {
|
||||||
|
var out = new(Account)
|
||||||
|
var uri = fmt.Sprintf("%s/2.0/user/", api)
|
||||||
|
var err = c.do(uri, get, nil, out)
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ListEmail() (*EmailResp, error) {
|
||||||
|
var out = new(EmailResp)
|
||||||
|
var uri = fmt.Sprintf("%s/2.0/user/emails", api)
|
||||||
|
var err = c.do(uri, get, nil, out)
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ListTeams(opts *ListOpts) (*AccountResp, error) {
|
||||||
|
var out = new(AccountResp)
|
||||||
|
var uri = fmt.Sprintf("%s/2.0/teams/?role=member", api)
|
||||||
|
if opts != nil && opts.Page > 0 {
|
||||||
|
uri = fmt.Sprintf("%s&page=%d", uri, opts.Page)
|
||||||
|
}
|
||||||
|
var err = c.do(uri, get, nil, out)
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) FindRepo(owner, name string) (*Repo, error) {
|
||||||
|
var out = new(Repo)
|
||||||
|
var uri = fmt.Sprintf("%s/2.0/repositories/%s/%s", api, owner, name)
|
||||||
|
var err = c.do(uri, get, nil, out)
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ListRepos(account string, opts *ListOpts) (*RepoResp, error) {
|
||||||
|
var out = new(RepoResp)
|
||||||
|
var uri = fmt.Sprintf("%s/2.0/repositories/%s", api)
|
||||||
|
if opts != nil && opts.Page > 0 {
|
||||||
|
uri = fmt.Sprintf("%s?page=%d", uri, opts.Page)
|
||||||
|
}
|
||||||
|
var err = c.do(uri, get, nil, out)
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) FindHook(owner, name, id string) (*Hook, error) {
|
||||||
|
var out = new(Hook)
|
||||||
|
var uri = fmt.Sprintf("%s/2.0/repositories/%s/%s/hooks/%s", api, owner, name, id)
|
||||||
|
var err = c.do(uri, get, nil, out)
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) ListHooks(owner, name string, opts *ListOpts) (*HookResp, error) {
|
||||||
|
var out = new(HookResp)
|
||||||
|
var uri = fmt.Sprintf("%s/2.0/repositories/%s/%s/hooks", api, owner, name)
|
||||||
|
if opts != nil && opts.Page > 0 {
|
||||||
|
uri = fmt.Sprintf("%s?page=%d", uri, opts.Page)
|
||||||
|
}
|
||||||
|
var err = c.do(uri, get, nil, out)
|
||||||
|
return out, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) CreateHook(owner, name, hook *Hook) error {
|
||||||
|
var uri = fmt.Sprintf("%s/2.0/repositories/%s/%s/hooks", api, owner, name)
|
||||||
|
return c.do(uri, post, hook, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) DeleteHook(owner, name, id string) error {
|
||||||
|
var uri = fmt.Sprintf("%s/2.0/repositories/%s/%s/hooks/%s", api, owner, name, id)
|
||||||
|
return c.do(uri, del, nil, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) do(rawurl, method string, in, out interface{}) error {
|
||||||
|
|
||||||
|
uri, err := url.Parse(rawurl)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
println(uri.String())
|
||||||
|
// if we are posting or putting data, we need to
|
||||||
|
// write it to the body of the request.
|
||||||
|
var buf io.ReadWriter
|
||||||
|
if in != nil {
|
||||||
|
buf = new(bytes.Buffer)
|
||||||
|
err := json.NewEncoder(buf).Encode(in)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new http request to bitbucket.
|
||||||
|
req, err := http.NewRequest(method, uri.String(), buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// if an error is encountered, parse and return the
|
||||||
|
// error response.
|
||||||
|
if resp.StatusCode > http.StatusPartialContent {
|
||||||
|
err := Error{}
|
||||||
|
json.NewDecoder(resp.Body).Decode(&err)
|
||||||
|
err.Status = resp.StatusCode
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if a json response is expected, parse and return
|
||||||
|
// the json response.
|
||||||
|
if out != nil {
|
||||||
|
return json.NewDecoder(resp.Body).Decode(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
92
remote/bitbucket/types.go
Normal file
92
remote/bitbucket/types.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package bitbucket
|
||||||
|
|
||||||
|
type Account struct {
|
||||||
|
Login string `json:"username"`
|
||||||
|
Name string `json:"display_name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Links Links `json:"links"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccountResp struct {
|
||||||
|
Page int `json:"page"`
|
||||||
|
Pages int `json:"pagelen"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
Values []Account `json:"values"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Email struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
IsConfirmed bool `json:"is_confirmed"`
|
||||||
|
IsPrimary bool `json:"is_primary"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type EmailResp struct {
|
||||||
|
Page int `json:"page"`
|
||||||
|
Pages int `json:"pagelen"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
Values []Email `json:"values"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Hook struct {
|
||||||
|
Uuid string `json:"uuid,omitempty"`
|
||||||
|
Desc string `json:"description"`
|
||||||
|
Url string `json:"url"`
|
||||||
|
Events []string `json:"events"`
|
||||||
|
Active bool `json:"active"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type HookResp struct {
|
||||||
|
Page int `json:"page"`
|
||||||
|
Pages int `json:"pagelen"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
Values []Hook `json:"values"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Links struct {
|
||||||
|
Avatar Link `json:"avatar"`
|
||||||
|
Html Link `json:"html"`
|
||||||
|
Clone []Link `json:"clone"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Link struct {
|
||||||
|
Href string `json:"href"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LinkClone struct {
|
||||||
|
Link
|
||||||
|
}
|
||||||
|
|
||||||
|
type Repo struct {
|
||||||
|
Owner Account `json:"owner"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
FullName string `json:"full_name"`
|
||||||
|
Language string `json:"language"`
|
||||||
|
IsPrivate bool `json:"is_private"`
|
||||||
|
Scm string `json:"scm"`
|
||||||
|
Desc string `json:"desc"`
|
||||||
|
Links Links `json:"links"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RepoResp struct {
|
||||||
|
Page int `json:"page"`
|
||||||
|
Pages int `json:"pagelen"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
Values []Repo `json:"values"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ListOpts struct {
|
||||||
|
Page int
|
||||||
|
PageLen int
|
||||||
|
}
|
||||||
|
|
||||||
|
type Error struct {
|
||||||
|
Status int
|
||||||
|
Body struct {
|
||||||
|
Message string `json:"message"`
|
||||||
|
} `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Error) Error() string {
|
||||||
|
return e.Body.Message
|
||||||
|
}
|
@ -4,6 +4,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/drone/drone/model"
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/drone/drone/remote/bitbucket"
|
||||||
"github.com/drone/drone/remote/github"
|
"github.com/drone/drone/remote/github"
|
||||||
"github.com/drone/drone/remote/gitlab"
|
"github.com/drone/drone/remote/gitlab"
|
||||||
"github.com/drone/drone/shared/envconfig"
|
"github.com/drone/drone/shared/envconfig"
|
||||||
@ -15,6 +16,8 @@ func Load(env envconfig.Env) Remote {
|
|||||||
driver := env.Get("REMOTE_DRIVER")
|
driver := env.Get("REMOTE_DRIVER")
|
||||||
|
|
||||||
switch driver {
|
switch driver {
|
||||||
|
case "bitbucket":
|
||||||
|
return bitbucket.Load(env)
|
||||||
case "github":
|
case "github":
|
||||||
return github.Load(env)
|
return github.Load(env)
|
||||||
case "gitlab":
|
case "gitlab":
|
||||||
@ -70,3 +73,10 @@ type Remote interface {
|
|||||||
// and returns the required data in a standard format.
|
// and returns the required data in a standard format.
|
||||||
Hook(r *http.Request) (*model.Repo, *model.Build, error)
|
Hook(r *http.Request) (*model.Repo, *model.Build, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Refresher interface {
|
||||||
|
// Refresh refreshes an oauth token and expiration for the given
|
||||||
|
// user. It returns true if the token was refreshed, false if the
|
||||||
|
// token was not refreshed, and error if it failed to refersh.
|
||||||
|
Refresh(*model.User) (bool, error)
|
||||||
|
}
|
||||||
|
10
vendor/github.com/bugagazavr/go-gitlab-client/util.go
generated
vendored
Normal file
10
vendor/github.com/bugagazavr/go-gitlab-client/util.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package gogitlab
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func encodeParameter(value string) string {
|
||||||
|
return strings.Replace(url.QueryEscape(value), "/", "%2F", 0)
|
||||||
|
}
|
11
vendor/github.com/bugagazavr/go-gitlab-client/util_test.go
generated
vendored
Normal file
11
vendor/github.com/bugagazavr/go-gitlab-client/util_test.go
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package gogitlab
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParameterEncoding(t *testing.T) {
|
||||||
|
assert.Equal(t, encodeParameter("namespace/project"), "namespace%2Fproject")
|
||||||
|
assert.Equal(t, encodeParameter("14"), "14")
|
||||||
|
}
|
3
vendor/golang.org/x/oauth2/AUTHORS
generated
vendored
Normal file
3
vendor/golang.org/x/oauth2/AUTHORS
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# This source code refers to The Go Authors for copyright purposes.
|
||||||
|
# The master list of authors is in the main Go distribution,
|
||||||
|
# visible at http://tip.golang.org/AUTHORS.
|
31
vendor/golang.org/x/oauth2/CONTRIBUTING.md
generated
vendored
Normal file
31
vendor/golang.org/x/oauth2/CONTRIBUTING.md
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# Contributing to Go
|
||||||
|
|
||||||
|
Go is an open source project.
|
||||||
|
|
||||||
|
It is the work of hundreds of contributors. We appreciate your help!
|
||||||
|
|
||||||
|
|
||||||
|
## Filing issues
|
||||||
|
|
||||||
|
When [filing an issue](https://github.com/golang/oauth2/issues), make sure to answer these five questions:
|
||||||
|
|
||||||
|
1. What version of Go are you using (`go version`)?
|
||||||
|
2. What operating system and processor architecture are you using?
|
||||||
|
3. What did you do?
|
||||||
|
4. What did you expect to see?
|
||||||
|
5. What did you see instead?
|
||||||
|
|
||||||
|
General questions should go to the [golang-nuts mailing list](https://groups.google.com/group/golang-nuts) instead of the issue tracker.
|
||||||
|
The gophers there will answer or ask you to file an issue if you've tripped over a bug.
|
||||||
|
|
||||||
|
## Contributing code
|
||||||
|
|
||||||
|
Please read the [Contribution Guidelines](https://golang.org/doc/contribute.html)
|
||||||
|
before sending patches.
|
||||||
|
|
||||||
|
**We do not accept GitHub pull requests**
|
||||||
|
(we use [Gerrit](https://code.google.com/p/gerrit/) instead for code review).
|
||||||
|
|
||||||
|
Unless otherwise noted, the Go source files are distributed under
|
||||||
|
the BSD-style license found in the LICENSE file.
|
||||||
|
|
3
vendor/golang.org/x/oauth2/CONTRIBUTORS
generated
vendored
Normal file
3
vendor/golang.org/x/oauth2/CONTRIBUTORS
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# This source code was written by the Go contributors.
|
||||||
|
# The master list of contributors is in the main Go distribution,
|
||||||
|
# visible at http://tip.golang.org/CONTRIBUTORS.
|
27
vendor/golang.org/x/oauth2/LICENSE
generated
vendored
Normal file
27
vendor/golang.org/x/oauth2/LICENSE
generated
vendored
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
Copyright (c) 2009 The oauth2 Authors. All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following disclaimer
|
||||||
|
in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from
|
||||||
|
this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
64
vendor/golang.org/x/oauth2/README.md
generated
vendored
Normal file
64
vendor/golang.org/x/oauth2/README.md
generated
vendored
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# OAuth2 for Go
|
||||||
|
|
||||||
|
[![Build Status](https://travis-ci.org/golang/oauth2.svg?branch=master)](https://travis-ci.org/golang/oauth2)
|
||||||
|
|
||||||
|
oauth2 package contains a client implementation for OAuth 2.0 spec.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
~~~~
|
||||||
|
go get golang.org/x/oauth2
|
||||||
|
~~~~
|
||||||
|
|
||||||
|
See godoc for further documentation and examples.
|
||||||
|
|
||||||
|
* [godoc.org/golang.org/x/oauth2](http://godoc.org/golang.org/x/oauth2)
|
||||||
|
* [godoc.org/golang.org/x/oauth2/google](http://godoc.org/golang.org/x/oauth2/google)
|
||||||
|
|
||||||
|
|
||||||
|
## App Engine
|
||||||
|
|
||||||
|
In change 96e89be (March 2015) we removed the `oauth2.Context2` type in favor
|
||||||
|
of the [`context.Context`](https://golang.org/x/net/context#Context) type from
|
||||||
|
the `golang.org/x/net/context` package
|
||||||
|
|
||||||
|
This means its no longer possible to use the "Classic App Engine"
|
||||||
|
`appengine.Context` type with the `oauth2` package. (You're using
|
||||||
|
Classic App Engine if you import the package `"appengine"`.)
|
||||||
|
|
||||||
|
To work around this, you may use the new `"google.golang.org/appengine"`
|
||||||
|
package. This package has almost the same API as the `"appengine"` package,
|
||||||
|
but it can be fetched with `go get` and used on "Managed VMs" and well as
|
||||||
|
Classic App Engine.
|
||||||
|
|
||||||
|
See the [new `appengine` package's readme](https://github.com/golang/appengine#updating-a-go-app-engine-app)
|
||||||
|
for information on updating your app.
|
||||||
|
|
||||||
|
If you don't want to update your entire app to use the new App Engine packages,
|
||||||
|
you may use both sets of packages in parallel, using only the new packages
|
||||||
|
with the `oauth2` package.
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/google"
|
||||||
|
newappengine "google.golang.org/appengine"
|
||||||
|
newurlfetch "google.golang.org/appengine/urlfetch"
|
||||||
|
|
||||||
|
"appengine"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
var c appengine.Context = appengine.NewContext(r)
|
||||||
|
c.Infof("Logging a message with the old package")
|
||||||
|
|
||||||
|
var ctx context.Context = newappengine.NewContext(r)
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: &oauth2.Transport{
|
||||||
|
Source: google.AppEngineTokenSource(ctx, "scope"),
|
||||||
|
Base: &newurlfetch.Transport{Context: ctx},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client.Get("...")
|
||||||
|
}
|
||||||
|
|
16
vendor/golang.org/x/oauth2/bitbucket/bitbucket.go
generated
vendored
Normal file
16
vendor/golang.org/x/oauth2/bitbucket/bitbucket.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2015 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package bitbucket provides constants for using OAuth2 to access Bitbucket.
|
||||||
|
package bitbucket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Endpoint is Bitbucket's OAuth 2.0 endpoint.
|
||||||
|
var Endpoint = oauth2.Endpoint{
|
||||||
|
AuthURL: "https://bitbucket.org/site/oauth2/authorize",
|
||||||
|
TokenURL: "https://bitbucket.org/site/oauth2/access_token",
|
||||||
|
}
|
25
vendor/golang.org/x/oauth2/client_appengine.go
generated
vendored
Normal file
25
vendor/golang.org/x/oauth2/client_appengine.go
generated
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build appengine
|
||||||
|
|
||||||
|
// App Engine hooks.
|
||||||
|
|
||||||
|
package oauth2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/oauth2/internal"
|
||||||
|
"google.golang.org/appengine/urlfetch"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
internal.RegisterContextClientFunc(contextClientAppEngine)
|
||||||
|
}
|
||||||
|
|
||||||
|
func contextClientAppEngine(ctx context.Context) (*http.Client, error) {
|
||||||
|
return urlfetch.Client(ctx), nil
|
||||||
|
}
|
112
vendor/golang.org/x/oauth2/clientcredentials/clientcredentials.go
generated
vendored
Normal file
112
vendor/golang.org/x/oauth2/clientcredentials/clientcredentials.go
generated
vendored
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package clientcredentials implements the OAuth2.0 "client credentials" token flow,
|
||||||
|
// also known as the "two-legged OAuth 2.0".
|
||||||
|
//
|
||||||
|
// This should be used when the client is acting on its own behalf or when the client
|
||||||
|
// is the resource owner. It may also be used when requesting access to protected
|
||||||
|
// resources based on an authorization previously arranged with the authorization
|
||||||
|
// server.
|
||||||
|
//
|
||||||
|
// See http://tools.ietf.org/html/draft-ietf-oauth-v2-31#section-4.4
|
||||||
|
package clientcredentials // import "golang.org/x/oauth2/clientcredentials"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// tokenFromInternal maps an *internal.Token struct into
|
||||||
|
// an *oauth2.Token struct.
|
||||||
|
func tokenFromInternal(t *internal.Token) *oauth2.Token {
|
||||||
|
if t == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
tk := &oauth2.Token{
|
||||||
|
AccessToken: t.AccessToken,
|
||||||
|
TokenType: t.TokenType,
|
||||||
|
RefreshToken: t.RefreshToken,
|
||||||
|
Expiry: t.Expiry,
|
||||||
|
}
|
||||||
|
return tk.WithExtra(t.Raw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// retrieveToken takes a *Config and uses that to retrieve an *internal.Token.
|
||||||
|
// This token is then mapped from *internal.Token into an *oauth2.Token which is
|
||||||
|
// returned along with an error.
|
||||||
|
func retrieveToken(ctx context.Context, c *Config, v url.Values) (*oauth2.Token, error) {
|
||||||
|
tk, err := internal.RetrieveToken(ctx, c.ClientID, c.ClientSecret, c.TokenURL, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tokenFromInternal(tk), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client Credentials Config describes a 2-legged OAuth2 flow, with both the
|
||||||
|
// client application information and the server's endpoint URLs.
|
||||||
|
type Config struct {
|
||||||
|
// ClientID is the application's ID.
|
||||||
|
ClientID string
|
||||||
|
|
||||||
|
// ClientSecret is the application's secret.
|
||||||
|
ClientSecret string
|
||||||
|
|
||||||
|
// TokenURL is the resource server's token endpoint
|
||||||
|
// URL. This is a constant specific to each server.
|
||||||
|
TokenURL string
|
||||||
|
|
||||||
|
// Scope specifies optional requested permissions.
|
||||||
|
Scopes []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token uses client credentials to retreive a token.
|
||||||
|
// The HTTP client to use is derived from the context.
|
||||||
|
// If nil, http.DefaultClient is used.
|
||||||
|
func (c *Config) Token(ctx context.Context) (*oauth2.Token, error) {
|
||||||
|
return retrieveToken(ctx, c, url.Values{
|
||||||
|
"grant_type": {"client_credentials"},
|
||||||
|
"scope": internal.CondVal(strings.Join(c.Scopes, " ")),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client returns an HTTP client using the provided token.
|
||||||
|
// The token will auto-refresh as necessary. The underlying
|
||||||
|
// HTTP transport will be obtained using the provided context.
|
||||||
|
// The returned client and its Transport should not be modified.
|
||||||
|
func (c *Config) Client(ctx context.Context) *http.Client {
|
||||||
|
return oauth2.NewClient(ctx, c.TokenSource(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenSource returns a TokenSource that returns t until t expires,
|
||||||
|
// automatically refreshing it as necessary using the provided context and the
|
||||||
|
// client ID and client secret.
|
||||||
|
//
|
||||||
|
// Most users will use Config.Client instead.
|
||||||
|
func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource {
|
||||||
|
source := &tokenSource{
|
||||||
|
ctx: ctx,
|
||||||
|
conf: c,
|
||||||
|
}
|
||||||
|
return oauth2.ReuseTokenSource(nil, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
type tokenSource struct {
|
||||||
|
ctx context.Context
|
||||||
|
conf *Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token refreshes the token by using a new client credentials request.
|
||||||
|
// tokens received this way do not include a refresh token
|
||||||
|
func (c *tokenSource) Token() (*oauth2.Token, error) {
|
||||||
|
return retrieveToken(c.ctx, c.conf, url.Values{
|
||||||
|
"grant_type": {"client_credentials"},
|
||||||
|
"scope": internal.CondVal(strings.Join(c.conf.Scopes, " ")),
|
||||||
|
})
|
||||||
|
}
|
96
vendor/golang.org/x/oauth2/clientcredentials/clientcredentials_test.go
generated
vendored
Normal file
96
vendor/golang.org/x/oauth2/clientcredentials/clientcredentials_test.go
generated
vendored
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package clientcredentials
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newConf(url string) *Config {
|
||||||
|
return &Config{
|
||||||
|
ClientID: "CLIENT_ID",
|
||||||
|
ClientSecret: "CLIENT_SECRET",
|
||||||
|
Scopes: []string{"scope1", "scope2"},
|
||||||
|
TokenURL: url + "/token",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockTransport struct {
|
||||||
|
rt func(req *http.Request) (resp *http.Response, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *mockTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
|
||||||
|
return t.rt(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTokenRequest(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.String() != "/token" {
|
||||||
|
t.Errorf("authenticate client request URL = %q; want %q", r.URL, "/token")
|
||||||
|
}
|
||||||
|
headerAuth := r.Header.Get("Authorization")
|
||||||
|
if headerAuth != "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ=" {
|
||||||
|
t.Errorf("Unexpected authorization header, %v is found.", headerAuth)
|
||||||
|
}
|
||||||
|
if got, want := r.Header.Get("Content-Type"), "application/x-www-form-urlencoded"; got != want {
|
||||||
|
t.Errorf("Content-Type header = %q; want %q", got, want)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
r.Body.Close()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("failed reading request body: %s.", err)
|
||||||
|
}
|
||||||
|
if string(body) != "client_id=CLIENT_ID&grant_type=client_credentials&scope=scope1+scope2" {
|
||||||
|
t.Errorf("payload = %q; want %q", string(body), "client_id=CLIENT_ID&grant_type=client_credentials&scope=scope1+scope2")
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
w.Write([]byte("access_token=90d64460d14870c08c81352a05dedd3465940a7c&token_type=bearer"))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
conf := newConf(ts.URL)
|
||||||
|
tok, err := conf.Token(oauth2.NoContext)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if !tok.Valid() {
|
||||||
|
t.Fatalf("token invalid. got: %#v", tok)
|
||||||
|
}
|
||||||
|
if tok.AccessToken != "90d64460d14870c08c81352a05dedd3465940a7c" {
|
||||||
|
t.Errorf("Access token = %q; want %q", tok.AccessToken, "90d64460d14870c08c81352a05dedd3465940a7c")
|
||||||
|
}
|
||||||
|
if tok.TokenType != "bearer" {
|
||||||
|
t.Errorf("token type = %q; want %q", tok.TokenType, "bearer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTokenRefreshRequest(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.String() == "/somethingelse" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.URL.String() != "/token" {
|
||||||
|
t.Errorf("Unexpected token refresh request URL, %v is found.", r.URL)
|
||||||
|
}
|
||||||
|
headerContentType := r.Header.Get("Content-Type")
|
||||||
|
if headerContentType != "application/x-www-form-urlencoded" {
|
||||||
|
t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType)
|
||||||
|
}
|
||||||
|
body, _ := ioutil.ReadAll(r.Body)
|
||||||
|
if string(body) != "client_id=CLIENT_ID&grant_type=client_credentials&scope=scope1+scope2" {
|
||||||
|
t.Errorf("Unexpected refresh token payload, %v is found.", string(body))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
conf := newConf(ts.URL)
|
||||||
|
c := conf.Client(oauth2.NoContext)
|
||||||
|
c.Get(ts.URL + "/somethingelse")
|
||||||
|
}
|
45
vendor/golang.org/x/oauth2/example_test.go
generated
vendored
Normal file
45
vendor/golang.org/x/oauth2/example_test.go
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package oauth2_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleConfig() {
|
||||||
|
conf := &oauth2.Config{
|
||||||
|
ClientID: "YOUR_CLIENT_ID",
|
||||||
|
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||||
|
Scopes: []string{"SCOPE1", "SCOPE2"},
|
||||||
|
Endpoint: oauth2.Endpoint{
|
||||||
|
AuthURL: "https://provider.com/o/oauth2/auth",
|
||||||
|
TokenURL: "https://provider.com/o/oauth2/token",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redirect user to consent page to ask for permission
|
||||||
|
// for the scopes specified above.
|
||||||
|
url := conf.AuthCodeURL("state", oauth2.AccessTypeOffline)
|
||||||
|
fmt.Printf("Visit the URL for the auth dialog: %v", url)
|
||||||
|
|
||||||
|
// Use the authorization code that is pushed to the redirect URL.
|
||||||
|
// NewTransportWithCode will do the handshake to retrieve
|
||||||
|
// an access token and initiate a Transport that is
|
||||||
|
// authorized and authenticated by the retrieved token.
|
||||||
|
var code string
|
||||||
|
if _, err := fmt.Scan(&code); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
tok, err := conf.Exchange(oauth2.NoContext, code)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := conf.Client(oauth2.NoContext, tok)
|
||||||
|
client.Get("...")
|
||||||
|
}
|
16
vendor/golang.org/x/oauth2/facebook/facebook.go
generated
vendored
Normal file
16
vendor/golang.org/x/oauth2/facebook/facebook.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2015 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package facebook provides constants for using OAuth2 to access Facebook.
|
||||||
|
package facebook // import "golang.org/x/oauth2/facebook"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Endpoint is Facebook's OAuth 2.0 endpoint.
|
||||||
|
var Endpoint = oauth2.Endpoint{
|
||||||
|
AuthURL: "https://www.facebook.com/dialog/oauth",
|
||||||
|
TokenURL: "https://graph.facebook.com/oauth/access_token",
|
||||||
|
}
|
16
vendor/golang.org/x/oauth2/github/github.go
generated
vendored
Normal file
16
vendor/golang.org/x/oauth2/github/github.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package github provides constants for using OAuth2 to access Github.
|
||||||
|
package github // import "golang.org/x/oauth2/github"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Endpoint is Github's OAuth 2.0 endpoint.
|
||||||
|
var Endpoint = oauth2.Endpoint{
|
||||||
|
AuthURL: "https://github.com/login/oauth/authorize",
|
||||||
|
TokenURL: "https://github.com/login/oauth/access_token",
|
||||||
|
}
|
86
vendor/golang.org/x/oauth2/google/appengine.go
generated
vendored
Normal file
86
vendor/golang.org/x/oauth2/google/appengine.go
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Set at init time by appenginevm_hook.go. If true, we are on App Engine Managed VMs.
|
||||||
|
var appengineVM bool
|
||||||
|
|
||||||
|
// Set at init time by appengine_hook.go. If nil, we're not on App Engine.
|
||||||
|
var appengineTokenFunc func(c context.Context, scopes ...string) (token string, expiry time.Time, err error)
|
||||||
|
|
||||||
|
// AppEngineTokenSource returns a token source that fetches tokens
|
||||||
|
// issued to the current App Engine application's service account.
|
||||||
|
// If you are implementing a 3-legged OAuth 2.0 flow on App Engine
|
||||||
|
// that involves user accounts, see oauth2.Config instead.
|
||||||
|
//
|
||||||
|
// The provided context must have come from appengine.NewContext.
|
||||||
|
func AppEngineTokenSource(ctx context.Context, scope ...string) oauth2.TokenSource {
|
||||||
|
if appengineTokenFunc == nil {
|
||||||
|
panic("google: AppEngineTokenSource can only be used on App Engine.")
|
||||||
|
}
|
||||||
|
scopes := append([]string{}, scope...)
|
||||||
|
sort.Strings(scopes)
|
||||||
|
return &appEngineTokenSource{
|
||||||
|
ctx: ctx,
|
||||||
|
scopes: scopes,
|
||||||
|
key: strings.Join(scopes, " "),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// aeTokens helps the fetched tokens to be reused until their expiration.
|
||||||
|
var (
|
||||||
|
aeTokensMu sync.Mutex
|
||||||
|
aeTokens = make(map[string]*tokenLock) // key is space-separated scopes
|
||||||
|
)
|
||||||
|
|
||||||
|
type tokenLock struct {
|
||||||
|
mu sync.Mutex // guards t; held while fetching or updating t
|
||||||
|
t *oauth2.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
type appEngineTokenSource struct {
|
||||||
|
ctx context.Context
|
||||||
|
scopes []string
|
||||||
|
key string // to aeTokens map; space-separated scopes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *appEngineTokenSource) Token() (*oauth2.Token, error) {
|
||||||
|
if appengineTokenFunc == nil {
|
||||||
|
panic("google: AppEngineTokenSource can only be used on App Engine.")
|
||||||
|
}
|
||||||
|
|
||||||
|
aeTokensMu.Lock()
|
||||||
|
tok, ok := aeTokens[ts.key]
|
||||||
|
if !ok {
|
||||||
|
tok = &tokenLock{}
|
||||||
|
aeTokens[ts.key] = tok
|
||||||
|
}
|
||||||
|
aeTokensMu.Unlock()
|
||||||
|
|
||||||
|
tok.mu.Lock()
|
||||||
|
defer tok.mu.Unlock()
|
||||||
|
if tok.t.Valid() {
|
||||||
|
return tok.t, nil
|
||||||
|
}
|
||||||
|
access, exp, err := appengineTokenFunc(ts.ctx, ts.scopes...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tok.t = &oauth2.Token{
|
||||||
|
AccessToken: access,
|
||||||
|
Expiry: exp,
|
||||||
|
}
|
||||||
|
return tok.t, nil
|
||||||
|
}
|
13
vendor/golang.org/x/oauth2/google/appengine_hook.go
generated
vendored
Normal file
13
vendor/golang.org/x/oauth2/google/appengine_hook.go
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// Copyright 2015 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build appengine
|
||||||
|
|
||||||
|
package google
|
||||||
|
|
||||||
|
import "google.golang.org/appengine"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
appengineTokenFunc = appengine.AccessToken
|
||||||
|
}
|
14
vendor/golang.org/x/oauth2/google/appenginevm_hook.go
generated
vendored
Normal file
14
vendor/golang.org/x/oauth2/google/appenginevm_hook.go
generated
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Copyright 2015 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build appenginevm
|
||||||
|
|
||||||
|
package google
|
||||||
|
|
||||||
|
import "google.golang.org/appengine"
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
appengineVM = true
|
||||||
|
appengineTokenFunc = appengine.AccessToken
|
||||||
|
}
|
155
vendor/golang.org/x/oauth2/google/default.go
generated
vendored
Normal file
155
vendor/golang.org/x/oauth2/google/default.go
generated
vendored
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
// Copyright 2015 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/jwt"
|
||||||
|
"google.golang.org/cloud/compute/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DefaultClient returns an HTTP Client that uses the
|
||||||
|
// DefaultTokenSource to obtain authentication credentials.
|
||||||
|
//
|
||||||
|
// This client should be used when developing services
|
||||||
|
// that run on Google App Engine or Google Compute Engine
|
||||||
|
// and use "Application Default Credentials."
|
||||||
|
//
|
||||||
|
// For more details, see:
|
||||||
|
// https://developers.google.com/accounts/docs/application-default-credentials
|
||||||
|
//
|
||||||
|
func DefaultClient(ctx context.Context, scope ...string) (*http.Client, error) {
|
||||||
|
ts, err := DefaultTokenSource(ctx, scope...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return oauth2.NewClient(ctx, ts), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultTokenSource is a token source that uses
|
||||||
|
// "Application Default Credentials".
|
||||||
|
//
|
||||||
|
// It looks for credentials in the following places,
|
||||||
|
// preferring the first location found:
|
||||||
|
//
|
||||||
|
// 1. A JSON file whose path is specified by the
|
||||||
|
// GOOGLE_APPLICATION_CREDENTIALS environment variable.
|
||||||
|
// 2. A JSON file in a location known to the gcloud command-line tool.
|
||||||
|
// On Windows, this is %APPDATA%/gcloud/application_default_credentials.json.
|
||||||
|
// On other systems, $HOME/.config/gcloud/application_default_credentials.json.
|
||||||
|
// 3. On Google App Engine it uses the appengine.AccessToken function.
|
||||||
|
// 4. On Google Compute Engine and Google App Engine Managed VMs, it fetches
|
||||||
|
// credentials from the metadata server.
|
||||||
|
// (In this final case any provided scopes are ignored.)
|
||||||
|
//
|
||||||
|
// For more details, see:
|
||||||
|
// https://developers.google.com/accounts/docs/application-default-credentials
|
||||||
|
//
|
||||||
|
func DefaultTokenSource(ctx context.Context, scope ...string) (oauth2.TokenSource, error) {
|
||||||
|
// First, try the environment variable.
|
||||||
|
const envVar = "GOOGLE_APPLICATION_CREDENTIALS"
|
||||||
|
if filename := os.Getenv(envVar); filename != "" {
|
||||||
|
ts, err := tokenSourceFromFile(ctx, filename, scope)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("google: error getting credentials using %v environment variable: %v", envVar, err)
|
||||||
|
}
|
||||||
|
return ts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second, try a well-known file.
|
||||||
|
filename := wellKnownFile()
|
||||||
|
_, err := os.Stat(filename)
|
||||||
|
if err == nil {
|
||||||
|
ts, err2 := tokenSourceFromFile(ctx, filename, scope)
|
||||||
|
if err2 == nil {
|
||||||
|
return ts, nil
|
||||||
|
}
|
||||||
|
err = err2
|
||||||
|
} else if os.IsNotExist(err) {
|
||||||
|
err = nil // ignore this error
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("google: error getting credentials using well-known file (%v): %v", filename, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Third, if we're on Google App Engine use those credentials.
|
||||||
|
if appengineTokenFunc != nil && !appengineVM {
|
||||||
|
return AppEngineTokenSource(ctx, scope...), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fourth, if we're on Google Compute Engine use the metadata server.
|
||||||
|
if metadata.OnGCE() {
|
||||||
|
return ComputeTokenSource(""), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// None are found; return helpful error.
|
||||||
|
const url = "https://developers.google.com/accounts/docs/application-default-credentials"
|
||||||
|
return nil, fmt.Errorf("google: could not find default credentials. See %v for more information.", url)
|
||||||
|
}
|
||||||
|
|
||||||
|
func wellKnownFile() string {
|
||||||
|
const f = "application_default_credentials.json"
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return filepath.Join(os.Getenv("APPDATA"), "gcloud", f)
|
||||||
|
}
|
||||||
|
return filepath.Join(guessUnixHomeDir(), ".config", "gcloud", f)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tokenSourceFromFile(ctx context.Context, filename string, scopes []string) (oauth2.TokenSource, error) {
|
||||||
|
b, err := ioutil.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var d struct {
|
||||||
|
// Common fields
|
||||||
|
Type string
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
|
||||||
|
// User Credential fields
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
|
||||||
|
// Service Account fields
|
||||||
|
ClientEmail string `json:"client_email"`
|
||||||
|
PrivateKeyID string `json:"private_key_id"`
|
||||||
|
PrivateKey string `json:"private_key"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(b, &d); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch d.Type {
|
||||||
|
case "authorized_user":
|
||||||
|
cfg := &oauth2.Config{
|
||||||
|
ClientID: d.ClientID,
|
||||||
|
ClientSecret: d.ClientSecret,
|
||||||
|
Scopes: append([]string{}, scopes...), // copy
|
||||||
|
Endpoint: Endpoint,
|
||||||
|
}
|
||||||
|
tok := &oauth2.Token{RefreshToken: d.RefreshToken}
|
||||||
|
return cfg.TokenSource(ctx, tok), nil
|
||||||
|
case "service_account":
|
||||||
|
cfg := &jwt.Config{
|
||||||
|
Email: d.ClientEmail,
|
||||||
|
PrivateKey: []byte(d.PrivateKey),
|
||||||
|
Scopes: append([]string{}, scopes...), // copy
|
||||||
|
TokenURL: JWTTokenURL,
|
||||||
|
}
|
||||||
|
return cfg.TokenSource(ctx), nil
|
||||||
|
case "":
|
||||||
|
return nil, errors.New("missing 'type' field in credentials")
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown credential type: %q", d.Type)
|
||||||
|
}
|
||||||
|
}
|
150
vendor/golang.org/x/oauth2/google/example_test.go
generated
vendored
Normal file
150
vendor/golang.org/x/oauth2/google/example_test.go
generated
vendored
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// +build appenginevm !appengine
|
||||||
|
|
||||||
|
package google_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/google"
|
||||||
|
"golang.org/x/oauth2/jwt"
|
||||||
|
"google.golang.org/appengine"
|
||||||
|
"google.golang.org/appengine/urlfetch"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleDefaultClient() {
|
||||||
|
client, err := google.DefaultClient(oauth2.NoContext,
|
||||||
|
"https://www.googleapis.com/auth/devstorage.full_control")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
client.Get("...")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_webServer() {
|
||||||
|
// Your credentials should be obtained from the Google
|
||||||
|
// Developer Console (https://console.developers.google.com).
|
||||||
|
conf := &oauth2.Config{
|
||||||
|
ClientID: "YOUR_CLIENT_ID",
|
||||||
|
ClientSecret: "YOUR_CLIENT_SECRET",
|
||||||
|
RedirectURL: "YOUR_REDIRECT_URL",
|
||||||
|
Scopes: []string{
|
||||||
|
"https://www.googleapis.com/auth/bigquery",
|
||||||
|
"https://www.googleapis.com/auth/blogger",
|
||||||
|
},
|
||||||
|
Endpoint: google.Endpoint,
|
||||||
|
}
|
||||||
|
// Redirect user to Google's consent page to ask for permission
|
||||||
|
// for the scopes specified above.
|
||||||
|
url := conf.AuthCodeURL("state")
|
||||||
|
fmt.Printf("Visit the URL for the auth dialog: %v", url)
|
||||||
|
|
||||||
|
// Handle the exchange code to initiate a transport.
|
||||||
|
tok, err := conf.Exchange(oauth2.NoContext, "authorization-code")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
client := conf.Client(oauth2.NoContext, tok)
|
||||||
|
client.Get("...")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleJWTConfigFromJSON() {
|
||||||
|
// Your credentials should be obtained from the Google
|
||||||
|
// Developer Console (https://console.developers.google.com).
|
||||||
|
// Navigate to your project, then see the "Credentials" page
|
||||||
|
// under "APIs & Auth".
|
||||||
|
// To create a service account client, click "Create new Client ID",
|
||||||
|
// select "Service Account", and click "Create Client ID". A JSON
|
||||||
|
// key file will then be downloaded to your computer.
|
||||||
|
data, err := ioutil.ReadFile("/path/to/your-project-key.json")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
conf, err := google.JWTConfigFromJSON(data, "https://www.googleapis.com/auth/bigquery")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
// Initiate an http.Client. The following GET request will be
|
||||||
|
// authorized and authenticated on the behalf of
|
||||||
|
// your service account.
|
||||||
|
client := conf.Client(oauth2.NoContext)
|
||||||
|
client.Get("...")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleSDKConfig() {
|
||||||
|
// The credentials will be obtained from the first account that
|
||||||
|
// has been authorized with `gcloud auth login`.
|
||||||
|
conf, err := google.NewSDKConfig("")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
// Initiate an http.Client. The following GET request will be
|
||||||
|
// authorized and authenticated on the behalf of the SDK user.
|
||||||
|
client := conf.Client(oauth2.NoContext)
|
||||||
|
client.Get("...")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Example_serviceAccount() {
|
||||||
|
// Your credentials should be obtained from the Google
|
||||||
|
// Developer Console (https://console.developers.google.com).
|
||||||
|
conf := &jwt.Config{
|
||||||
|
Email: "xxx@developer.gserviceaccount.com",
|
||||||
|
// The contents of your RSA private key or your PEM file
|
||||||
|
// that contains a private key.
|
||||||
|
// If you have a p12 file instead, you
|
||||||
|
// can use `openssl` to export the private key into a pem file.
|
||||||
|
//
|
||||||
|
// $ openssl pkcs12 -in key.p12 -passin pass:notasecret -out key.pem -nodes
|
||||||
|
//
|
||||||
|
// The field only supports PEM containers with no passphrase.
|
||||||
|
// The openssl command will convert p12 keys to passphrase-less PEM containers.
|
||||||
|
PrivateKey: []byte("-----BEGIN RSA PRIVATE KEY-----..."),
|
||||||
|
Scopes: []string{
|
||||||
|
"https://www.googleapis.com/auth/bigquery",
|
||||||
|
"https://www.googleapis.com/auth/blogger",
|
||||||
|
},
|
||||||
|
TokenURL: google.JWTTokenURL,
|
||||||
|
// If you would like to impersonate a user, you can
|
||||||
|
// create a transport with a subject. The following GET
|
||||||
|
// request will be made on the behalf of user@example.com.
|
||||||
|
// Optional.
|
||||||
|
Subject: "user@example.com",
|
||||||
|
}
|
||||||
|
// Initiate an http.Client, the following GET request will be
|
||||||
|
// authorized and authenticated on the behalf of user@example.com.
|
||||||
|
client := conf.Client(oauth2.NoContext)
|
||||||
|
client.Get("...")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleAppEngineTokenSource() {
|
||||||
|
var req *http.Request // from the ServeHTTP handler
|
||||||
|
ctx := appengine.NewContext(req)
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: &oauth2.Transport{
|
||||||
|
Source: google.AppEngineTokenSource(ctx, "https://www.googleapis.com/auth/bigquery"),
|
||||||
|
Base: &urlfetch.Transport{
|
||||||
|
Context: ctx,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client.Get("...")
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleComputeTokenSource() {
|
||||||
|
client := &http.Client{
|
||||||
|
Transport: &oauth2.Transport{
|
||||||
|
// Fetch from Google Compute Engine's metadata server to retrieve
|
||||||
|
// an access token for the provided account.
|
||||||
|
// If no account is specified, "default" is used.
|
||||||
|
Source: google.ComputeTokenSource(""),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
client.Get("...")
|
||||||
|
}
|
145
vendor/golang.org/x/oauth2/google/google.go
generated
vendored
Normal file
145
vendor/golang.org/x/oauth2/google/google.go
generated
vendored
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package google provides support for making OAuth2 authorized and
|
||||||
|
// authenticated HTTP requests to Google APIs.
|
||||||
|
// It supports the Web server flow, client-side credentials, service accounts,
|
||||||
|
// Google Compute Engine service accounts, and Google App Engine service
|
||||||
|
// accounts.
|
||||||
|
//
|
||||||
|
// For more information, please read
|
||||||
|
// https://developers.google.com/accounts/docs/OAuth2
|
||||||
|
// and
|
||||||
|
// https://developers.google.com/accounts/docs/application-default-credentials.
|
||||||
|
package google // import "golang.org/x/oauth2/google"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/jwt"
|
||||||
|
"google.golang.org/cloud/compute/metadata"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Endpoint is Google's OAuth 2.0 endpoint.
|
||||||
|
var Endpoint = oauth2.Endpoint{
|
||||||
|
AuthURL: "https://accounts.google.com/o/oauth2/auth",
|
||||||
|
TokenURL: "https://accounts.google.com/o/oauth2/token",
|
||||||
|
}
|
||||||
|
|
||||||
|
// JWTTokenURL is Google's OAuth 2.0 token URL to use with the JWT flow.
|
||||||
|
const JWTTokenURL = "https://accounts.google.com/o/oauth2/token"
|
||||||
|
|
||||||
|
// ConfigFromJSON uses a Google Developers Console client_credentials.json
|
||||||
|
// file to construct a config.
|
||||||
|
// client_credentials.json can be downloadable from https://console.developers.google.com,
|
||||||
|
// under "APIs & Auth" > "Credentials". Download the Web application credentials in the
|
||||||
|
// JSON format and provide the contents of the file as jsonKey.
|
||||||
|
func ConfigFromJSON(jsonKey []byte, scope ...string) (*oauth2.Config, error) {
|
||||||
|
type cred struct {
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
RedirectURIs []string `json:"redirect_uris"`
|
||||||
|
AuthURI string `json:"auth_uri"`
|
||||||
|
TokenURI string `json:"token_uri"`
|
||||||
|
}
|
||||||
|
var j struct {
|
||||||
|
Web *cred `json:"web"`
|
||||||
|
Installed *cred `json:"installed"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(jsonKey, &j); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var c *cred
|
||||||
|
switch {
|
||||||
|
case j.Web != nil:
|
||||||
|
c = j.Web
|
||||||
|
case j.Installed != nil:
|
||||||
|
c = j.Installed
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("oauth2/google: no credentials found")
|
||||||
|
}
|
||||||
|
if len(c.RedirectURIs) < 1 {
|
||||||
|
return nil, errors.New("oauth2/google: missing redirect URL in the client_credentials.json")
|
||||||
|
}
|
||||||
|
return &oauth2.Config{
|
||||||
|
ClientID: c.ClientID,
|
||||||
|
ClientSecret: c.ClientSecret,
|
||||||
|
RedirectURL: c.RedirectURIs[0],
|
||||||
|
Scopes: scope,
|
||||||
|
Endpoint: oauth2.Endpoint{
|
||||||
|
AuthURL: c.AuthURI,
|
||||||
|
TokenURL: c.TokenURI,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// JWTConfigFromJSON uses a Google Developers service account JSON key file to read
|
||||||
|
// the credentials that authorize and authenticate the requests.
|
||||||
|
// Create a service account on "Credentials" page under "APIs & Auth" for your
|
||||||
|
// project at https://console.developers.google.com to download a JSON key file.
|
||||||
|
func JWTConfigFromJSON(jsonKey []byte, scope ...string) (*jwt.Config, error) {
|
||||||
|
var key struct {
|
||||||
|
Email string `json:"client_email"`
|
||||||
|
PrivateKey string `json:"private_key"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(jsonKey, &key); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &jwt.Config{
|
||||||
|
Email: key.Email,
|
||||||
|
PrivateKey: []byte(key.PrivateKey),
|
||||||
|
Scopes: scope,
|
||||||
|
TokenURL: JWTTokenURL,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComputeTokenSource returns a token source that fetches access tokens
|
||||||
|
// from Google Compute Engine (GCE)'s metadata server. It's only valid to use
|
||||||
|
// this token source if your program is running on a GCE instance.
|
||||||
|
// If no account is specified, "default" is used.
|
||||||
|
// Further information about retrieving access tokens from the GCE metadata
|
||||||
|
// server can be found at https://cloud.google.com/compute/docs/authentication.
|
||||||
|
func ComputeTokenSource(account string) oauth2.TokenSource {
|
||||||
|
return oauth2.ReuseTokenSource(nil, computeSource{account: account})
|
||||||
|
}
|
||||||
|
|
||||||
|
type computeSource struct {
|
||||||
|
account string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cs computeSource) Token() (*oauth2.Token, error) {
|
||||||
|
if !metadata.OnGCE() {
|
||||||
|
return nil, errors.New("oauth2/google: can't get a token from the metadata service; not running on GCE")
|
||||||
|
}
|
||||||
|
acct := cs.account
|
||||||
|
if acct == "" {
|
||||||
|
acct = "default"
|
||||||
|
}
|
||||||
|
tokenJSON, err := metadata.Get("instance/service-accounts/" + acct + "/token")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var res struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresInSec int `json:"expires_in"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
}
|
||||||
|
err = json.NewDecoder(strings.NewReader(tokenJSON)).Decode(&res)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: invalid token JSON from metadata: %v", err)
|
||||||
|
}
|
||||||
|
if res.ExpiresInSec == 0 || res.AccessToken == "" {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: incomplete token received from metadata")
|
||||||
|
}
|
||||||
|
return &oauth2.Token{
|
||||||
|
AccessToken: res.AccessToken,
|
||||||
|
TokenType: res.TokenType,
|
||||||
|
Expiry: time.Now().Add(time.Duration(res.ExpiresInSec) * time.Second),
|
||||||
|
}, nil
|
||||||
|
}
|
67
vendor/golang.org/x/oauth2/google/google_test.go
generated
vendored
Normal file
67
vendor/golang.org/x/oauth2/google/google_test.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright 2015 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var webJSONKey = []byte(`
|
||||||
|
{
|
||||||
|
"web": {
|
||||||
|
"auth_uri": "https://google.com/o/oauth2/auth",
|
||||||
|
"client_secret": "3Oknc4jS_wA2r9i",
|
||||||
|
"token_uri": "https://google.com/o/oauth2/token",
|
||||||
|
"client_email": "222-nprqovg5k43uum874cs9osjt2koe97g8@developer.gserviceaccount.com",
|
||||||
|
"redirect_uris": ["https://www.example.com/oauth2callback"],
|
||||||
|
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/222-nprqovg5k43uum874cs9osjt2koe97g8@developer.gserviceaccount.com",
|
||||||
|
"client_id": "222-nprqovg5k43uum874cs9osjt2koe97g8.apps.googleusercontent.com",
|
||||||
|
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||||
|
"javascript_origins": ["https://www.example.com"]
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
var installedJSONKey = []byte(`{
|
||||||
|
"installed": {
|
||||||
|
"client_id": "222-installed.apps.googleusercontent.com",
|
||||||
|
"redirect_uris": ["https://www.example.com/oauth2callback"]
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
|
||||||
|
func TestConfigFromJSON(t *testing.T) {
|
||||||
|
conf, err := ConfigFromJSON(webJSONKey, "scope1", "scope2")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if got, want := conf.ClientID, "222-nprqovg5k43uum874cs9osjt2koe97g8.apps.googleusercontent.com"; got != want {
|
||||||
|
t.Errorf("ClientID = %q; want %q", got, want)
|
||||||
|
}
|
||||||
|
if got, want := conf.ClientSecret, "3Oknc4jS_wA2r9i"; got != want {
|
||||||
|
t.Errorf("ClientSecret = %q; want %q", got, want)
|
||||||
|
}
|
||||||
|
if got, want := conf.RedirectURL, "https://www.example.com/oauth2callback"; got != want {
|
||||||
|
t.Errorf("RedictURL = %q; want %q", got, want)
|
||||||
|
}
|
||||||
|
if got, want := strings.Join(conf.Scopes, ","), "scope1,scope2"; got != want {
|
||||||
|
t.Errorf("Scopes = %q; want %q", got, want)
|
||||||
|
}
|
||||||
|
if got, want := conf.Endpoint.AuthURL, "https://google.com/o/oauth2/auth"; got != want {
|
||||||
|
t.Errorf("AuthURL = %q; want %q", got, want)
|
||||||
|
}
|
||||||
|
if got, want := conf.Endpoint.TokenURL, "https://google.com/o/oauth2/token"; got != want {
|
||||||
|
t.Errorf("TokenURL = %q; want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigFromJSON_Installed(t *testing.T) {
|
||||||
|
conf, err := ConfigFromJSON(installedJSONKey)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if got, want := conf.ClientID, "222-installed.apps.googleusercontent.com"; got != want {
|
||||||
|
t.Errorf("ClientID = %q; want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
71
vendor/golang.org/x/oauth2/google/jwt.go
generated
vendored
Normal file
71
vendor/golang.org/x/oauth2/google/jwt.go
generated
vendored
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
// Copyright 2015 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rsa"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/internal"
|
||||||
|
"golang.org/x/oauth2/jws"
|
||||||
|
)
|
||||||
|
|
||||||
|
// JWTAccessTokenSourceFromJSON uses a Google Developers service account JSON
|
||||||
|
// key file to read the credentials that authorize and authenticate the
|
||||||
|
// requests, and returns a TokenSource that does not use any OAuth2 flow but
|
||||||
|
// instead creates a JWT and sends that as the access token.
|
||||||
|
// The audience is typically a URL that specifies the scope of the credentials.
|
||||||
|
//
|
||||||
|
// Note that this is not a standard OAuth flow, but rather an
|
||||||
|
// optimization supported by a few Google services.
|
||||||
|
// Unless you know otherwise, you should use JWTConfigFromJSON instead.
|
||||||
|
func JWTAccessTokenSourceFromJSON(jsonKey []byte, audience string) (oauth2.TokenSource, error) {
|
||||||
|
cfg, err := JWTConfigFromJSON(jsonKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("google: could not parse JSON key: %v", err)
|
||||||
|
}
|
||||||
|
pk, err := internal.ParseKey(cfg.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("google: could not parse key: %v", err)
|
||||||
|
}
|
||||||
|
ts := &jwtAccessTokenSource{
|
||||||
|
email: cfg.Email,
|
||||||
|
audience: audience,
|
||||||
|
pk: pk,
|
||||||
|
}
|
||||||
|
tok, err := ts.Token()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return oauth2.ReuseTokenSource(tok, ts), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type jwtAccessTokenSource struct {
|
||||||
|
email, audience string
|
||||||
|
pk *rsa.PrivateKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ts *jwtAccessTokenSource) Token() (*oauth2.Token, error) {
|
||||||
|
iat := time.Now()
|
||||||
|
exp := iat.Add(time.Hour)
|
||||||
|
cs := &jws.ClaimSet{
|
||||||
|
Iss: ts.email,
|
||||||
|
Sub: ts.email,
|
||||||
|
Aud: ts.audience,
|
||||||
|
Iat: iat.Unix(),
|
||||||
|
Exp: exp.Unix(),
|
||||||
|
}
|
||||||
|
hdr := &jws.Header{
|
||||||
|
Algorithm: "RS256",
|
||||||
|
Typ: "JWT",
|
||||||
|
}
|
||||||
|
msg, err := jws.Encode(hdr, cs, ts.pk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("google: could not encode JWT: %v", err)
|
||||||
|
}
|
||||||
|
return &oauth2.Token{AccessToken: msg, TokenType: "Bearer", Expiry: exp}, nil
|
||||||
|
}
|
168
vendor/golang.org/x/oauth2/google/sdk.go
generated
vendored
Normal file
168
vendor/golang.org/x/oauth2/google/sdk.go
generated
vendored
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
// Copyright 2015 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package google
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sdkCredentials struct {
|
||||||
|
Data []struct {
|
||||||
|
Credential struct {
|
||||||
|
ClientID string `json:"client_id"`
|
||||||
|
ClientSecret string `json:"client_secret"`
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
TokenExpiry *time.Time `json:"token_expiry"`
|
||||||
|
} `json:"credential"`
|
||||||
|
Key struct {
|
||||||
|
Account string `json:"account"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
} `json:"key"`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An SDKConfig provides access to tokens from an account already
|
||||||
|
// authorized via the Google Cloud SDK.
|
||||||
|
type SDKConfig struct {
|
||||||
|
conf oauth2.Config
|
||||||
|
initialToken *oauth2.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSDKConfig creates an SDKConfig for the given Google Cloud SDK
|
||||||
|
// account. If account is empty, the account currently active in
|
||||||
|
// Google Cloud SDK properties is used.
|
||||||
|
// Google Cloud SDK credentials must be created by running `gcloud auth`
|
||||||
|
// before using this function.
|
||||||
|
// The Google Cloud SDK is available at https://cloud.google.com/sdk/.
|
||||||
|
func NewSDKConfig(account string) (*SDKConfig, error) {
|
||||||
|
configPath, err := sdkConfigPath()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: error getting SDK config path: %v", err)
|
||||||
|
}
|
||||||
|
credentialsPath := filepath.Join(configPath, "credentials")
|
||||||
|
f, err := os.Open(credentialsPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: failed to load SDK credentials: %v", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
var c sdkCredentials
|
||||||
|
if err := json.NewDecoder(f).Decode(&c); err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: failed to decode SDK credentials from %q: %v", credentialsPath, err)
|
||||||
|
}
|
||||||
|
if len(c.Data) == 0 {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: no credentials found in %q, run `gcloud auth login` to create one", credentialsPath)
|
||||||
|
}
|
||||||
|
if account == "" {
|
||||||
|
propertiesPath := filepath.Join(configPath, "properties")
|
||||||
|
f, err := os.Open(propertiesPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: failed to load SDK properties: %v", err)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
ini, err := internal.ParseINI(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: failed to parse SDK properties %q: %v", propertiesPath, err)
|
||||||
|
}
|
||||||
|
core, ok := ini["core"]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: failed to find [core] section in %v", ini)
|
||||||
|
}
|
||||||
|
active, ok := core["account"]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: failed to find %q attribute in %v", "account", core)
|
||||||
|
}
|
||||||
|
account = active
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, d := range c.Data {
|
||||||
|
if account == "" || d.Key.Account == account {
|
||||||
|
if d.Credential.AccessToken == "" && d.Credential.RefreshToken == "" {
|
||||||
|
return nil, fmt.Errorf("oauth2/google: no token available for account %q", account)
|
||||||
|
}
|
||||||
|
var expiry time.Time
|
||||||
|
if d.Credential.TokenExpiry != nil {
|
||||||
|
expiry = *d.Credential.TokenExpiry
|
||||||
|
}
|
||||||
|
return &SDKConfig{
|
||||||
|
conf: oauth2.Config{
|
||||||
|
ClientID: d.Credential.ClientID,
|
||||||
|
ClientSecret: d.Credential.ClientSecret,
|
||||||
|
Scopes: strings.Split(d.Key.Scope, " "),
|
||||||
|
Endpoint: Endpoint,
|
||||||
|
RedirectURL: "oob",
|
||||||
|
},
|
||||||
|
initialToken: &oauth2.Token{
|
||||||
|
AccessToken: d.Credential.AccessToken,
|
||||||
|
RefreshToken: d.Credential.RefreshToken,
|
||||||
|
Expiry: expiry,
|
||||||
|
},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("oauth2/google: no such credentials for account %q", account)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client returns an HTTP client using Google Cloud SDK credentials to
|
||||||
|
// authorize requests. The token will auto-refresh as necessary. The
|
||||||
|
// underlying http.RoundTripper will be obtained using the provided
|
||||||
|
// context. The returned client and its Transport should not be
|
||||||
|
// modified.
|
||||||
|
func (c *SDKConfig) Client(ctx context.Context) *http.Client {
|
||||||
|
return &http.Client{
|
||||||
|
Transport: &oauth2.Transport{
|
||||||
|
Source: c.TokenSource(ctx),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenSource returns an oauth2.TokenSource that retrieve tokens from
|
||||||
|
// Google Cloud SDK credentials using the provided context.
|
||||||
|
// It will returns the current access token stored in the credentials,
|
||||||
|
// and refresh it when it expires, but it won't update the credentials
|
||||||
|
// with the new access token.
|
||||||
|
func (c *SDKConfig) TokenSource(ctx context.Context) oauth2.TokenSource {
|
||||||
|
return c.conf.TokenSource(ctx, c.initialToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scopes are the OAuth 2.0 scopes the current account is authorized for.
|
||||||
|
func (c *SDKConfig) Scopes() []string {
|
||||||
|
return c.conf.Scopes
|
||||||
|
}
|
||||||
|
|
||||||
|
// sdkConfigPath tries to guess where the gcloud config is located.
|
||||||
|
// It can be overridden during tests.
|
||||||
|
var sdkConfigPath = func() (string, error) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
return filepath.Join(os.Getenv("APPDATA"), "gcloud"), nil
|
||||||
|
}
|
||||||
|
homeDir := guessUnixHomeDir()
|
||||||
|
if homeDir == "" {
|
||||||
|
return "", errors.New("unable to get current user home directory: os/user lookup failed; $HOME is empty")
|
||||||
|
}
|
||||||
|
return filepath.Join(homeDir, ".config", "gcloud"), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func guessUnixHomeDir() string {
|
||||||
|
usr, err := user.Current()
|
||||||
|
if err == nil {
|
||||||
|
return usr.HomeDir
|
||||||
|
}
|
||||||
|
return os.Getenv("HOME")
|
||||||
|
}
|
46
vendor/golang.org/x/oauth2/google/sdk_test.go
generated
vendored
Normal file
46
vendor/golang.org/x/oauth2/google/sdk_test.go
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
// Copyright 2015 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package google
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestSDKConfig(t *testing.T) {
|
||||||
|
sdkConfigPath = func() (string, error) {
|
||||||
|
return "testdata/gcloud", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
account string
|
||||||
|
accessToken string
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{"", "bar_access_token", false},
|
||||||
|
{"foo@example.com", "foo_access_token", false},
|
||||||
|
{"bar@example.com", "bar_access_token", false},
|
||||||
|
{"baz@serviceaccount.example.com", "", true},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
c, err := NewSDKConfig(tt.account)
|
||||||
|
if got, want := err != nil, tt.err; got != want {
|
||||||
|
if !tt.err {
|
||||||
|
t.Errorf("expected no error, got error: %v", tt.err, err)
|
||||||
|
} else {
|
||||||
|
t.Errorf("expected error, got none")
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tok := c.initialToken
|
||||||
|
if tok == nil {
|
||||||
|
t.Errorf("expected token %q, got: nil", tt.accessToken)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tok.AccessToken != tt.accessToken {
|
||||||
|
t.Errorf("expected token %q, got: %q", tt.accessToken, tok.AccessToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
76
vendor/golang.org/x/oauth2/internal/oauth2.go
generated
vendored
Normal file
76
vendor/golang.org/x/oauth2/internal/oauth2.go
generated
vendored
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package internal contains support packages for oauth2 package.
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParseKey converts the binary contents of a private key file
|
||||||
|
// to an *rsa.PrivateKey. It detects whether the private key is in a
|
||||||
|
// PEM container or not. If so, it extracts the the private key
|
||||||
|
// from PEM container before conversion. It only supports PEM
|
||||||
|
// containers with no passphrase.
|
||||||
|
func ParseKey(key []byte) (*rsa.PrivateKey, error) {
|
||||||
|
block, _ := pem.Decode(key)
|
||||||
|
if block != nil {
|
||||||
|
key = block.Bytes
|
||||||
|
}
|
||||||
|
parsedKey, err := x509.ParsePKCS8PrivateKey(key)
|
||||||
|
if err != nil {
|
||||||
|
parsedKey, err = x509.ParsePKCS1PrivateKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("private key should be a PEM or plain PKSC1 or PKCS8; parse error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parsed, ok := parsedKey.(*rsa.PrivateKey)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("private key is invalid")
|
||||||
|
}
|
||||||
|
return parsed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseINI(ini io.Reader) (map[string]map[string]string, error) {
|
||||||
|
result := map[string]map[string]string{
|
||||||
|
"": map[string]string{}, // root section
|
||||||
|
}
|
||||||
|
scanner := bufio.NewScanner(ini)
|
||||||
|
currentSection := ""
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
if strings.HasPrefix(line, ";") {
|
||||||
|
// comment.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
|
||||||
|
currentSection = strings.TrimSpace(line[1 : len(line)-1])
|
||||||
|
result[currentSection] = map[string]string{}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
parts := strings.SplitN(line, "=", 2)
|
||||||
|
if len(parts) == 2 && parts[0] != "" {
|
||||||
|
result[currentSection][strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
return nil, fmt.Errorf("error scanning ini: %v", err)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CondVal(v string) []string {
|
||||||
|
if v == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return []string{v}
|
||||||
|
}
|
62
vendor/golang.org/x/oauth2/internal/oauth2_test.go
generated
vendored
Normal file
62
vendor/golang.org/x/oauth2/internal/oauth2_test.go
generated
vendored
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package internal contains support packages for oauth2 package.
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseINI(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
ini string
|
||||||
|
want map[string]map[string]string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
`root = toor
|
||||||
|
[foo]
|
||||||
|
bar = hop
|
||||||
|
ini = nin
|
||||||
|
`,
|
||||||
|
map[string]map[string]string{
|
||||||
|
"": map[string]string{"root": "toor"},
|
||||||
|
"foo": map[string]string{"bar": "hop", "ini": "nin"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`[empty]
|
||||||
|
[section]
|
||||||
|
empty=
|
||||||
|
`,
|
||||||
|
map[string]map[string]string{
|
||||||
|
"": map[string]string{},
|
||||||
|
"empty": map[string]string{},
|
||||||
|
"section": map[string]string{"empty": ""},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`ignore
|
||||||
|
[invalid
|
||||||
|
=stuff
|
||||||
|
;comment=true
|
||||||
|
`,
|
||||||
|
map[string]map[string]string{
|
||||||
|
"": map[string]string{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
result, err := ParseINI(strings.NewReader(tt.ini))
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ParseINI(%q) error %v, want: no error", tt.ini, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(result, tt.want) {
|
||||||
|
t.Errorf("ParseINI(%q) = %#v, want: %#v", tt.ini, result, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
214
vendor/golang.org/x/oauth2/internal/token.go
generated
vendored
Normal file
214
vendor/golang.org/x/oauth2/internal/token.go
generated
vendored
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package internal contains support packages for oauth2 package.
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Token represents the crendentials used to authorize
|
||||||
|
// the requests to access protected resources on the OAuth 2.0
|
||||||
|
// provider's backend.
|
||||||
|
//
|
||||||
|
// This type is a mirror of oauth2.Token and exists to break
|
||||||
|
// an otherwise-circular dependency. Other internal packages
|
||||||
|
// should convert this Token into an oauth2.Token before use.
|
||||||
|
type Token struct {
|
||||||
|
// AccessToken is the token that authorizes and authenticates
|
||||||
|
// the requests.
|
||||||
|
AccessToken string
|
||||||
|
|
||||||
|
// TokenType is the type of token.
|
||||||
|
// The Type method returns either this or "Bearer", the default.
|
||||||
|
TokenType string
|
||||||
|
|
||||||
|
// RefreshToken is a token that's used by the application
|
||||||
|
// (as opposed to the user) to refresh the access token
|
||||||
|
// if it expires.
|
||||||
|
RefreshToken string
|
||||||
|
|
||||||
|
// Expiry is the optional expiration time of the access token.
|
||||||
|
//
|
||||||
|
// If zero, TokenSource implementations will reuse the same
|
||||||
|
// token forever and RefreshToken or equivalent
|
||||||
|
// mechanisms for that TokenSource will not be used.
|
||||||
|
Expiry time.Time
|
||||||
|
|
||||||
|
// Raw optionally contains extra metadata from the server
|
||||||
|
// when updating a token.
|
||||||
|
Raw interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tokenJSON is the struct representing the HTTP response from OAuth2
|
||||||
|
// providers returning a token in JSON form.
|
||||||
|
type tokenJSON struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
ExpiresIn expirationTime `json:"expires_in"` // at least PayPal returns string, while most return number
|
||||||
|
Expires expirationTime `json:"expires"` // broken Facebook spelling of expires_in
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *tokenJSON) expiry() (t time.Time) {
|
||||||
|
if v := e.ExpiresIn; v != 0 {
|
||||||
|
return time.Now().Add(time.Duration(v) * time.Second)
|
||||||
|
}
|
||||||
|
if v := e.Expires; v != 0 {
|
||||||
|
return time.Now().Add(time.Duration(v) * time.Second)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type expirationTime int32
|
||||||
|
|
||||||
|
func (e *expirationTime) UnmarshalJSON(b []byte) error {
|
||||||
|
var n json.Number
|
||||||
|
err := json.Unmarshal(b, &n)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i, err := n.Int64()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*e = expirationTime(i)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var brokenAuthHeaderProviders = []string{
|
||||||
|
"https://accounts.google.com/",
|
||||||
|
"https://www.googleapis.com/",
|
||||||
|
"https://api.instagram.com/",
|
||||||
|
"https://www.douban.com/",
|
||||||
|
"https://api.dropbox.com/",
|
||||||
|
"https://api.soundcloud.com/",
|
||||||
|
"https://www.linkedin.com/",
|
||||||
|
"https://api.twitch.tv/",
|
||||||
|
"https://oauth.vk.com/",
|
||||||
|
"https://api.odnoklassniki.ru/",
|
||||||
|
"https://connect.stripe.com/",
|
||||||
|
"https://api.pushbullet.com/",
|
||||||
|
"https://oauth.sandbox.trainingpeaks.com/",
|
||||||
|
"https://oauth.trainingpeaks.com/",
|
||||||
|
"https://www.strava.com/oauth/",
|
||||||
|
"https://app.box.com/",
|
||||||
|
"https://test-sandbox.auth.corp.google.com",
|
||||||
|
"https://user.gini.net/",
|
||||||
|
"https://api.netatmo.net/",
|
||||||
|
"https://slack.com/",
|
||||||
|
}
|
||||||
|
|
||||||
|
// providerAuthHeaderWorks reports whether the OAuth2 server identified by the tokenURL
|
||||||
|
// implements the OAuth2 spec correctly
|
||||||
|
// See https://code.google.com/p/goauth2/issues/detail?id=31 for background.
|
||||||
|
// In summary:
|
||||||
|
// - Reddit only accepts client secret in the Authorization header
|
||||||
|
// - Dropbox accepts either it in URL param or Auth header, but not both.
|
||||||
|
// - Google only accepts URL param (not spec compliant?), not Auth header
|
||||||
|
// - Stripe only accepts client secret in Auth header with Bearer method, not Basic
|
||||||
|
func providerAuthHeaderWorks(tokenURL string) bool {
|
||||||
|
for _, s := range brokenAuthHeaderProviders {
|
||||||
|
if strings.HasPrefix(tokenURL, s) {
|
||||||
|
// Some sites fail to implement the OAuth2 spec fully.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume the provider implements the spec properly
|
||||||
|
// otherwise. We can add more exceptions as they're
|
||||||
|
// discovered. We will _not_ be adding configurable hooks
|
||||||
|
// to this package to let users select server bugs.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func RetrieveToken(ctx context.Context, ClientID, ClientSecret, TokenURL string, v url.Values) (*Token, error) {
|
||||||
|
hc, err := ContextClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
v.Set("client_id", ClientID)
|
||||||
|
bustedAuth := !providerAuthHeaderWorks(TokenURL)
|
||||||
|
if bustedAuth && ClientSecret != "" {
|
||||||
|
v.Set("client_secret", ClientSecret)
|
||||||
|
}
|
||||||
|
req, err := http.NewRequest("POST", TokenURL, strings.NewReader(v.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
if !bustedAuth {
|
||||||
|
req.SetBasicAuth(ClientID, ClientSecret)
|
||||||
|
}
|
||||||
|
r, err := hc.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
|
||||||
|
}
|
||||||
|
if code := r.StatusCode; code < 200 || code > 299 {
|
||||||
|
return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", r.Status, body)
|
||||||
|
}
|
||||||
|
|
||||||
|
var token *Token
|
||||||
|
content, _, _ := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
||||||
|
switch content {
|
||||||
|
case "application/x-www-form-urlencoded", "text/plain":
|
||||||
|
vals, err := url.ParseQuery(string(body))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
token = &Token{
|
||||||
|
AccessToken: vals.Get("access_token"),
|
||||||
|
TokenType: vals.Get("token_type"),
|
||||||
|
RefreshToken: vals.Get("refresh_token"),
|
||||||
|
Raw: vals,
|
||||||
|
}
|
||||||
|
e := vals.Get("expires_in")
|
||||||
|
if e == "" {
|
||||||
|
// TODO(jbd): Facebook's OAuth2 implementation is broken and
|
||||||
|
// returns expires_in field in expires. Remove the fallback to expires,
|
||||||
|
// when Facebook fixes their implementation.
|
||||||
|
e = vals.Get("expires")
|
||||||
|
}
|
||||||
|
expires, _ := strconv.Atoi(e)
|
||||||
|
if expires != 0 {
|
||||||
|
token.Expiry = time.Now().Add(time.Duration(expires) * time.Second)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
var tj tokenJSON
|
||||||
|
if err = json.Unmarshal(body, &tj); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
token = &Token{
|
||||||
|
AccessToken: tj.AccessToken,
|
||||||
|
TokenType: tj.TokenType,
|
||||||
|
RefreshToken: tj.RefreshToken,
|
||||||
|
Expiry: tj.expiry(),
|
||||||
|
Raw: make(map[string]interface{}),
|
||||||
|
}
|
||||||
|
json.Unmarshal(body, &token.Raw) // no error checks for optional fields
|
||||||
|
}
|
||||||
|
// Don't overwrite `RefreshToken` with an empty value
|
||||||
|
// if this was a token refreshing request.
|
||||||
|
if token.RefreshToken == "" {
|
||||||
|
token.RefreshToken = v.Get("refresh_token")
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
}
|
28
vendor/golang.org/x/oauth2/internal/token_test.go
generated
vendored
Normal file
28
vendor/golang.org/x/oauth2/internal/token_test.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package internal contains support packages for oauth2 package.
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_providerAuthHeaderWorks(t *testing.T) {
|
||||||
|
for _, p := range brokenAuthHeaderProviders {
|
||||||
|
if providerAuthHeaderWorks(p) {
|
||||||
|
t.Errorf("URL: %s not found in list", p)
|
||||||
|
}
|
||||||
|
p := fmt.Sprintf("%ssomesuffix", p)
|
||||||
|
if providerAuthHeaderWorks(p) {
|
||||||
|
t.Errorf("URL: %s not found in list", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p := "https://api.not-in-the-list-example.com/"
|
||||||
|
if !providerAuthHeaderWorks(p) {
|
||||||
|
t.Errorf("URL: %s found in list", p)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
67
vendor/golang.org/x/oauth2/internal/transport.go
generated
vendored
Normal file
67
vendor/golang.org/x/oauth2/internal/transport.go
generated
vendored
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package internal contains support packages for oauth2 package.
|
||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPClient is the context key to use with golang.org/x/net/context's
|
||||||
|
// WithValue function to associate an *http.Client value with a context.
|
||||||
|
var HTTPClient ContextKey
|
||||||
|
|
||||||
|
// ContextKey is just an empty struct. It exists so HTTPClient can be
|
||||||
|
// an immutable public variable with a unique type. It's immutable
|
||||||
|
// because nobody else can create a ContextKey, being unexported.
|
||||||
|
type ContextKey struct{}
|
||||||
|
|
||||||
|
// ContextClientFunc is a func which tries to return an *http.Client
|
||||||
|
// given a Context value. If it returns an error, the search stops
|
||||||
|
// with that error. If it returns (nil, nil), the search continues
|
||||||
|
// down the list of registered funcs.
|
||||||
|
type ContextClientFunc func(context.Context) (*http.Client, error)
|
||||||
|
|
||||||
|
var contextClientFuncs []ContextClientFunc
|
||||||
|
|
||||||
|
func RegisterContextClientFunc(fn ContextClientFunc) {
|
||||||
|
contextClientFuncs = append(contextClientFuncs, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContextClient(ctx context.Context) (*http.Client, error) {
|
||||||
|
for _, fn := range contextClientFuncs {
|
||||||
|
c, err := fn(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hc, ok := ctx.Value(HTTPClient).(*http.Client); ok {
|
||||||
|
return hc, nil
|
||||||
|
}
|
||||||
|
return http.DefaultClient, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ContextTransport(ctx context.Context) http.RoundTripper {
|
||||||
|
hc, err := ContextClient(ctx)
|
||||||
|
// This is a rare error case (somebody using nil on App Engine).
|
||||||
|
if err != nil {
|
||||||
|
return ErrorTransport{err}
|
||||||
|
}
|
||||||
|
return hc.Transport
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrorTransport returns the specified error on RoundTrip.
|
||||||
|
// This RoundTripper should be used in rare error cases where
|
||||||
|
// error handling can be postponed to response handling time.
|
||||||
|
type ErrorTransport struct{ Err error }
|
||||||
|
|
||||||
|
func (t ErrorTransport) RoundTrip(*http.Request) (*http.Response, error) {
|
||||||
|
return nil, t.Err
|
||||||
|
}
|
159
vendor/golang.org/x/oauth2/jws/jws.go
generated
vendored
Normal file
159
vendor/golang.org/x/oauth2/jws/jws.go
generated
vendored
Normal file
@ -0,0 +1,159 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package jws provides encoding and decoding utilities for
|
||||||
|
// signed JWS messages.
|
||||||
|
package jws // import "golang.org/x/oauth2/jws"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClaimSet contains information about the JWT signature including the
|
||||||
|
// permissions being requested (scopes), the target of the token, the issuer,
|
||||||
|
// the time the token was issued, and the lifetime of the token.
|
||||||
|
type ClaimSet struct {
|
||||||
|
Iss string `json:"iss"` // email address of the client_id of the application making the access token request
|
||||||
|
Scope string `json:"scope,omitempty"` // space-delimited list of the permissions the application requests
|
||||||
|
Aud string `json:"aud"` // descriptor of the intended target of the assertion (Optional).
|
||||||
|
Exp int64 `json:"exp"` // the expiration time of the assertion (seconds since Unix epoch)
|
||||||
|
Iat int64 `json:"iat"` // the time the assertion was issued (seconds since Unix epoch)
|
||||||
|
Typ string `json:"typ,omitempty"` // token type (Optional).
|
||||||
|
|
||||||
|
// Email for which the application is requesting delegated access (Optional).
|
||||||
|
Sub string `json:"sub,omitempty"`
|
||||||
|
|
||||||
|
// The old name of Sub. Client keeps setting Prn to be
|
||||||
|
// complaint with legacy OAuth 2.0 providers. (Optional)
|
||||||
|
Prn string `json:"prn,omitempty"`
|
||||||
|
|
||||||
|
// See http://tools.ietf.org/html/draft-jones-json-web-token-10#section-4.3
|
||||||
|
// This array is marshalled using custom code (see (c *ClaimSet) encode()).
|
||||||
|
PrivateClaims map[string]interface{} `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ClaimSet) encode() (string, error) {
|
||||||
|
// Reverting time back for machines whose time is not perfectly in sync.
|
||||||
|
// If client machine's time is in the future according
|
||||||
|
// to Google servers, an access token will not be issued.
|
||||||
|
now := time.Now().Add(-10 * time.Second)
|
||||||
|
if c.Iat == 0 {
|
||||||
|
c.Iat = now.Unix()
|
||||||
|
}
|
||||||
|
if c.Exp == 0 {
|
||||||
|
c.Exp = now.Add(time.Hour).Unix()
|
||||||
|
}
|
||||||
|
if c.Exp < c.Iat {
|
||||||
|
return "", fmt.Errorf("jws: invalid Exp = %v; must be later than Iat = %v", c.Exp, c.Iat)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := json.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(c.PrivateClaims) == 0 {
|
||||||
|
return base64Encode(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal private claim set and then append it to b.
|
||||||
|
prv, err := json.Marshal(c.PrivateClaims)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("jws: invalid map of private claims %v", c.PrivateClaims)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concatenate public and private claim JSON objects.
|
||||||
|
if !bytes.HasSuffix(b, []byte{'}'}) {
|
||||||
|
return "", fmt.Errorf("jws: invalid JSON %s", b)
|
||||||
|
}
|
||||||
|
if !bytes.HasPrefix(prv, []byte{'{'}) {
|
||||||
|
return "", fmt.Errorf("jws: invalid JSON %s", prv)
|
||||||
|
}
|
||||||
|
b[len(b)-1] = ',' // Replace closing curly brace with a comma.
|
||||||
|
b = append(b, prv[1:]...) // Append private claims.
|
||||||
|
return base64Encode(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Header represents the header for the signed JWS payloads.
|
||||||
|
type Header struct {
|
||||||
|
// The algorithm used for signature.
|
||||||
|
Algorithm string `json:"alg"`
|
||||||
|
|
||||||
|
// Represents the token type.
|
||||||
|
Typ string `json:"typ"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *Header) encode() (string, error) {
|
||||||
|
b, err := json.Marshal(h)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return base64Encode(b), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode decodes a claim set from a JWS payload.
|
||||||
|
func Decode(payload string) (*ClaimSet, error) {
|
||||||
|
// decode returned id token to get expiry
|
||||||
|
s := strings.Split(payload, ".")
|
||||||
|
if len(s) < 2 {
|
||||||
|
// TODO(jbd): Provide more context about the error.
|
||||||
|
return nil, errors.New("jws: invalid token received")
|
||||||
|
}
|
||||||
|
decoded, err := base64Decode(s[1])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
c := &ClaimSet{}
|
||||||
|
err = json.NewDecoder(bytes.NewBuffer(decoded)).Decode(c)
|
||||||
|
return c, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode encodes a signed JWS with provided header and claim set.
|
||||||
|
func Encode(header *Header, c *ClaimSet, signature *rsa.PrivateKey) (string, error) {
|
||||||
|
head, err := header.encode()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
cs, err := c.encode()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
ss := fmt.Sprintf("%s.%s", head, cs)
|
||||||
|
h := sha256.New()
|
||||||
|
h.Write([]byte(ss))
|
||||||
|
b, err := rsa.SignPKCS1v15(rand.Reader, signature, crypto.SHA256, h.Sum(nil))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
sig := base64Encode(b)
|
||||||
|
return fmt.Sprintf("%s.%s", ss, sig), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// base64Encode returns and Base64url encoded version of the input string with any
|
||||||
|
// trailing "=" stripped.
|
||||||
|
func base64Encode(b []byte) string {
|
||||||
|
return strings.TrimRight(base64.URLEncoding.EncodeToString(b), "=")
|
||||||
|
}
|
||||||
|
|
||||||
|
// base64Decode decodes the Base64url encoded string
|
||||||
|
func base64Decode(s string) ([]byte, error) {
|
||||||
|
// add back missing padding
|
||||||
|
switch len(s) % 4 {
|
||||||
|
case 2:
|
||||||
|
s += "=="
|
||||||
|
case 3:
|
||||||
|
s += "="
|
||||||
|
}
|
||||||
|
return base64.URLEncoding.DecodeString(s)
|
||||||
|
}
|
31
vendor/golang.org/x/oauth2/jwt/example_test.go
generated
vendored
Normal file
31
vendor/golang.org/x/oauth2/jwt/example_test.go
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package jwt_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/jwt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleJWTConfig() {
|
||||||
|
conf := &jwt.Config{
|
||||||
|
Email: "xxx@developer.com",
|
||||||
|
// The contents of your RSA private key or your PEM file
|
||||||
|
// that contains a private key.
|
||||||
|
// If you have a p12 file instead, you
|
||||||
|
// can use `openssl` to export the private key into a pem file.
|
||||||
|
//
|
||||||
|
// $ openssl pkcs12 -in key.p12 -out key.pem -nodes
|
||||||
|
//
|
||||||
|
// It only supports PEM containers with no passphrase.
|
||||||
|
PrivateKey: []byte("-----BEGIN RSA PRIVATE KEY-----..."),
|
||||||
|
Subject: "user@example.com",
|
||||||
|
TokenURL: "https://provider.com/o/oauth2/token",
|
||||||
|
}
|
||||||
|
// Initiate an http.Client, the following GET request will be
|
||||||
|
// authorized and authenticated on the behalf of user@example.com.
|
||||||
|
client := conf.Client(oauth2.NoContext)
|
||||||
|
client.Get("...")
|
||||||
|
}
|
153
vendor/golang.org/x/oauth2/jwt/jwt.go
generated
vendored
Normal file
153
vendor/golang.org/x/oauth2/jwt/jwt.go
generated
vendored
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package jwt implements the OAuth 2.0 JSON Web Token flow, commonly
|
||||||
|
// known as "two-legged OAuth 2.0".
|
||||||
|
//
|
||||||
|
// See: https://tools.ietf.org/html/draft-ietf-oauth-jwt-bearer-12
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
"golang.org/x/oauth2/internal"
|
||||||
|
"golang.org/x/oauth2/jws"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
defaultGrantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
||||||
|
defaultHeader = &jws.Header{Algorithm: "RS256", Typ: "JWT"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config is the configuration for using JWT to fetch tokens,
|
||||||
|
// commonly known as "two-legged OAuth 2.0".
|
||||||
|
type Config struct {
|
||||||
|
// Email is the OAuth client identifier used when communicating with
|
||||||
|
// the configured OAuth provider.
|
||||||
|
Email string
|
||||||
|
|
||||||
|
// PrivateKey contains the contents of an RSA private key or the
|
||||||
|
// contents of a PEM file that contains a private key. The provided
|
||||||
|
// private key is used to sign JWT payloads.
|
||||||
|
// PEM containers with a passphrase are not supported.
|
||||||
|
// Use the following command to convert a PKCS 12 file into a PEM.
|
||||||
|
//
|
||||||
|
// $ openssl pkcs12 -in key.p12 -out key.pem -nodes
|
||||||
|
//
|
||||||
|
PrivateKey []byte
|
||||||
|
|
||||||
|
// Subject is the optional user to impersonate.
|
||||||
|
Subject string
|
||||||
|
|
||||||
|
// Scopes optionally specifies a list of requested permission scopes.
|
||||||
|
Scopes []string
|
||||||
|
|
||||||
|
// TokenURL is the endpoint required to complete the 2-legged JWT flow.
|
||||||
|
TokenURL string
|
||||||
|
|
||||||
|
// Expires optionally specifies how long the token is valid for.
|
||||||
|
Expires time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenSource returns a JWT TokenSource using the configuration
|
||||||
|
// in c and the HTTP client from the provided context.
|
||||||
|
func (c *Config) TokenSource(ctx context.Context) oauth2.TokenSource {
|
||||||
|
return oauth2.ReuseTokenSource(nil, jwtSource{ctx, c})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client returns an HTTP client wrapping the context's
|
||||||
|
// HTTP transport and adding Authorization headers with tokens
|
||||||
|
// obtained from c.
|
||||||
|
//
|
||||||
|
// The returned client and its Transport should not be modified.
|
||||||
|
func (c *Config) Client(ctx context.Context) *http.Client {
|
||||||
|
return oauth2.NewClient(ctx, c.TokenSource(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
// jwtSource is a source that always does a signed JWT request for a token.
|
||||||
|
// It should typically be wrapped with a reuseTokenSource.
|
||||||
|
type jwtSource struct {
|
||||||
|
ctx context.Context
|
||||||
|
conf *Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (js jwtSource) Token() (*oauth2.Token, error) {
|
||||||
|
pk, err := internal.ParseKey(js.conf.PrivateKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hc := oauth2.NewClient(js.ctx, nil)
|
||||||
|
claimSet := &jws.ClaimSet{
|
||||||
|
Iss: js.conf.Email,
|
||||||
|
Scope: strings.Join(js.conf.Scopes, " "),
|
||||||
|
Aud: js.conf.TokenURL,
|
||||||
|
}
|
||||||
|
if subject := js.conf.Subject; subject != "" {
|
||||||
|
claimSet.Sub = subject
|
||||||
|
// prn is the old name of sub. Keep setting it
|
||||||
|
// to be compatible with legacy OAuth 2.0 providers.
|
||||||
|
claimSet.Prn = subject
|
||||||
|
}
|
||||||
|
if t := js.conf.Expires; t > 0 {
|
||||||
|
claimSet.Exp = time.Now().Add(t).Unix()
|
||||||
|
}
|
||||||
|
payload, err := jws.Encode(defaultHeader, claimSet, pk)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("grant_type", defaultGrantType)
|
||||||
|
v.Set("assertion", payload)
|
||||||
|
resp, err := hc.PostForm(js.conf.TokenURL, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
body, err := ioutil.ReadAll(io.LimitReader(resp.Body, 1<<20))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
|
||||||
|
}
|
||||||
|
if c := resp.StatusCode; c < 200 || c > 299 {
|
||||||
|
return nil, fmt.Errorf("oauth2: cannot fetch token: %v\nResponse: %s", resp.Status, body)
|
||||||
|
}
|
||||||
|
// tokenRes is the JSON response body.
|
||||||
|
var tokenRes struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
IDToken string `json:"id_token"`
|
||||||
|
ExpiresIn int64 `json:"expires_in"` // relative seconds from now
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(body, &tokenRes); err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2: cannot fetch token: %v", err)
|
||||||
|
}
|
||||||
|
token := &oauth2.Token{
|
||||||
|
AccessToken: tokenRes.AccessToken,
|
||||||
|
TokenType: tokenRes.TokenType,
|
||||||
|
}
|
||||||
|
raw := make(map[string]interface{})
|
||||||
|
json.Unmarshal(body, &raw) // no error checks for optional fields
|
||||||
|
token = token.WithExtra(raw)
|
||||||
|
|
||||||
|
if secs := tokenRes.ExpiresIn; secs > 0 {
|
||||||
|
token.Expiry = time.Now().Add(time.Duration(secs) * time.Second)
|
||||||
|
}
|
||||||
|
if v := tokenRes.IDToken; v != "" {
|
||||||
|
// decode returned id token to get expiry
|
||||||
|
claimSet, err := jws.Decode(v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("oauth2: error decoding JWT token: %v", err)
|
||||||
|
}
|
||||||
|
token.Expiry = time.Unix(claimSet.Exp, 0)
|
||||||
|
}
|
||||||
|
return token, nil
|
||||||
|
}
|
134
vendor/golang.org/x/oauth2/jwt/jwt_test.go
generated
vendored
Normal file
134
vendor/golang.org/x/oauth2/jwt/jwt_test.go
generated
vendored
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package jwt
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var dummyPrivateKey = []byte(`-----BEGIN RSA PRIVATE KEY-----
|
||||||
|
MIIEpAIBAAKCAQEAx4fm7dngEmOULNmAs1IGZ9Apfzh+BkaQ1dzkmbUgpcoghucE
|
||||||
|
DZRnAGd2aPyB6skGMXUytWQvNYav0WTR00wFtX1ohWTfv68HGXJ8QXCpyoSKSSFY
|
||||||
|
fuP9X36wBSkSX9J5DVgiuzD5VBdzUISSmapjKm+DcbRALjz6OUIPEWi1Tjl6p5RK
|
||||||
|
1w41qdbmt7E5/kGhKLDuT7+M83g4VWhgIvaAXtnhklDAggilPPa8ZJ1IFe31lNlr
|
||||||
|
k4DRk38nc6sEutdf3RL7QoH7FBusI7uXV03DC6dwN1kP4GE7bjJhcRb/7jYt7CQ9
|
||||||
|
/E9Exz3c0yAp0yrTg0Fwh+qxfH9dKwN52S7SBwIDAQABAoIBAQCaCs26K07WY5Jt
|
||||||
|
3a2Cw3y2gPrIgTCqX6hJs7O5ByEhXZ8nBwsWANBUe4vrGaajQHdLj5OKfsIDrOvn
|
||||||
|
2NI1MqflqeAbu/kR32q3tq8/Rl+PPiwUsW3E6Pcf1orGMSNCXxeducF2iySySzh3
|
||||||
|
nSIhCG5uwJDWI7a4+9KiieFgK1pt/Iv30q1SQS8IEntTfXYwANQrfKUVMmVF9aIK
|
||||||
|
6/WZE2yd5+q3wVVIJ6jsmTzoDCX6QQkkJICIYwCkglmVy5AeTckOVwcXL0jqw5Kf
|
||||||
|
5/soZJQwLEyBoQq7Kbpa26QHq+CJONetPP8Ssy8MJJXBT+u/bSseMb3Zsr5cr43e
|
||||||
|
DJOhwsThAoGBAPY6rPKl2NT/K7XfRCGm1sbWjUQyDShscwuWJ5+kD0yudnT/ZEJ1
|
||||||
|
M3+KS/iOOAoHDdEDi9crRvMl0UfNa8MAcDKHflzxg2jg/QI+fTBjPP5GOX0lkZ9g
|
||||||
|
z6VePoVoQw2gpPFVNPPTxKfk27tEzbaffvOLGBEih0Kb7HTINkW8rIlzAoGBAM9y
|
||||||
|
1yr+jvfS1cGFtNU+Gotoihw2eMKtIqR03Yn3n0PK1nVCDKqwdUqCypz4+ml6cxRK
|
||||||
|
J8+Pfdh7D+ZJd4LEG6Y4QRDLuv5OA700tUoSHxMSNn3q9As4+T3MUyYxWKvTeu3U
|
||||||
|
f2NWP9ePU0lV8ttk7YlpVRaPQmc1qwooBA/z/8AdAoGAW9x0HWqmRICWTBnpjyxx
|
||||||
|
QGlW9rQ9mHEtUotIaRSJ6K/F3cxSGUEkX1a3FRnp6kPLcckC6NlqdNgNBd6rb2rA
|
||||||
|
cPl/uSkZP42Als+9YMoFPU/xrrDPbUhu72EDrj3Bllnyb168jKLa4VBOccUvggxr
|
||||||
|
Dm08I1hgYgdN5huzs7y6GeUCgYEAj+AZJSOJ6o1aXS6rfV3mMRve9bQ9yt8jcKXw
|
||||||
|
5HhOCEmMtaSKfnOF1Ziih34Sxsb7O2428DiX0mV/YHtBnPsAJidL0SdLWIapBzeg
|
||||||
|
KHArByIRkwE6IvJvwpGMdaex1PIGhx5i/3VZL9qiq/ElT05PhIb+UXgoWMabCp84
|
||||||
|
OgxDK20CgYAeaFo8BdQ7FmVX2+EEejF+8xSge6WVLtkaon8bqcn6P0O8lLypoOhd
|
||||||
|
mJAYH8WU+UAy9pecUnDZj14LAGNVmYcse8HFX71MoshnvCTFEPVo4rZxIAGwMpeJ
|
||||||
|
5jgQ3slYLpqrGlcbLgUXBUgzEO684Wk/UV9DFPlHALVqCfXQ9dpJPg==
|
||||||
|
-----END RSA PRIVATE KEY-----`)
|
||||||
|
|
||||||
|
func TestJWTFetch_JSONResponse(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(`{
|
||||||
|
"access_token": "90d64460d14870c08c81352a05dedd3465940a7c",
|
||||||
|
"scope": "user",
|
||||||
|
"token_type": "bearer",
|
||||||
|
"expires_in": 3600
|
||||||
|
}`))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
conf := &Config{
|
||||||
|
Email: "aaa@xxx.com",
|
||||||
|
PrivateKey: dummyPrivateKey,
|
||||||
|
TokenURL: ts.URL,
|
||||||
|
}
|
||||||
|
tok, err := conf.TokenSource(oauth2.NoContext).Token()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !tok.Valid() {
|
||||||
|
t.Errorf("Token invalid")
|
||||||
|
}
|
||||||
|
if tok.AccessToken != "90d64460d14870c08c81352a05dedd3465940a7c" {
|
||||||
|
t.Errorf("Unexpected access token, %#v", tok.AccessToken)
|
||||||
|
}
|
||||||
|
if tok.TokenType != "bearer" {
|
||||||
|
t.Errorf("Unexpected token type, %#v", tok.TokenType)
|
||||||
|
}
|
||||||
|
if tok.Expiry.IsZero() {
|
||||||
|
t.Errorf("Unexpected token expiry, %#v", tok.Expiry)
|
||||||
|
}
|
||||||
|
scope := tok.Extra("scope")
|
||||||
|
if scope != "user" {
|
||||||
|
t.Errorf("Unexpected value for scope: %v", scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJWTFetch_BadResponse(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(`{"scope": "user", "token_type": "bearer"}`))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
conf := &Config{
|
||||||
|
Email: "aaa@xxx.com",
|
||||||
|
PrivateKey: dummyPrivateKey,
|
||||||
|
TokenURL: ts.URL,
|
||||||
|
}
|
||||||
|
tok, err := conf.TokenSource(oauth2.NoContext).Token()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if tok == nil {
|
||||||
|
t.Fatalf("token is nil")
|
||||||
|
}
|
||||||
|
if tok.Valid() {
|
||||||
|
t.Errorf("token is valid. want invalid.")
|
||||||
|
}
|
||||||
|
if tok.AccessToken != "" {
|
||||||
|
t.Errorf("Unexpected non-empty access token %q.", tok.AccessToken)
|
||||||
|
}
|
||||||
|
if want := "bearer"; tok.TokenType != want {
|
||||||
|
t.Errorf("TokenType = %q; want %q", tok.TokenType, want)
|
||||||
|
}
|
||||||
|
scope := tok.Extra("scope")
|
||||||
|
if want := "user"; scope != want {
|
||||||
|
t.Errorf("token scope = %q; want %q", scope, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJWTFetch_BadResponseType(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(`{"access_token":123, "scope": "user", "token_type": "bearer"}`))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
conf := &Config{
|
||||||
|
Email: "aaa@xxx.com",
|
||||||
|
PrivateKey: dummyPrivateKey,
|
||||||
|
TokenURL: ts.URL,
|
||||||
|
}
|
||||||
|
tok, err := conf.TokenSource(oauth2.NoContext).Token()
|
||||||
|
if err == nil {
|
||||||
|
t.Error("got a token; expected error")
|
||||||
|
if tok.AccessToken != "" {
|
||||||
|
t.Errorf("Unexpected access token, %#v.", tok.AccessToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
vendor/golang.org/x/oauth2/linkedin/linkedin.go
generated
vendored
Normal file
16
vendor/golang.org/x/oauth2/linkedin/linkedin.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2015 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package linkedin provides constants for using OAuth2 to access LinkedIn.
|
||||||
|
package linkedin // import "golang.org/x/oauth2/linkedin"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Endpoint is LinkedIn's OAuth 2.0 endpoint.
|
||||||
|
var Endpoint = oauth2.Endpoint{
|
||||||
|
AuthURL: "https://www.linkedin.com/uas/oauth2/authorization",
|
||||||
|
TokenURL: "https://www.linkedin.com/uas/oauth2/accessToken",
|
||||||
|
}
|
325
vendor/golang.org/x/oauth2/oauth2.go
generated
vendored
Normal file
325
vendor/golang.org/x/oauth2/oauth2.go
generated
vendored
Normal file
@ -0,0 +1,325 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package oauth2 provides support for making
|
||||||
|
// OAuth2 authorized and authenticated HTTP requests.
|
||||||
|
// It can additionally grant authorization with Bearer JWT.
|
||||||
|
package oauth2 // import "golang.org/x/oauth2"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/oauth2/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NoContext is the default context you should supply if not using
|
||||||
|
// your own context.Context (see https://golang.org/x/net/context).
|
||||||
|
var NoContext = context.TODO()
|
||||||
|
|
||||||
|
// Config describes a typical 3-legged OAuth2 flow, with both the
|
||||||
|
// client application information and the server's endpoint URLs.
|
||||||
|
type Config struct {
|
||||||
|
// ClientID is the application's ID.
|
||||||
|
ClientID string
|
||||||
|
|
||||||
|
// ClientSecret is the application's secret.
|
||||||
|
ClientSecret string
|
||||||
|
|
||||||
|
// Endpoint contains the resource server's token endpoint
|
||||||
|
// URLs. These are constants specific to each server and are
|
||||||
|
// often available via site-specific packages, such as
|
||||||
|
// google.Endpoint or github.Endpoint.
|
||||||
|
Endpoint Endpoint
|
||||||
|
|
||||||
|
// RedirectURL is the URL to redirect users going through
|
||||||
|
// the OAuth flow, after the resource owner's URLs.
|
||||||
|
RedirectURL string
|
||||||
|
|
||||||
|
// Scope specifies optional requested permissions.
|
||||||
|
Scopes []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// A TokenSource is anything that can return a token.
|
||||||
|
type TokenSource interface {
|
||||||
|
// Token returns a token or an error.
|
||||||
|
// Token must be safe for concurrent use by multiple goroutines.
|
||||||
|
// The returned Token must not be modified.
|
||||||
|
Token() (*Token, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Endpoint contains the OAuth 2.0 provider's authorization and token
|
||||||
|
// endpoint URLs.
|
||||||
|
type Endpoint struct {
|
||||||
|
AuthURL string
|
||||||
|
TokenURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// AccessTypeOnline and AccessTypeOffline are options passed
|
||||||
|
// to the Options.AuthCodeURL method. They modify the
|
||||||
|
// "access_type" field that gets sent in the URL returned by
|
||||||
|
// AuthCodeURL.
|
||||||
|
//
|
||||||
|
// Online is the default if neither is specified. If your
|
||||||
|
// application needs to refresh access tokens when the user
|
||||||
|
// is not present at the browser, then use offline. This will
|
||||||
|
// result in your application obtaining a refresh token the
|
||||||
|
// first time your application exchanges an authorization
|
||||||
|
// code for a user.
|
||||||
|
AccessTypeOnline AuthCodeOption = SetAuthURLParam("access_type", "online")
|
||||||
|
AccessTypeOffline AuthCodeOption = SetAuthURLParam("access_type", "offline")
|
||||||
|
|
||||||
|
// ApprovalForce forces the users to view the consent dialog
|
||||||
|
// and confirm the permissions request at the URL returned
|
||||||
|
// from AuthCodeURL, even if they've already done so.
|
||||||
|
ApprovalForce AuthCodeOption = SetAuthURLParam("approval_prompt", "force")
|
||||||
|
)
|
||||||
|
|
||||||
|
// An AuthCodeOption is passed to Config.AuthCodeURL.
|
||||||
|
type AuthCodeOption interface {
|
||||||
|
setValue(url.Values)
|
||||||
|
}
|
||||||
|
|
||||||
|
type setParam struct{ k, v string }
|
||||||
|
|
||||||
|
func (p setParam) setValue(m url.Values) { m.Set(p.k, p.v) }
|
||||||
|
|
||||||
|
// SetAuthURLParam builds an AuthCodeOption which passes key/value parameters
|
||||||
|
// to a provider's authorization endpoint.
|
||||||
|
func SetAuthURLParam(key, value string) AuthCodeOption {
|
||||||
|
return setParam{key, value}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AuthCodeURL returns a URL to OAuth 2.0 provider's consent page
|
||||||
|
// that asks for permissions for the required scopes explicitly.
|
||||||
|
//
|
||||||
|
// State is a token to protect the user from CSRF attacks. You must
|
||||||
|
// always provide a non-zero string and validate that it matches the
|
||||||
|
// the state query parameter on your redirect callback.
|
||||||
|
// See http://tools.ietf.org/html/rfc6749#section-10.12 for more info.
|
||||||
|
//
|
||||||
|
// Opts may include AccessTypeOnline or AccessTypeOffline, as well
|
||||||
|
// as ApprovalForce.
|
||||||
|
func (c *Config) AuthCodeURL(state string, opts ...AuthCodeOption) string {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.WriteString(c.Endpoint.AuthURL)
|
||||||
|
v := url.Values{
|
||||||
|
"response_type": {"code"},
|
||||||
|
"client_id": {c.ClientID},
|
||||||
|
"redirect_uri": internal.CondVal(c.RedirectURL),
|
||||||
|
"scope": internal.CondVal(strings.Join(c.Scopes, " ")),
|
||||||
|
"state": internal.CondVal(state),
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt.setValue(v)
|
||||||
|
}
|
||||||
|
if strings.Contains(c.Endpoint.AuthURL, "?") {
|
||||||
|
buf.WriteByte('&')
|
||||||
|
} else {
|
||||||
|
buf.WriteByte('?')
|
||||||
|
}
|
||||||
|
buf.WriteString(v.Encode())
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PasswordCredentialsToken converts a resource owner username and password
|
||||||
|
// pair into a token.
|
||||||
|
//
|
||||||
|
// Per the RFC, this grant type should only be used "when there is a high
|
||||||
|
// degree of trust between the resource owner and the client (e.g., the client
|
||||||
|
// is part of the device operating system or a highly privileged application),
|
||||||
|
// and when other authorization grant types are not available."
|
||||||
|
// See https://tools.ietf.org/html/rfc6749#section-4.3 for more info.
|
||||||
|
//
|
||||||
|
// The HTTP client to use is derived from the context.
|
||||||
|
// If nil, http.DefaultClient is used.
|
||||||
|
func (c *Config) PasswordCredentialsToken(ctx context.Context, username, password string) (*Token, error) {
|
||||||
|
return retrieveToken(ctx, c, url.Values{
|
||||||
|
"grant_type": {"password"},
|
||||||
|
"username": {username},
|
||||||
|
"password": {password},
|
||||||
|
"scope": internal.CondVal(strings.Join(c.Scopes, " ")),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exchange converts an authorization code into a token.
|
||||||
|
//
|
||||||
|
// It is used after a resource provider redirects the user back
|
||||||
|
// to the Redirect URI (the URL obtained from AuthCodeURL).
|
||||||
|
//
|
||||||
|
// The HTTP client to use is derived from the context.
|
||||||
|
// If a client is not provided via the context, http.DefaultClient is used.
|
||||||
|
//
|
||||||
|
// The code will be in the *http.Request.FormValue("code"). Before
|
||||||
|
// calling Exchange, be sure to validate FormValue("state").
|
||||||
|
func (c *Config) Exchange(ctx context.Context, code string) (*Token, error) {
|
||||||
|
return retrieveToken(ctx, c, url.Values{
|
||||||
|
"grant_type": {"authorization_code"},
|
||||||
|
"code": {code},
|
||||||
|
"redirect_uri": internal.CondVal(c.RedirectURL),
|
||||||
|
"scope": internal.CondVal(strings.Join(c.Scopes, " ")),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client returns an HTTP client using the provided token.
|
||||||
|
// The token will auto-refresh as necessary. The underlying
|
||||||
|
// HTTP transport will be obtained using the provided context.
|
||||||
|
// The returned client and its Transport should not be modified.
|
||||||
|
func (c *Config) Client(ctx context.Context, t *Token) *http.Client {
|
||||||
|
return NewClient(ctx, c.TokenSource(ctx, t))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokenSource returns a TokenSource that returns t until t expires,
|
||||||
|
// automatically refreshing it as necessary using the provided context.
|
||||||
|
//
|
||||||
|
// Most users will use Config.Client instead.
|
||||||
|
func (c *Config) TokenSource(ctx context.Context, t *Token) TokenSource {
|
||||||
|
tkr := &tokenRefresher{
|
||||||
|
ctx: ctx,
|
||||||
|
conf: c,
|
||||||
|
}
|
||||||
|
if t != nil {
|
||||||
|
tkr.refreshToken = t.RefreshToken
|
||||||
|
}
|
||||||
|
return &reuseTokenSource{
|
||||||
|
t: t,
|
||||||
|
new: tkr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// tokenRefresher is a TokenSource that makes "grant_type"=="refresh_token"
|
||||||
|
// HTTP requests to renew a token using a RefreshToken.
|
||||||
|
type tokenRefresher struct {
|
||||||
|
ctx context.Context // used to get HTTP requests
|
||||||
|
conf *Config
|
||||||
|
refreshToken string
|
||||||
|
}
|
||||||
|
|
||||||
|
// WARNING: Token is not safe for concurrent access, as it
|
||||||
|
// updates the tokenRefresher's refreshToken field.
|
||||||
|
// Within this package, it is used by reuseTokenSource which
|
||||||
|
// synchronizes calls to this method with its own mutex.
|
||||||
|
func (tf *tokenRefresher) Token() (*Token, error) {
|
||||||
|
if tf.refreshToken == "" {
|
||||||
|
return nil, errors.New("oauth2: token expired and refresh token is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
tk, err := retrieveToken(tf.ctx, tf.conf, url.Values{
|
||||||
|
"grant_type": {"refresh_token"},
|
||||||
|
"refresh_token": {tf.refreshToken},
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if tf.refreshToken != tk.RefreshToken {
|
||||||
|
tf.refreshToken = tk.RefreshToken
|
||||||
|
}
|
||||||
|
return tk, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// reuseTokenSource is a TokenSource that holds a single token in memory
|
||||||
|
// and validates its expiry before each call to retrieve it with
|
||||||
|
// Token. If it's expired, it will be auto-refreshed using the
|
||||||
|
// new TokenSource.
|
||||||
|
type reuseTokenSource struct {
|
||||||
|
new TokenSource // called when t is expired.
|
||||||
|
|
||||||
|
mu sync.Mutex // guards t
|
||||||
|
t *Token
|
||||||
|
}
|
||||||
|
|
||||||
|
// Token returns the current token if it's still valid, else will
|
||||||
|
// refresh the current token (using r.Context for HTTP client
|
||||||
|
// information) and return the new one.
|
||||||
|
func (s *reuseTokenSource) Token() (*Token, error) {
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
if s.t.Valid() {
|
||||||
|
return s.t, nil
|
||||||
|
}
|
||||||
|
t, err := s.new.Token()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s.t = t
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// StaticTokenSource returns a TokenSource that always returns the same token.
|
||||||
|
// Because the provided token t is never refreshed, StaticTokenSource is only
|
||||||
|
// useful for tokens that never expire.
|
||||||
|
func StaticTokenSource(t *Token) TokenSource {
|
||||||
|
return staticTokenSource{t}
|
||||||
|
}
|
||||||
|
|
||||||
|
// staticTokenSource is a TokenSource that always returns the same Token.
|
||||||
|
type staticTokenSource struct {
|
||||||
|
t *Token
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s staticTokenSource) Token() (*Token, error) {
|
||||||
|
return s.t, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTPClient is the context key to use with golang.org/x/net/context's
|
||||||
|
// WithValue function to associate an *http.Client value with a context.
|
||||||
|
var HTTPClient internal.ContextKey
|
||||||
|
|
||||||
|
// NewClient creates an *http.Client from a Context and TokenSource.
|
||||||
|
// The returned client is not valid beyond the lifetime of the context.
|
||||||
|
//
|
||||||
|
// As a special case, if src is nil, a non-OAuth2 client is returned
|
||||||
|
// using the provided context. This exists to support related OAuth2
|
||||||
|
// packages.
|
||||||
|
func NewClient(ctx context.Context, src TokenSource) *http.Client {
|
||||||
|
if src == nil {
|
||||||
|
c, err := internal.ContextClient(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return &http.Client{Transport: internal.ErrorTransport{err}}
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return &http.Client{
|
||||||
|
Transport: &Transport{
|
||||||
|
Base: internal.ContextTransport(ctx),
|
||||||
|
Source: ReuseTokenSource(nil, src),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReuseTokenSource returns a TokenSource which repeatedly returns the
|
||||||
|
// same token as long as it's valid, starting with t.
|
||||||
|
// When its cached token is invalid, a new token is obtained from src.
|
||||||
|
//
|
||||||
|
// ReuseTokenSource is typically used to reuse tokens from a cache
|
||||||
|
// (such as a file on disk) between runs of a program, rather than
|
||||||
|
// obtaining new tokens unnecessarily.
|
||||||
|
//
|
||||||
|
// The initial token t may be nil, in which case the TokenSource is
|
||||||
|
// wrapped in a caching version if it isn't one already. This also
|
||||||
|
// means it's always safe to wrap ReuseTokenSource around any other
|
||||||
|
// TokenSource without adverse effects.
|
||||||
|
func ReuseTokenSource(t *Token, src TokenSource) TokenSource {
|
||||||
|
// Don't wrap a reuseTokenSource in itself. That would work,
|
||||||
|
// but cause an unnecessary number of mutex operations.
|
||||||
|
// Just build the equivalent one.
|
||||||
|
if rt, ok := src.(*reuseTokenSource); ok {
|
||||||
|
if t == nil {
|
||||||
|
// Just use it directly.
|
||||||
|
return rt
|
||||||
|
}
|
||||||
|
src = rt.new
|
||||||
|
}
|
||||||
|
return &reuseTokenSource{
|
||||||
|
t: t,
|
||||||
|
new: src,
|
||||||
|
}
|
||||||
|
}
|
471
vendor/golang.org/x/oauth2/oauth2_test.go
generated
vendored
Normal file
471
vendor/golang.org/x/oauth2/oauth2_test.go
generated
vendored
Normal file
@ -0,0 +1,471 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package oauth2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockTransport struct {
|
||||||
|
rt func(req *http.Request) (resp *http.Response, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *mockTransport) RoundTrip(req *http.Request) (resp *http.Response, err error) {
|
||||||
|
return t.rt(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockCache struct {
|
||||||
|
token *Token
|
||||||
|
readErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockCache) ReadToken() (*Token, error) {
|
||||||
|
return c.token, c.readErr
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *mockCache) WriteToken(*Token) {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
func newConf(url string) *Config {
|
||||||
|
return &Config{
|
||||||
|
ClientID: "CLIENT_ID",
|
||||||
|
ClientSecret: "CLIENT_SECRET",
|
||||||
|
RedirectURL: "REDIRECT_URL",
|
||||||
|
Scopes: []string{"scope1", "scope2"},
|
||||||
|
Endpoint: Endpoint{
|
||||||
|
AuthURL: url + "/auth",
|
||||||
|
TokenURL: url + "/token",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthCodeURL(t *testing.T) {
|
||||||
|
conf := newConf("server")
|
||||||
|
url := conf.AuthCodeURL("foo", AccessTypeOffline, ApprovalForce)
|
||||||
|
if url != "server/auth?access_type=offline&approval_prompt=force&client_id=CLIENT_ID&redirect_uri=REDIRECT_URL&response_type=code&scope=scope1+scope2&state=foo" {
|
||||||
|
t.Errorf("Auth code URL doesn't match the expected, found: %v", url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthCodeURL_CustomParam(t *testing.T) {
|
||||||
|
conf := newConf("server")
|
||||||
|
param := SetAuthURLParam("foo", "bar")
|
||||||
|
url := conf.AuthCodeURL("baz", param)
|
||||||
|
if url != "server/auth?client_id=CLIENT_ID&foo=bar&redirect_uri=REDIRECT_URL&response_type=code&scope=scope1+scope2&state=baz" {
|
||||||
|
t.Errorf("Auth code URL doesn't match the expected, found: %v", url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthCodeURL_Optional(t *testing.T) {
|
||||||
|
conf := &Config{
|
||||||
|
ClientID: "CLIENT_ID",
|
||||||
|
Endpoint: Endpoint{
|
||||||
|
AuthURL: "/auth-url",
|
||||||
|
TokenURL: "/token-url",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
url := conf.AuthCodeURL("")
|
||||||
|
if url != "/auth-url?client_id=CLIENT_ID&response_type=code" {
|
||||||
|
t.Fatalf("Auth code URL doesn't match the expected, found: %v", url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExchangeRequest(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.String() != "/token" {
|
||||||
|
t.Errorf("Unexpected exchange request URL, %v is found.", r.URL)
|
||||||
|
}
|
||||||
|
headerAuth := r.Header.Get("Authorization")
|
||||||
|
if headerAuth != "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ=" {
|
||||||
|
t.Errorf("Unexpected authorization header, %v is found.", headerAuth)
|
||||||
|
}
|
||||||
|
headerContentType := r.Header.Get("Content-Type")
|
||||||
|
if headerContentType != "application/x-www-form-urlencoded" {
|
||||||
|
t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed reading request body: %s.", err)
|
||||||
|
}
|
||||||
|
if string(body) != "client_id=CLIENT_ID&code=exchange-code&grant_type=authorization_code&redirect_uri=REDIRECT_URL&scope=scope1+scope2" {
|
||||||
|
t.Errorf("Unexpected exchange payload, %v is found.", string(body))
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
w.Write([]byte("access_token=90d64460d14870c08c81352a05dedd3465940a7c&scope=user&token_type=bearer"))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
conf := newConf(ts.URL)
|
||||||
|
tok, err := conf.Exchange(NoContext, "exchange-code")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if !tok.Valid() {
|
||||||
|
t.Fatalf("Token invalid. Got: %#v", tok)
|
||||||
|
}
|
||||||
|
if tok.AccessToken != "90d64460d14870c08c81352a05dedd3465940a7c" {
|
||||||
|
t.Errorf("Unexpected access token, %#v.", tok.AccessToken)
|
||||||
|
}
|
||||||
|
if tok.TokenType != "bearer" {
|
||||||
|
t.Errorf("Unexpected token type, %#v.", tok.TokenType)
|
||||||
|
}
|
||||||
|
scope := tok.Extra("scope")
|
||||||
|
if scope != "user" {
|
||||||
|
t.Errorf("Unexpected value for scope: %v", scope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExchangeRequest_JSONResponse(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.String() != "/token" {
|
||||||
|
t.Errorf("Unexpected exchange request URL, %v is found.", r.URL)
|
||||||
|
}
|
||||||
|
headerAuth := r.Header.Get("Authorization")
|
||||||
|
if headerAuth != "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ=" {
|
||||||
|
t.Errorf("Unexpected authorization header, %v is found.", headerAuth)
|
||||||
|
}
|
||||||
|
headerContentType := r.Header.Get("Content-Type")
|
||||||
|
if headerContentType != "application/x-www-form-urlencoded" {
|
||||||
|
t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed reading request body: %s.", err)
|
||||||
|
}
|
||||||
|
if string(body) != "client_id=CLIENT_ID&code=exchange-code&grant_type=authorization_code&redirect_uri=REDIRECT_URL&scope=scope1+scope2" {
|
||||||
|
t.Errorf("Unexpected exchange payload, %v is found.", string(body))
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(`{"access_token": "90d64460d14870c08c81352a05dedd3465940a7c", "scope": "user", "token_type": "bearer", "expires_in": 86400}`))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
conf := newConf(ts.URL)
|
||||||
|
tok, err := conf.Exchange(NoContext, "exchange-code")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if !tok.Valid() {
|
||||||
|
t.Fatalf("Token invalid. Got: %#v", tok)
|
||||||
|
}
|
||||||
|
if tok.AccessToken != "90d64460d14870c08c81352a05dedd3465940a7c" {
|
||||||
|
t.Errorf("Unexpected access token, %#v.", tok.AccessToken)
|
||||||
|
}
|
||||||
|
if tok.TokenType != "bearer" {
|
||||||
|
t.Errorf("Unexpected token type, %#v.", tok.TokenType)
|
||||||
|
}
|
||||||
|
scope := tok.Extra("scope")
|
||||||
|
if scope != "user" {
|
||||||
|
t.Errorf("Unexpected value for scope: %v", scope)
|
||||||
|
}
|
||||||
|
expiresIn := tok.Extra("expires_in")
|
||||||
|
if expiresIn != float64(86400) {
|
||||||
|
t.Errorf("Unexpected non-numeric value for expires_in: %v", expiresIn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExtraValueRetrieval(t *testing.T) {
|
||||||
|
values := url.Values{}
|
||||||
|
|
||||||
|
kvmap := map[string]string{
|
||||||
|
"scope": "user", "token_type": "bearer", "expires_in": "86400.92",
|
||||||
|
"server_time": "1443571905.5606415", "referer_ip": "10.0.0.1",
|
||||||
|
"etag": "\"afZYj912P4alikMz_P11982\"", "request_id": "86400",
|
||||||
|
"untrimmed": " untrimmed ",
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range kvmap {
|
||||||
|
values.Set(key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
tok := Token{
|
||||||
|
raw: values,
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := tok.Extra("scope")
|
||||||
|
if scope != "user" {
|
||||||
|
t.Errorf("Unexpected scope %v wanted \"user\"", scope)
|
||||||
|
}
|
||||||
|
serverTime := tok.Extra("server_time")
|
||||||
|
if serverTime != 1443571905.5606415 {
|
||||||
|
t.Errorf("Unexpected non-float64 value for server_time: %v", serverTime)
|
||||||
|
}
|
||||||
|
refererIp := tok.Extra("referer_ip")
|
||||||
|
if refererIp != "10.0.0.1" {
|
||||||
|
t.Errorf("Unexpected non-string value for referer_ip: %v", refererIp)
|
||||||
|
}
|
||||||
|
expires_in := tok.Extra("expires_in")
|
||||||
|
if expires_in != 86400.92 {
|
||||||
|
t.Errorf("Unexpected value for expires_in, wanted 86400 got %v", expires_in)
|
||||||
|
}
|
||||||
|
requestId := tok.Extra("request_id")
|
||||||
|
if requestId != int64(86400) {
|
||||||
|
t.Errorf("Unexpected non-int64 value for request_id: %v", requestId)
|
||||||
|
}
|
||||||
|
untrimmed := tok.Extra("untrimmed")
|
||||||
|
if untrimmed != " untrimmed " {
|
||||||
|
t.Errorf("Unexpected value for untrimmed, got %q expected \" untrimmed \"", untrimmed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const day = 24 * time.Hour
|
||||||
|
|
||||||
|
func TestExchangeRequest_JSONResponse_Expiry(t *testing.T) {
|
||||||
|
seconds := int32(day.Seconds())
|
||||||
|
jsonNumberType := reflect.TypeOf(json.Number("0"))
|
||||||
|
for _, c := range []struct {
|
||||||
|
expires string
|
||||||
|
expect error
|
||||||
|
}{
|
||||||
|
{fmt.Sprintf(`"expires_in": %d`, seconds), nil},
|
||||||
|
{fmt.Sprintf(`"expires_in": "%d"`, seconds), nil}, // PayPal case
|
||||||
|
{fmt.Sprintf(`"expires": %d`, seconds), nil}, // Facebook case
|
||||||
|
{`"expires": false`, &json.UnmarshalTypeError{Value: "bool", Type: jsonNumberType}}, // wrong type
|
||||||
|
{`"expires": {}`, &json.UnmarshalTypeError{Value: "object", Type: jsonNumberType}}, // wrong type
|
||||||
|
{`"expires": "zzz"`, &strconv.NumError{Func: "ParseInt", Num: "zzz", Err: strconv.ErrSyntax}}, // wrong value
|
||||||
|
} {
|
||||||
|
testExchangeRequest_JSONResponse_expiry(t, c.expires, c.expect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testExchangeRequest_JSONResponse_expiry(t *testing.T, exp string, expect error) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(fmt.Sprintf(`{"access_token": "90d", "scope": "user", "token_type": "bearer", %s}`, exp)))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
conf := newConf(ts.URL)
|
||||||
|
t1 := time.Now().Add(day)
|
||||||
|
tok, err := conf.Exchange(NoContext, "exchange-code")
|
||||||
|
t2 := time.Now().Add(day)
|
||||||
|
// Do a fmt.Sprint comparison so either side can be
|
||||||
|
// nil. fmt.Sprint just stringifies them to "<nil>", and no
|
||||||
|
// non-nil expected error ever stringifies as "<nil>", so this
|
||||||
|
// isn't terribly disgusting. We do this because Go 1.4 and
|
||||||
|
// Go 1.5 return a different deep value for
|
||||||
|
// json.UnmarshalTypeError. In Go 1.5, the
|
||||||
|
// json.UnmarshalTypeError contains a new field with a new
|
||||||
|
// non-zero value. Rather than ignore it here with reflect or
|
||||||
|
// add new files and +build tags, just look at the strings.
|
||||||
|
if fmt.Sprint(err) != fmt.Sprint(expect) {
|
||||||
|
t.Errorf("Error = %v; want %v", err, expect)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !tok.Valid() {
|
||||||
|
t.Fatalf("Token invalid. Got: %#v", tok)
|
||||||
|
}
|
||||||
|
expiry := tok.Expiry
|
||||||
|
if expiry.Before(t1) || expiry.After(t2) {
|
||||||
|
t.Errorf("Unexpected value for Expiry: %v (shold be between %v and %v)", expiry, t1, t2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExchangeRequest_BadResponse(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(`{"scope": "user", "token_type": "bearer"}`))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
conf := newConf(ts.URL)
|
||||||
|
tok, err := conf.Exchange(NoContext, "code")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if tok.AccessToken != "" {
|
||||||
|
t.Errorf("Unexpected access token, %#v.", tok.AccessToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExchangeRequest_BadResponseType(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(`{"access_token":123, "scope": "user", "token_type": "bearer"}`))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
conf := newConf(ts.URL)
|
||||||
|
_, err := conf.Exchange(NoContext, "exchange-code")
|
||||||
|
if err == nil {
|
||||||
|
t.Error("expected error from invalid access_token type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExchangeRequest_NonBasicAuth(t *testing.T) {
|
||||||
|
tr := &mockTransport{
|
||||||
|
rt: func(r *http.Request) (w *http.Response, err error) {
|
||||||
|
headerAuth := r.Header.Get("Authorization")
|
||||||
|
if headerAuth != "" {
|
||||||
|
t.Errorf("Unexpected authorization header, %v is found.", headerAuth)
|
||||||
|
}
|
||||||
|
return nil, errors.New("no response")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
c := &http.Client{Transport: tr}
|
||||||
|
conf := &Config{
|
||||||
|
ClientID: "CLIENT_ID",
|
||||||
|
Endpoint: Endpoint{
|
||||||
|
AuthURL: "https://accounts.google.com/auth",
|
||||||
|
TokenURL: "https://accounts.google.com/token",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.WithValue(context.Background(), HTTPClient, c)
|
||||||
|
conf.Exchange(ctx, "code")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPasswordCredentialsTokenRequest(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
defer r.Body.Close()
|
||||||
|
expected := "/token"
|
||||||
|
if r.URL.String() != expected {
|
||||||
|
t.Errorf("URL = %q; want %q", r.URL, expected)
|
||||||
|
}
|
||||||
|
headerAuth := r.Header.Get("Authorization")
|
||||||
|
expected = "Basic Q0xJRU5UX0lEOkNMSUVOVF9TRUNSRVQ="
|
||||||
|
if headerAuth != expected {
|
||||||
|
t.Errorf("Authorization header = %q; want %q", headerAuth, expected)
|
||||||
|
}
|
||||||
|
headerContentType := r.Header.Get("Content-Type")
|
||||||
|
expected = "application/x-www-form-urlencoded"
|
||||||
|
if headerContentType != expected {
|
||||||
|
t.Errorf("Content-Type header = %q; want %q", headerContentType, expected)
|
||||||
|
}
|
||||||
|
body, err := ioutil.ReadAll(r.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Failed reading request body: %s.", err)
|
||||||
|
}
|
||||||
|
expected = "client_id=CLIENT_ID&grant_type=password&password=password1&scope=scope1+scope2&username=user1"
|
||||||
|
if string(body) != expected {
|
||||||
|
t.Errorf("res.Body = %q; want %q", string(body), expected)
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
w.Write([]byte("access_token=90d64460d14870c08c81352a05dedd3465940a7c&scope=user&token_type=bearer"))
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
conf := newConf(ts.URL)
|
||||||
|
tok, err := conf.PasswordCredentialsToken(NoContext, "user1", "password1")
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
if !tok.Valid() {
|
||||||
|
t.Fatalf("Token invalid. Got: %#v", tok)
|
||||||
|
}
|
||||||
|
expected := "90d64460d14870c08c81352a05dedd3465940a7c"
|
||||||
|
if tok.AccessToken != expected {
|
||||||
|
t.Errorf("AccessToken = %q; want %q", tok.AccessToken, expected)
|
||||||
|
}
|
||||||
|
expected = "bearer"
|
||||||
|
if tok.TokenType != expected {
|
||||||
|
t.Errorf("TokenType = %q; want %q", tok.TokenType, expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTokenRefreshRequest(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.String() == "/somethingelse" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.URL.String() != "/token" {
|
||||||
|
t.Errorf("Unexpected token refresh request URL, %v is found.", r.URL)
|
||||||
|
}
|
||||||
|
headerContentType := r.Header.Get("Content-Type")
|
||||||
|
if headerContentType != "application/x-www-form-urlencoded" {
|
||||||
|
t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType)
|
||||||
|
}
|
||||||
|
body, _ := ioutil.ReadAll(r.Body)
|
||||||
|
if string(body) != "client_id=CLIENT_ID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN" {
|
||||||
|
t.Errorf("Unexpected refresh token payload, %v is found.", string(body))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
conf := newConf(ts.URL)
|
||||||
|
c := conf.Client(NoContext, &Token{RefreshToken: "REFRESH_TOKEN"})
|
||||||
|
c.Get(ts.URL + "/somethingelse")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFetchWithNoRefreshToken(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.String() == "/somethingelse" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.URL.String() != "/token" {
|
||||||
|
t.Errorf("Unexpected token refresh request URL, %v is found.", r.URL)
|
||||||
|
}
|
||||||
|
headerContentType := r.Header.Get("Content-Type")
|
||||||
|
if headerContentType != "application/x-www-form-urlencoded" {
|
||||||
|
t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType)
|
||||||
|
}
|
||||||
|
body, _ := ioutil.ReadAll(r.Body)
|
||||||
|
if string(body) != "client_id=CLIENT_ID&grant_type=refresh_token&refresh_token=REFRESH_TOKEN" {
|
||||||
|
t.Errorf("Unexpected refresh token payload, %v is found.", string(body))
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
conf := newConf(ts.URL)
|
||||||
|
c := conf.Client(NoContext, nil)
|
||||||
|
_, err := c.Get(ts.URL + "/somethingelse")
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Fetch should return an error if no refresh token is set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRefreshToken_RefreshTokenReplacement(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.Write([]byte(`{"access_token":"ACCESS TOKEN", "scope": "user", "token_type": "bearer", "refresh_token": "NEW REFRESH TOKEN"}`))
|
||||||
|
return
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
conf := newConf(ts.URL)
|
||||||
|
tkr := tokenRefresher{
|
||||||
|
conf: conf,
|
||||||
|
ctx: NoContext,
|
||||||
|
refreshToken: "OLD REFRESH TOKEN",
|
||||||
|
}
|
||||||
|
tk, err := tkr.Token()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Unexpected refreshToken error returned: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if tk.RefreshToken != tkr.refreshToken {
|
||||||
|
t.Errorf("tokenRefresher.refresh_token = %s; want %s", tkr.refreshToken, tk.RefreshToken)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigClientWithToken(t *testing.T) {
|
||||||
|
tok := &Token{
|
||||||
|
AccessToken: "abc123",
|
||||||
|
}
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if got, want := r.Header.Get("Authorization"), fmt.Sprintf("Bearer %s", tok.AccessToken); got != want {
|
||||||
|
t.Errorf("Authorization header = %q; want %q", got, want)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
conf := newConf(ts.URL)
|
||||||
|
|
||||||
|
c := conf.Client(NoContext, tok)
|
||||||
|
req, err := http.NewRequest("GET", ts.URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
_, err = c.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
16
vendor/golang.org/x/oauth2/odnoklassniki/odnoklassniki.go
generated
vendored
Normal file
16
vendor/golang.org/x/oauth2/odnoklassniki/odnoklassniki.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2015 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package odnoklassniki provides constants for using OAuth2 to access Odnoklassniki.
|
||||||
|
package odnoklassniki // import "golang.org/x/oauth2/odnoklassniki"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Endpoint is Odnoklassniki's OAuth 2.0 endpoint.
|
||||||
|
var Endpoint = oauth2.Endpoint{
|
||||||
|
AuthURL: "https://www.odnoklassniki.ru/oauth/authorize",
|
||||||
|
TokenURL: "https://api.odnoklassniki.ru/oauth/token.do",
|
||||||
|
}
|
22
vendor/golang.org/x/oauth2/paypal/paypal.go
generated
vendored
Normal file
22
vendor/golang.org/x/oauth2/paypal/paypal.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// Copyright 2015 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package paypal provides constants for using OAuth2 to access PayPal.
|
||||||
|
package paypal // import "golang.org/x/oauth2/paypal"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Endpoint is PayPal's OAuth 2.0 endpoint in live (production) environment.
|
||||||
|
var Endpoint = oauth2.Endpoint{
|
||||||
|
AuthURL: "https://www.paypal.com/webapps/auth/protocol/openidconnect/v1/authorize",
|
||||||
|
TokenURL: "https://api.paypal.com/v1/identity/openidconnect/tokenservice",
|
||||||
|
}
|
||||||
|
|
||||||
|
// SandboxEndpoint is PayPal's OAuth 2.0 endpoint in sandbox (testing) environment.
|
||||||
|
var SandboxEndpoint = oauth2.Endpoint{
|
||||||
|
AuthURL: "https://www.sandbox.paypal.com/webapps/auth/protocol/openidconnect/v1/authorize",
|
||||||
|
TokenURL: "https://api.sandbox.paypal.com/v1/identity/openidconnect/tokenservice",
|
||||||
|
}
|
158
vendor/golang.org/x/oauth2/token.go
generated
vendored
Normal file
158
vendor/golang.org/x/oauth2/token.go
generated
vendored
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package oauth2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
"golang.org/x/oauth2/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// expiryDelta determines how earlier a token should be considered
|
||||||
|
// expired than its actual expiration time. It is used to avoid late
|
||||||
|
// expirations due to client-server time mismatches.
|
||||||
|
const expiryDelta = 10 * time.Second
|
||||||
|
|
||||||
|
// Token represents the crendentials used to authorize
|
||||||
|
// the requests to access protected resources on the OAuth 2.0
|
||||||
|
// provider's backend.
|
||||||
|
//
|
||||||
|
// Most users of this package should not access fields of Token
|
||||||
|
// directly. They're exported mostly for use by related packages
|
||||||
|
// implementing derivative OAuth2 flows.
|
||||||
|
type Token struct {
|
||||||
|
// AccessToken is the token that authorizes and authenticates
|
||||||
|
// the requests.
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
|
||||||
|
// TokenType is the type of token.
|
||||||
|
// The Type method returns either this or "Bearer", the default.
|
||||||
|
TokenType string `json:"token_type,omitempty"`
|
||||||
|
|
||||||
|
// RefreshToken is a token that's used by the application
|
||||||
|
// (as opposed to the user) to refresh the access token
|
||||||
|
// if it expires.
|
||||||
|
RefreshToken string `json:"refresh_token,omitempty"`
|
||||||
|
|
||||||
|
// Expiry is the optional expiration time of the access token.
|
||||||
|
//
|
||||||
|
// If zero, TokenSource implementations will reuse the same
|
||||||
|
// token forever and RefreshToken or equivalent
|
||||||
|
// mechanisms for that TokenSource will not be used.
|
||||||
|
Expiry time.Time `json:"expiry,omitempty"`
|
||||||
|
|
||||||
|
// raw optionally contains extra metadata from the server
|
||||||
|
// when updating a token.
|
||||||
|
raw interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type returns t.TokenType if non-empty, else "Bearer".
|
||||||
|
func (t *Token) Type() string {
|
||||||
|
if strings.EqualFold(t.TokenType, "bearer") {
|
||||||
|
return "Bearer"
|
||||||
|
}
|
||||||
|
if strings.EqualFold(t.TokenType, "mac") {
|
||||||
|
return "MAC"
|
||||||
|
}
|
||||||
|
if strings.EqualFold(t.TokenType, "basic") {
|
||||||
|
return "Basic"
|
||||||
|
}
|
||||||
|
if t.TokenType != "" {
|
||||||
|
return t.TokenType
|
||||||
|
}
|
||||||
|
return "Bearer"
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAuthHeader sets the Authorization header to r using the access
|
||||||
|
// token in t.
|
||||||
|
//
|
||||||
|
// This method is unnecessary when using Transport or an HTTP Client
|
||||||
|
// returned by this package.
|
||||||
|
func (t *Token) SetAuthHeader(r *http.Request) {
|
||||||
|
r.Header.Set("Authorization", t.Type()+" "+t.AccessToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithExtra returns a new Token that's a clone of t, but using the
|
||||||
|
// provided raw extra map. This is only intended for use by packages
|
||||||
|
// implementing derivative OAuth2 flows.
|
||||||
|
func (t *Token) WithExtra(extra interface{}) *Token {
|
||||||
|
t2 := new(Token)
|
||||||
|
*t2 = *t
|
||||||
|
t2.raw = extra
|
||||||
|
return t2
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extra returns an extra field.
|
||||||
|
// Extra fields are key-value pairs returned by the server as a
|
||||||
|
// part of the token retrieval response.
|
||||||
|
func (t *Token) Extra(key string) interface{} {
|
||||||
|
if raw, ok := t.raw.(map[string]interface{}); ok {
|
||||||
|
return raw[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
vals, ok := t.raw.(url.Values)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
v := vals.Get(key)
|
||||||
|
switch s := strings.TrimSpace(v); strings.Count(s, ".") {
|
||||||
|
case 0: // Contains no "."; try to parse as int
|
||||||
|
if i, err := strconv.ParseInt(s, 10, 64); err == nil {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
case 1: // Contains a single "."; try to parse as float
|
||||||
|
if f, err := strconv.ParseFloat(s, 64); err == nil {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// expired reports whether the token is expired.
|
||||||
|
// t must be non-nil.
|
||||||
|
func (t *Token) expired() bool {
|
||||||
|
if t.Expiry.IsZero() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return t.Expiry.Add(-expiryDelta).Before(time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid reports whether t is non-nil, has an AccessToken, and is not expired.
|
||||||
|
func (t *Token) Valid() bool {
|
||||||
|
return t != nil && t.AccessToken != "" && !t.expired()
|
||||||
|
}
|
||||||
|
|
||||||
|
// tokenFromInternal maps an *internal.Token struct into
|
||||||
|
// a *Token struct.
|
||||||
|
func tokenFromInternal(t *internal.Token) *Token {
|
||||||
|
if t == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &Token{
|
||||||
|
AccessToken: t.AccessToken,
|
||||||
|
TokenType: t.TokenType,
|
||||||
|
RefreshToken: t.RefreshToken,
|
||||||
|
Expiry: t.Expiry,
|
||||||
|
raw: t.Raw,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// retrieveToken takes a *Config and uses that to retrieve an *internal.Token.
|
||||||
|
// This token is then mapped from *internal.Token into an *oauth2.Token which is returned along
|
||||||
|
// with an error..
|
||||||
|
func retrieveToken(ctx context.Context, c *Config, v url.Values) (*Token, error) {
|
||||||
|
tk, err := internal.RetrieveToken(ctx, c.ClientID, c.ClientSecret, c.Endpoint.TokenURL, v)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return tokenFromInternal(tk), nil
|
||||||
|
}
|
72
vendor/golang.org/x/oauth2/token_test.go
generated
vendored
Normal file
72
vendor/golang.org/x/oauth2/token_test.go
generated
vendored
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package oauth2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTokenExtra(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
key string
|
||||||
|
val interface{}
|
||||||
|
want interface{}
|
||||||
|
}
|
||||||
|
const key = "extra-key"
|
||||||
|
cases := []testCase{
|
||||||
|
{key: key, val: "abc", want: "abc"},
|
||||||
|
{key: key, val: 123, want: 123},
|
||||||
|
{key: key, val: "", want: ""},
|
||||||
|
{key: "other-key", val: "def", want: nil},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
extra := make(map[string]interface{})
|
||||||
|
extra[tc.key] = tc.val
|
||||||
|
tok := &Token{raw: extra}
|
||||||
|
if got, want := tok.Extra(key), tc.want; got != want {
|
||||||
|
t.Errorf("Extra(%q) = %q; want %q", key, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTokenExpiry(t *testing.T) {
|
||||||
|
now := time.Now()
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
tok *Token
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{name: "12 seconds", tok: &Token{Expiry: now.Add(12 * time.Second)}, want: false},
|
||||||
|
{name: "10 seconds", tok: &Token{Expiry: now.Add(expiryDelta)}, want: true},
|
||||||
|
{name: "-1 hour", tok: &Token{Expiry: now.Add(-1 * time.Hour)}, want: true},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
if got, want := tc.tok.expired(), tc.want; got != want {
|
||||||
|
t.Errorf("expired (%q) = %v; want %v", tc.name, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTokenTypeMethod(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
tok *Token
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{name: "bearer-mixed_case", tok: &Token{TokenType: "beAREr"}, want: "Bearer"},
|
||||||
|
{name: "default-bearer", tok: &Token{}, want: "Bearer"},
|
||||||
|
{name: "basic", tok: &Token{TokenType: "basic"}, want: "Basic"},
|
||||||
|
{name: "basic-capitalized", tok: &Token{TokenType: "Basic"}, want: "Basic"},
|
||||||
|
{name: "mac", tok: &Token{TokenType: "mac"}, want: "MAC"},
|
||||||
|
{name: "mac-caps", tok: &Token{TokenType: "MAC"}, want: "MAC"},
|
||||||
|
{name: "mac-mixed_case", tok: &Token{TokenType: "mAc"}, want: "MAC"},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
if got, want := tc.tok.Type(), tc.want; got != want {
|
||||||
|
t.Errorf("TokenType(%q) = %v; want %v", tc.name, got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
132
vendor/golang.org/x/oauth2/transport.go
generated
vendored
Normal file
132
vendor/golang.org/x/oauth2/transport.go
generated
vendored
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
// Copyright 2014 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package oauth2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Transport is an http.RoundTripper that makes OAuth 2.0 HTTP requests,
|
||||||
|
// wrapping a base RoundTripper and adding an Authorization header
|
||||||
|
// with a token from the supplied Sources.
|
||||||
|
//
|
||||||
|
// Transport is a low-level mechanism. Most code will use the
|
||||||
|
// higher-level Config.Client method instead.
|
||||||
|
type Transport struct {
|
||||||
|
// Source supplies the token to add to outgoing requests'
|
||||||
|
// Authorization headers.
|
||||||
|
Source TokenSource
|
||||||
|
|
||||||
|
// Base is the base RoundTripper used to make HTTP requests.
|
||||||
|
// If nil, http.DefaultTransport is used.
|
||||||
|
Base http.RoundTripper
|
||||||
|
|
||||||
|
mu sync.Mutex // guards modReq
|
||||||
|
modReq map[*http.Request]*http.Request // original -> modified
|
||||||
|
}
|
||||||
|
|
||||||
|
// RoundTrip authorizes and authenticates the request with an
|
||||||
|
// access token. If no token exists or token is expired,
|
||||||
|
// tries to refresh/fetch a new token.
|
||||||
|
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||||
|
if t.Source == nil {
|
||||||
|
return nil, errors.New("oauth2: Transport's Source is nil")
|
||||||
|
}
|
||||||
|
token, err := t.Source.Token()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req2 := cloneRequest(req) // per RoundTripper contract
|
||||||
|
token.SetAuthHeader(req2)
|
||||||
|
t.setModReq(req, req2)
|
||||||
|
res, err := t.base().RoundTrip(req2)
|
||||||
|
if err != nil {
|
||||||
|
t.setModReq(req, nil)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res.Body = &onEOFReader{
|
||||||
|
rc: res.Body,
|
||||||
|
fn: func() { t.setModReq(req, nil) },
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CancelRequest cancels an in-flight request by closing its connection.
|
||||||
|
func (t *Transport) CancelRequest(req *http.Request) {
|
||||||
|
type canceler interface {
|
||||||
|
CancelRequest(*http.Request)
|
||||||
|
}
|
||||||
|
if cr, ok := t.base().(canceler); ok {
|
||||||
|
t.mu.Lock()
|
||||||
|
modReq := t.modReq[req]
|
||||||
|
delete(t.modReq, req)
|
||||||
|
t.mu.Unlock()
|
||||||
|
cr.CancelRequest(modReq)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) base() http.RoundTripper {
|
||||||
|
if t.Base != nil {
|
||||||
|
return t.Base
|
||||||
|
}
|
||||||
|
return http.DefaultTransport
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Transport) setModReq(orig, mod *http.Request) {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
if t.modReq == nil {
|
||||||
|
t.modReq = make(map[*http.Request]*http.Request)
|
||||||
|
}
|
||||||
|
if mod == nil {
|
||||||
|
delete(t.modReq, orig)
|
||||||
|
} else {
|
||||||
|
t.modReq[orig] = mod
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// cloneRequest returns a clone of the provided *http.Request.
|
||||||
|
// The clone is a shallow copy of the struct and its Header map.
|
||||||
|
func cloneRequest(r *http.Request) *http.Request {
|
||||||
|
// shallow copy of the struct
|
||||||
|
r2 := new(http.Request)
|
||||||
|
*r2 = *r
|
||||||
|
// deep copy of the Header
|
||||||
|
r2.Header = make(http.Header, len(r.Header))
|
||||||
|
for k, s := range r.Header {
|
||||||
|
r2.Header[k] = append([]string(nil), s...)
|
||||||
|
}
|
||||||
|
return r2
|
||||||
|
}
|
||||||
|
|
||||||
|
type onEOFReader struct {
|
||||||
|
rc io.ReadCloser
|
||||||
|
fn func()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *onEOFReader) Read(p []byte) (n int, err error) {
|
||||||
|
n, err = r.rc.Read(p)
|
||||||
|
if err == io.EOF {
|
||||||
|
r.runFunc()
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *onEOFReader) Close() error {
|
||||||
|
err := r.rc.Close()
|
||||||
|
r.runFunc()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *onEOFReader) runFunc() {
|
||||||
|
if fn := r.fn; fn != nil {
|
||||||
|
fn()
|
||||||
|
r.fn = nil
|
||||||
|
}
|
||||||
|
}
|
86
vendor/golang.org/x/oauth2/transport_test.go
generated
vendored
Normal file
86
vendor/golang.org/x/oauth2/transport_test.go
generated
vendored
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
package oauth2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type tokenSource struct{ token *Token }
|
||||||
|
|
||||||
|
func (t *tokenSource) Token() (*Token, error) {
|
||||||
|
return t.token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTransportTokenSource(t *testing.T) {
|
||||||
|
ts := &tokenSource{
|
||||||
|
token: &Token{
|
||||||
|
AccessToken: "abc",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tr := &Transport{
|
||||||
|
Source: ts,
|
||||||
|
}
|
||||||
|
server := newMockServer(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Header.Get("Authorization") != "Bearer abc" {
|
||||||
|
t.Errorf("Transport doesn't set the Authorization header from the fetched token")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
defer server.Close()
|
||||||
|
client := http.Client{Transport: tr}
|
||||||
|
client.Get(server.URL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for case-sensitive token types, per https://github.com/golang/oauth2/issues/113
|
||||||
|
func TestTransportTokenSourceTypes(t *testing.T) {
|
||||||
|
const val = "abc"
|
||||||
|
tests := []struct {
|
||||||
|
key string
|
||||||
|
val string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{key: "bearer", val: val, want: "Bearer abc"},
|
||||||
|
{key: "mac", val: val, want: "MAC abc"},
|
||||||
|
{key: "basic", val: val, want: "Basic abc"},
|
||||||
|
}
|
||||||
|
for _, tc := range tests {
|
||||||
|
ts := &tokenSource{
|
||||||
|
token: &Token{
|
||||||
|
AccessToken: tc.val,
|
||||||
|
TokenType: tc.key,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tr := &Transport{
|
||||||
|
Source: ts,
|
||||||
|
}
|
||||||
|
server := newMockServer(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if got, want := r.Header.Get("Authorization"), tc.want; got != want {
|
||||||
|
t.Errorf("Authorization header (%q) = %q; want %q", val, got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
defer server.Close()
|
||||||
|
client := http.Client{Transport: tr}
|
||||||
|
client.Get(server.URL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTokenValidNoAccessToken(t *testing.T) {
|
||||||
|
token := &Token{}
|
||||||
|
if token.Valid() {
|
||||||
|
t.Errorf("Token should not be valid with no access token")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExpiredWithExpiry(t *testing.T) {
|
||||||
|
token := &Token{
|
||||||
|
Expiry: time.Now().Add(-5 * time.Hour),
|
||||||
|
}
|
||||||
|
if token.Valid() {
|
||||||
|
t.Errorf("Token should not be valid if it expired in the past")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockServer(handler func(w http.ResponseWriter, r *http.Request)) *httptest.Server {
|
||||||
|
return httptest.NewServer(http.HandlerFunc(handler))
|
||||||
|
}
|
16
vendor/golang.org/x/oauth2/vk/vk.go
generated
vendored
Normal file
16
vendor/golang.org/x/oauth2/vk/vk.go
generated
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// Copyright 2015 The oauth2 Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Package vk provides constants for using OAuth2 to access VK.com.
|
||||||
|
package vk // import "golang.org/x/oauth2/vk"
|
||||||
|
|
||||||
|
import (
|
||||||
|
"golang.org/x/oauth2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Endpoint is VK's OAuth 2.0 endpoint.
|
||||||
|
var Endpoint = oauth2.Endpoint{
|
||||||
|
AuthURL: "https://oauth.vk.com/authorize",
|
||||||
|
TokenURL: "https://oauth.vk.com/access_token",
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user