mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2025-01-08 16:45:30 +00:00
Remove own copy of oauth2 implementation (#1127)
at some point (~7years ago) the oauth2 implementation was copied into the code-base and never touched. We only use it for gitlab the rest is already back using std. This migrates to the std oauth2 implementation
This commit is contained in:
parent
9a57602174
commit
f15b27aadf
2 changed files with 32 additions and 490 deletions
|
@ -18,7 +18,7 @@ import (
|
|||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
@ -26,13 +26,13 @@ import (
|
|||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/xanzy/go-gitlab"
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
"github.com/woodpecker-ci/woodpecker/server"
|
||||
"github.com/woodpecker-ci/woodpecker/server/model"
|
||||
"github.com/woodpecker-ci/woodpecker/server/remote"
|
||||
"github.com/woodpecker-ci/woodpecker/server/remote/common"
|
||||
"github.com/woodpecker-ci/woodpecker/server/store"
|
||||
"github.com/woodpecker-ci/woodpecker/shared/oauth2"
|
||||
"github.com/woodpecker-ci/woodpecker/shared/utils"
|
||||
)
|
||||
|
||||
|
@ -75,21 +75,28 @@ func (g *Gitlab) Name() string {
|
|||
return "gitlab"
|
||||
}
|
||||
|
||||
func (g *Gitlab) oauth2Config() *oauth2.Config {
|
||||
func (g *Gitlab) oauth2Config(ctx context.Context) (*oauth2.Config, context.Context) {
|
||||
return &oauth2.Config{
|
||||
ClientID: g.ClientID,
|
||||
ClientSecret: g.ClientSecret,
|
||||
Scope: defaultScope,
|
||||
AuthURL: fmt.Sprintf("%s/oauth/authorize", g.URL),
|
||||
TokenURL: fmt.Sprintf("%s/oauth/token", g.URL),
|
||||
RedirectURL: fmt.Sprintf("%s/authorize", server.Config.Server.OAuthHost),
|
||||
}
|
||||
ClientID: g.ClientID,
|
||||
ClientSecret: g.ClientSecret,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
AuthURL: fmt.Sprintf("%s/oauth/authorize", g.URL),
|
||||
TokenURL: fmt.Sprintf("%s/oauth/token", g.URL),
|
||||
},
|
||||
Scopes: []string{defaultScope},
|
||||
RedirectURL: fmt.Sprintf("%s/authorize", server.Config.Server.OAuthHost),
|
||||
},
|
||||
|
||||
context.WithValue(ctx, oauth2.HTTPClient, &http.Client{Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: g.SkipVerify},
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
}})
|
||||
}
|
||||
|
||||
// Login authenticates the session and returns the
|
||||
// remote user details.
|
||||
func (g *Gitlab) Login(ctx context.Context, res http.ResponseWriter, req *http.Request) (*model.User, error) {
|
||||
config := g.oauth2Config()
|
||||
config, oauth2Ctx := g.oauth2Config(ctx)
|
||||
|
||||
// get the OAuth errors
|
||||
if err := req.FormValue("error"); err != "" {
|
||||
|
@ -103,19 +110,11 @@ func (g *Gitlab) Login(ctx context.Context, res http.ResponseWriter, req *http.R
|
|||
// get the OAuth code
|
||||
code := req.FormValue("code")
|
||||
if len(code) == 0 {
|
||||
authCodeURL, err := config.AuthCodeURL("woodpecker")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("authCodeURL error: %v", err)
|
||||
}
|
||||
http.Redirect(res, req, authCodeURL, http.StatusSeeOther)
|
||||
http.Redirect(res, req, config.AuthCodeURL("woodpecker"), http.StatusSeeOther)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
trans := &oauth2.Transport{Config: config, Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: g.SkipVerify},
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
}}
|
||||
token, err := trans.Exchange(code)
|
||||
token, err := config.Exchange(oauth2Ctx, code)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error exchanging token. %s", err)
|
||||
}
|
||||
|
@ -147,29 +146,23 @@ func (g *Gitlab) Login(ctx context.Context, res http.ResponseWriter, req *http.R
|
|||
// Refresh refreshes the Gitlab oauth2 access token. If the token is
|
||||
// refreshed the user is updated and a true value is returned.
|
||||
func (g *Gitlab) Refresh(ctx context.Context, user *model.User) (bool, error) {
|
||||
config := g.oauth2Config()
|
||||
config, oauth2Ctx := g.oauth2Config(ctx)
|
||||
config.RedirectURL = ""
|
||||
|
||||
trans := &oauth2.Transport{
|
||||
Config: config,
|
||||
Token: &oauth2.Token{
|
||||
AccessToken: user.Token,
|
||||
RefreshToken: user.Secret,
|
||||
Expiry: time.Unix(user.Expiry, 0),
|
||||
},
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: g.SkipVerify},
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
},
|
||||
}
|
||||
source := config.TokenSource(oauth2Ctx, &oauth2.Token{
|
||||
AccessToken: user.Token,
|
||||
RefreshToken: user.Secret,
|
||||
Expiry: time.Unix(user.Expiry, 0),
|
||||
})
|
||||
|
||||
if err := trans.Refresh(); err != nil {
|
||||
token, err := source.Token()
|
||||
if err != nil || len(token.AccessToken) == 0 {
|
||||
return false, err
|
||||
}
|
||||
|
||||
user.Token = trans.Token.AccessToken
|
||||
user.Secret = trans.Token.RefreshToken
|
||||
user.Expiry = trans.Token.Expiry.UTC().Unix()
|
||||
user.Token = token.AccessToken
|
||||
user.Secret = token.RefreshToken
|
||||
user.Expiry = token.Expiry.UTC().Unix()
|
||||
return true, nil
|
||||
}
|
||||
|
||||
|
@ -556,7 +549,7 @@ func (g *Gitlab) Branches(ctx context.Context, user *model.User, repo *model.Rep
|
|||
// and returns the required data in a standard format.
|
||||
func (g *Gitlab) Hook(ctx context.Context, req *http.Request) (*model.Repo, *model.Build, error) {
|
||||
defer req.Body.Close()
|
||||
payload, err := ioutil.ReadAll(req.Body)
|
||||
payload, err := io.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
|
@ -1,451 +0,0 @@
|
|||
// Copyright 2011 The goauth2 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 oauth supports making OAuth2-authenticated HTTP requests.
|
||||
//
|
||||
// Example usage:
|
||||
//
|
||||
// // Specify your configuration. (typically as a global variable)
|
||||
// var config = &oauth.Config{
|
||||
// ClientId: YOUR_CLIENT_ID,
|
||||
// ClientSecret: YOUR_CLIENT_SECRET,
|
||||
// Scope: "https://www.googleapis.com/auth/buzz",
|
||||
// AuthURL: "https://accounts.google.com/o/oauth2/auth",
|
||||
// TokenURL: "https://accounts.google.com/o/oauth2/token",
|
||||
// RedirectURL: "http://you.example.org/handler",
|
||||
// }
|
||||
//
|
||||
// // A landing page redirects to the OAuth provider to get the auth code.
|
||||
// func landing(w http.ResponseWriter, r *http.Request) {
|
||||
// http.Redirect(w, r, config.AuthCodeURL("foo"), http.StatusFound)
|
||||
// }
|
||||
//
|
||||
// // The user will be redirected back to this handler, that takes the
|
||||
// // "code" query parameter and Exchanges it for an access token.
|
||||
// func handler(w http.ResponseWriter, r *http.Request) {
|
||||
// t := &oauth.Transport{Config: config}
|
||||
// t.Exchange(r.FormValue("code"))
|
||||
// // The Transport now has a valid Token. Create an *http.Client
|
||||
// // with which we can make authenticated API requests.
|
||||
// c := t.Client()
|
||||
// c.Post(...)
|
||||
// // ...
|
||||
// // btw, r.FormValue("state") == "foo"
|
||||
// }
|
||||
//
|
||||
package oauth2
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// OAuthError is the error type returned by many operations.
|
||||
//
|
||||
// In retrospect it should not exist. Don't depend on it.
|
||||
type OAuthError struct {
|
||||
prefix string
|
||||
msg string
|
||||
}
|
||||
|
||||
func (oe OAuthError) Error() string {
|
||||
return "OAuthError: " + oe.prefix + ": " + oe.msg
|
||||
}
|
||||
|
||||
// Cache specifies the methods that implement a Token cache.
|
||||
type Cache interface {
|
||||
Token() (*Token, error)
|
||||
PutToken(*Token) error
|
||||
}
|
||||
|
||||
// CacheFile implements Cache. Its value is the name of the file in which
|
||||
// the Token is stored in JSON format.
|
||||
type CacheFile string
|
||||
|
||||
func (f CacheFile) Token() (*Token, error) {
|
||||
file, err := os.Open(string(f))
|
||||
if err != nil {
|
||||
return nil, OAuthError{"CacheFile.Token", err.Error()}
|
||||
}
|
||||
defer file.Close()
|
||||
tok := &Token{}
|
||||
if err := json.NewDecoder(file).Decode(tok); err != nil {
|
||||
return nil, OAuthError{"CacheFile.Token", err.Error()}
|
||||
}
|
||||
return tok, nil
|
||||
}
|
||||
|
||||
func (f CacheFile) PutToken(tok *Token) error {
|
||||
file, err := os.OpenFile(string(f), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0o600)
|
||||
if err != nil {
|
||||
return OAuthError{"CacheFile.PutToken", err.Error()}
|
||||
}
|
||||
if err := json.NewEncoder(file).Encode(tok); err != nil {
|
||||
file.Close()
|
||||
return OAuthError{"CacheFile.PutToken", err.Error()}
|
||||
}
|
||||
if err := file.Close(); err != nil {
|
||||
return OAuthError{"CacheFile.PutToken", err.Error()}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Config is the configuration of an OAuth consumer.
|
||||
type Config struct {
|
||||
// ClientID is the OAuth client identifier used when communicating with
|
||||
// the configured OAuth provider.
|
||||
ClientID string
|
||||
|
||||
// ClientSecret is the OAuth client secret used when communicating with
|
||||
// the configured OAuth provider.
|
||||
ClientSecret string
|
||||
|
||||
// Scope identifies the level of access being requested. Multiple scope
|
||||
// values should be provided as a space-delimited string.
|
||||
Scope string
|
||||
|
||||
// AuthURL is the URL the user will be directed to in order to grant
|
||||
// access.
|
||||
AuthURL string
|
||||
|
||||
// TokenURL is the URL used to retrieve OAuth tokens.
|
||||
TokenURL string
|
||||
|
||||
// RedirectURL is the URL to which the user will be returned after
|
||||
// granting (or denying) access.
|
||||
RedirectURL string
|
||||
|
||||
// TokenCache allows tokens to be cached for subsequent requests.
|
||||
TokenCache Cache
|
||||
|
||||
// AccessType is an OAuth extension that gets sent as the
|
||||
// "access_type" field in the URL from AuthCodeURL.
|
||||
// See https://developers.google.com/accounts/docs/OAuth2WebServer.
|
||||
// It may be "online" (the default) or "offline".
|
||||
// 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.
|
||||
AccessType string
|
||||
|
||||
// ApprovalPrompt indicates whether the user should be
|
||||
// re-prompted for consent. If set to "auto" (default) the
|
||||
// user will be prompted only if they haven't previously
|
||||
// granted consent and the code can only be exchanged for an
|
||||
// access token.
|
||||
// If set to "force" the user will always be prompted, and the
|
||||
// code can be exchanged for a refresh token.
|
||||
ApprovalPrompt string
|
||||
}
|
||||
|
||||
// Token contains an end-user's tokens.
|
||||
// This is the data you must store to persist authentication.
|
||||
type Token struct {
|
||||
AccessToken string
|
||||
RefreshToken string
|
||||
Expiry time.Time // If zero the token has no (known) expiry time.
|
||||
|
||||
// Extra optionally contains extra metadata from the server
|
||||
// when updating a token. The only current key that may be
|
||||
// populated is "id_token". It may be nil and will be
|
||||
// initialized as needed.
|
||||
Extra map[string]string
|
||||
}
|
||||
|
||||
// Expired reports whether the token has expired or is invalid.
|
||||
func (t *Token) Expired() bool {
|
||||
if t.AccessToken == "" {
|
||||
return true
|
||||
}
|
||||
if t.Expiry.IsZero() {
|
||||
return false
|
||||
}
|
||||
return t.Expiry.Before(time.Now())
|
||||
}
|
||||
|
||||
// Transport implements http.RoundTripper. When configured with a valid
|
||||
// Config and Token it can be used to make authenticated HTTP requests.
|
||||
//
|
||||
// t := &oauth.Transport{config}
|
||||
// t.Exchange(code)
|
||||
// // t now contains a valid Token
|
||||
// r, _, err := t.Client().Get("http://example.org/url/requiring/auth")
|
||||
//
|
||||
// It will automatically refresh the Token if it can,
|
||||
// updating the supplied Token in place.
|
||||
type Transport struct {
|
||||
*Config
|
||||
*Token
|
||||
|
||||
// mu guards modifying the token.
|
||||
mu sync.Mutex
|
||||
|
||||
// Transport is the HTTP transport to use when making requests.
|
||||
// It will default to http.DefaultTransport if nil.
|
||||
// (It should never be an oauth.Transport.)
|
||||
Transport http.RoundTripper
|
||||
}
|
||||
|
||||
// Client returns an *http.Client that makes OAuth-authenticated requests.
|
||||
func (t *Transport) Client() *http.Client {
|
||||
return &http.Client{Transport: t}
|
||||
}
|
||||
|
||||
func (t *Transport) transport() http.RoundTripper {
|
||||
if t.Transport != nil {
|
||||
return t.Transport
|
||||
}
|
||||
return http.DefaultTransport
|
||||
}
|
||||
|
||||
// AuthCodeURL returns a URL that the end-user should be redirected to,
|
||||
// so that they may obtain an authorization code.
|
||||
func (c *Config) AuthCodeURL(state string) (string, error) {
|
||||
_url, err := url.Parse(c.AuthURL)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("AuthURL malformed: %v", err)
|
||||
}
|
||||
if err := _url.Query().Get("error"); err != "" {
|
||||
return "", fmt.Errorf("AuthURL contains error: %v", err)
|
||||
}
|
||||
q := url.Values{
|
||||
"response_type": {"code"},
|
||||
"client_id": {c.ClientID},
|
||||
"state": condVal(state),
|
||||
"scope": condVal(c.Scope),
|
||||
"redirect_uri": condVal(c.RedirectURL),
|
||||
"access_type": condVal(c.AccessType),
|
||||
"approval_prompt": condVal(c.ApprovalPrompt),
|
||||
}.Encode()
|
||||
if _url.RawQuery == "" {
|
||||
_url.RawQuery = q
|
||||
} else {
|
||||
_url.RawQuery += "&" + q
|
||||
}
|
||||
return _url.String(), nil
|
||||
}
|
||||
|
||||
func condVal(v string) []string {
|
||||
if v == "" {
|
||||
return nil
|
||||
}
|
||||
return []string{v}
|
||||
}
|
||||
|
||||
// Exchange takes a code and gets access Token from the remote server.
|
||||
func (t *Transport) Exchange(code string) (*Token, error) {
|
||||
if t.Config == nil {
|
||||
return nil, OAuthError{"Exchange", "no Config supplied"}
|
||||
}
|
||||
|
||||
// If the transport or the cache already has a token, it is
|
||||
// passed to `updateToken` to preserve existing refresh token.
|
||||
tok := t.Token
|
||||
if tok == nil && t.TokenCache != nil {
|
||||
tok, _ = t.TokenCache.Token()
|
||||
}
|
||||
if tok == nil {
|
||||
tok = new(Token)
|
||||
}
|
||||
err := t.updateToken(tok, url.Values{
|
||||
"grant_type": {"authorization_code"},
|
||||
"redirect_uri": {t.RedirectURL},
|
||||
"scope": {t.Scope},
|
||||
"code": {code},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
t.Token = tok
|
||||
if t.TokenCache != nil {
|
||||
return tok, t.TokenCache.PutToken(tok)
|
||||
}
|
||||
return tok, nil
|
||||
}
|
||||
|
||||
// RoundTrip executes a single HTTP transaction using the Transport's
|
||||
// Token as authorization headers.
|
||||
//
|
||||
// This method will attempt to renew the Token if it has expired and may return
|
||||
// an error related to that Token renewal before attempting the client request.
|
||||
// If the Token cannot be renewed a non-nil os.Error value will be returned.
|
||||
// If the Token is invalid callers should expect HTTP-level errors,
|
||||
// as indicated by the Response's StatusCode.
|
||||
func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
accessToken, err := t.getAccessToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// To set the Authorization header, we must make a copy of the Request
|
||||
// so that we don't modify the Request we were given.
|
||||
// This is required by the specification of http.RoundTripper.
|
||||
req = cloneRequest(req)
|
||||
req.Header.Set("Authorization", "Bearer "+accessToken)
|
||||
|
||||
// Make the HTTP request.
|
||||
return t.transport().RoundTrip(req)
|
||||
}
|
||||
|
||||
func (t *Transport) getAccessToken() (string, error) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
if t.Token == nil {
|
||||
if t.Config == nil {
|
||||
return "", OAuthError{"RoundTrip", "no Config supplied"}
|
||||
}
|
||||
if t.TokenCache == nil {
|
||||
return "", OAuthError{"RoundTrip", "no Token supplied"}
|
||||
}
|
||||
var err error
|
||||
t.Token, err = t.TokenCache.Token()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// Refresh the Token if it has expired.
|
||||
if t.Expired() {
|
||||
if err := t.Refresh(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if t.AccessToken == "" {
|
||||
return "", errors.New("no access token obtained from refresh")
|
||||
}
|
||||
return t.AccessToken, nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
for k, s := range r.Header {
|
||||
r2.Header[k] = s
|
||||
}
|
||||
return r2
|
||||
}
|
||||
|
||||
// Refresh renews the Transport's AccessToken using its RefreshToken.
|
||||
func (t *Transport) Refresh() error {
|
||||
if t.Token == nil {
|
||||
return OAuthError{"Refresh", "no existing Token"}
|
||||
}
|
||||
if t.RefreshToken == "" {
|
||||
return OAuthError{"Refresh", "Token expired; no Refresh Token"}
|
||||
}
|
||||
if t.Config == nil {
|
||||
return OAuthError{"Refresh", "no Config supplied"}
|
||||
}
|
||||
|
||||
err := t.updateToken(t.Token, url.Values{
|
||||
"grant_type": {"refresh_token"},
|
||||
"refresh_token": {t.RefreshToken},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if t.TokenCache != nil {
|
||||
return t.TokenCache.PutToken(t.Token)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AuthenticateClient gets an access Token using the client_credentials grant
|
||||
// type.
|
||||
func (t *Transport) AuthenticateClient() error {
|
||||
if t.Config == nil {
|
||||
return OAuthError{"Exchange", "no Config supplied"}
|
||||
}
|
||||
if t.Token == nil {
|
||||
t.Token = &Token{}
|
||||
}
|
||||
return t.updateToken(t.Token, url.Values{"grant_type": {"client_credentials"}})
|
||||
}
|
||||
|
||||
// updateToken mutates both tok and v.
|
||||
func (t *Transport) updateToken(tok *Token, v url.Values) error {
|
||||
v.Set("client_id", t.ClientID)
|
||||
v.Set("client_secret", t.ClientSecret)
|
||||
client := &http.Client{Transport: t.transport()}
|
||||
req, err := http.NewRequest("POST", t.TokenURL, strings.NewReader(v.Encode()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
req.SetBasicAuth(t.ClientID, t.ClientSecret)
|
||||
r, err := client.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
if r.StatusCode != 200 {
|
||||
return OAuthError{"updateToken", "Unexpected HTTP status " + r.Status}
|
||||
}
|
||||
var b struct {
|
||||
Access string `json:"access_token"`
|
||||
Refresh string `json:"refresh_token"`
|
||||
ExpiresIn int64 `json:"expires_in"` // seconds
|
||||
ID string `json:"id_token"`
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1<<20))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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 err
|
||||
}
|
||||
|
||||
b.Access = vals.Get("access_token")
|
||||
b.Refresh = vals.Get("refresh_token")
|
||||
b.ExpiresIn, _ = strconv.ParseInt(vals.Get("expires_in"), 10, 64)
|
||||
b.ID = vals.Get("id_token")
|
||||
default:
|
||||
if err = json.Unmarshal(body, &b); err != nil {
|
||||
return fmt.Errorf("got bad response from server: %q", body)
|
||||
}
|
||||
}
|
||||
if b.Access == "" {
|
||||
return errors.New("received empty access token from authorization server")
|
||||
}
|
||||
tok.AccessToken = b.Access
|
||||
// Don't overwrite `RefreshToken` with an empty value
|
||||
if b.Refresh != "" {
|
||||
tok.RefreshToken = b.Refresh
|
||||
}
|
||||
if b.ExpiresIn == 0 {
|
||||
tok.Expiry = time.Time{}
|
||||
} else {
|
||||
tok.Expiry = time.Now().Add(time.Duration(b.ExpiresIn) * time.Second)
|
||||
}
|
||||
if b.ID != "" {
|
||||
if tok.Extra == nil {
|
||||
tok.Extra = make(map[string]string)
|
||||
}
|
||||
tok.Extra["id_token"] = b.ID
|
||||
}
|
||||
return nil
|
||||
}
|
Loading…
Reference in a new issue