Skip to content

Commit

Permalink
added token refresh function
Browse files Browse the repository at this point in the history
  • Loading branch information
bradrydzewski committed Jul 15, 2018
1 parent f184e9c commit 7463104
Show file tree
Hide file tree
Showing 18 changed files with 287 additions and 9 deletions.
1 change: 1 addition & 0 deletions scm/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ type (
PullRequests PullRequestService
Repositories RepositoryService
Reviews ReviewService
Tokens TokenService
Users UserService
Webhooks WebhookService

Expand Down
25 changes: 22 additions & 3 deletions scm/driver/bitbucket/bitbucket.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,35 @@ import (
"strings"

"github.com/drone/go-scm/scm"
"github.com/drone/go-scm/scm/transport/oauth2"
)

// ClientID string
// ClientSecret string
// Endpoint string

// Source scm.TokenSource
// Client *http.Client

// New returns a new Bitbucket API client.
func New(uri string) (*scm.Client, error) {
func New(uri string, opt ...Option) (*scm.Client, error) {
base, err := url.Parse(uri)
if err != nil {
return nil, err
}
if !strings.HasSuffix(base.Path, "/") {
base.Path = base.Path + "/"
}
opts := new(Options)
for _, o := range opt {
o(opts)
}
refresher := &oauth2.Refresher{
Endpoint: tokenEndpoint,
ClientID: opts.clientID,
ClientSecret: opts.clientSecret,
Client: opts.client,
}
client := &wrapper{new(scm.Client)}
client.BaseURL = base
// initialize services
Expand All @@ -36,15 +54,16 @@ func New(uri string) (*scm.Client, error) {
client.PullRequests = &pullService{&issueService{client}}
client.Repositories = &repositoryService{client}
client.Reviews = &reviewService{client}
client.Tokens = &tokenService{refresher}
client.Users = &userService{client}
client.Webhooks = &webhookService{client}
return client.Client, nil
}

// NewDefault returns a new Bitbucket API client using the
// default api.bitbucket.org address.
func NewDefault() *scm.Client {
client, _ := New("https://api.bitbucket.org")
func NewDefault(opt ...Option) *scm.Client {
client, _ := New("https://api.bitbucket.org", opt...)
return client
}

Expand Down
41 changes: 41 additions & 0 deletions scm/driver/bitbucket/option.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright 2017 Drone.IO Inc. 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

import "net/http"

// Options provides Bitbucket client options.
type Options struct {
clientID string
clientSecret string
client *http.Client
}

// Option provides a Bitbucket client option.
type Option func(*Options)

// WithClient returns an option to set the http.Client
// used to refresh authorization tokens.
func WithClient(client *http.Client) Option {
return func(opts *Options) {
opts.client = client
}
}

// WithClientID returns an option to set the Bitbucket
// oauth2 client identifier.
func WithClientID(clientID string) Option {
return func(opts *Options) {
opts.clientID = clientID
}
}

// WithClientSecret returns an option to set the
// Bitbucket oauth2 client secret.
func WithClientSecret(clientSecret string) Option {
return func(opts *Options) {
opts.clientSecret = clientSecret
}
}
29 changes: 29 additions & 0 deletions scm/driver/bitbucket/token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright 2018 Drone.IO Inc. 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

import (
"context"

"github.com/drone/go-scm/scm"
"github.com/drone/go-scm/scm/transport/oauth2"
)

// bitbucket cloud access_token endpoint.
const tokenEndpoint = "https://bitbucket.org/site/oauth2/access_token"

type tokenService struct {
refresher *oauth2.Refresher
}

func (t *tokenService) Refresh(ctx context.Context, token *scm.Token) (bool, error) {
if oauth2.Expired(token) == false {
return false, nil
}
t1 := token.Token
err := t.refresher.Refresh(token)
t2 := token.Token
return t1 != t2, err
}
74 changes: 74 additions & 0 deletions scm/driver/bitbucket/token_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
// Copyright 2017 Drone.IO Inc. 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

import (
"context"
"testing"
"time"

"github.com/drone/go-scm/scm"
"github.com/h2non/gock"
)

func TestTokenRefresh(t *testing.T) {
defer gock.Off()

gock.New("https://bitbucket.org").
Post("/site/oauth2/access_token").
MatchHeader("Authorization", "Basic NTU5OTE4YTgwODowMmJiYTUwMTJm").
Reply(200).
BodyString(`
{
"access_token": "9698fa6a8113b3",
"expires_in": 7200,
"refresh_token": "3a2bfce4cb9b0f",
"token_type": "bearer"
}
`)

token := &scm.Token{
Token: "ae215a0a8223a9",
Refresh: "3a2bfce4cb9b0f",
Expires: time.Now().Add(-time.Second),
}

client, _ := New("https://api.bitbucket.org",
WithClientID("559918a808"),
WithClientSecret("02bba5012f"),
)
refreshed, err := client.Tokens.Refresh(context.Background(), token)
if err != nil {
t.Error(err)
}

if !refreshed {
t.Errorf("Expected token refresh")
}
if got, want := token.Token, "9698fa6a8113b3"; got != want {
t.Errorf("Expected refresh token %s, got %s", want, got)
}
}

func TestTokenRefresh_NoRefresh(t *testing.T) {
token := &scm.Token{
Token: "ae215a0a8223a9",
Refresh: "3a2bfce4cb9b0f",
Expires: time.Now().Add(time.Hour),
}

client, _ := New("https://api.bitbucket.org",
WithClientID("559918a808"),
WithClientSecret("02bba5012f"),
)
refreshed, err := client.Tokens.Refresh(context.Background(), token)
if err != nil {
t.Error(err)
}

if refreshed {
t.Errorf("Expected token not refreshed")
}
}
1 change: 1 addition & 0 deletions scm/driver/gitea/gitea.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func New(uri string) (*scm.Client, error) {
client.PullRequests = &pullService{client}
client.Repositories = &repositoryService{client}
client.Reviews = &reviewService{client}
client.Tokens = &tokenService{}
client.Users = &userService{client}
client.Webhooks = &webhookService{client}
return client.Client, nil
Expand Down
20 changes: 20 additions & 0 deletions scm/driver/gitea/token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2018 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gitea

import (
"context"

"github.com/drone/go-scm/scm"
)

type tokenService struct {
}

func (t *tokenService) Refresh(context.Context, *scm.Token) (bool, error) {
// this function is a no-op because Gitea
// does not implement refresh tokens.
return false, nil
}
1 change: 1 addition & 0 deletions scm/driver/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ func New(uri string) (*scm.Client, error) {
client.PullRequests = &pullService{&issueService{client}}
client.Repositories = &repositoryService{client}
client.Reviews = &reviewService{client}
client.Tokens = &tokenService{}
client.Users = &userService{client}
client.Webhooks = &webhookService{client}
return client.Client, nil
Expand Down
20 changes: 20 additions & 0 deletions scm/driver/github/token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2018 Drone.IO Inc. 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

import (
"context"

"github.com/drone/go-scm/scm"
)

type tokenService struct {
}

func (t *tokenService) Refresh(context.Context, *scm.Token) (bool, error) {
// this function is a no-op because GitHub
// does not implement refresh tokens.
return false, nil
}
2 changes: 2 additions & 0 deletions scm/driver/gitlab/gitlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ func New(uri string) (*scm.Client, error) {
client.PullRequests = &pullService{client}
client.Repositories = &repositoryService{client}
client.Reviews = &reviewService{client}
client.Tokens = &tokenService{}
client.Users = &userService{client}
client.Webhooks = &webhookService{client}
return client.Client, nil
}

Expand Down
20 changes: 20 additions & 0 deletions scm/driver/gitlab/token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2018 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gitlab

import (
"context"

"github.com/drone/go-scm/scm"
)

type tokenService struct {
}

func (t *tokenService) Refresh(context.Context, *scm.Token) (bool, error) {
// this function is a no-op because GitLab
// does not implement refresh tokens.
return false, nil
}
1 change: 1 addition & 0 deletions scm/driver/gogs/gogs.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ func New(uri string) (*scm.Client, error) {
client.PullRequests = &pullService{client}
client.Repositories = &repositoryService{client}
client.Reviews = &reviewService{client}
client.Tokens = &tokenService{}
client.Users = &userService{client}
client.Webhooks = &webhookService{client}
return client.Client, nil
Expand Down
20 changes: 20 additions & 0 deletions scm/driver/gogs/token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2018 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gogs

import (
"context"

"github.com/drone/go-scm/scm"
)

type tokenService struct {
}

func (t *tokenService) Refresh(context.Context, *scm.Token) (bool, error) {
// this function is a no-op because Gogs
// does not implement refresh tokens.
return false, nil
}
1 change: 1 addition & 0 deletions scm/driver/stash/stash.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func New(uri string) (*scm.Client, error) {
client.PullRequests = &pullService{client}
client.Repositories = &repositoryService{client}
client.Reviews = &reviewService{client}
client.Tokens = &tokenService{}
client.Users = &userService{client}
client.Webhooks = &webhookService{client}
return client.Client, nil
Expand Down
20 changes: 20 additions & 0 deletions scm/driver/stash/token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2018 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package stash

import (
"context"

"github.com/drone/go-scm/scm"
)

type tokenService struct {
}

func (t *tokenService) Refresh(context.Context, *scm.Token) (bool, error) {
// this function is a no-op because Bitbucket Server
// does not implement refresh tokens.
return false, nil
}
12 changes: 10 additions & 2 deletions scm/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,20 @@ type (
Token(context.Context) (*Token, error)
}

// TokenService provides token refresh capabilities.
TokenService interface {
// Refresh refreshes the token if necessary. The
// function returns a boolean value indicating
// whether or not the token is refreshed.
Refresh(context.Context, *Token) (bool, error)
}

// TokenKey is the key to use with the context.WithValue
// function to associate an Token value with a context.
TokenKey struct{}
)

// WithContext returns a copy of parent in which the token value is set
func WithContext(parent context.Context, token *Token) context.Context {
// WithToken returns a copy of parent in which the token value is set
func WithToken(parent context.Context, token *Token) context.Context {
return context.WithValue(parent, TokenKey{}, token)
}
Loading

0 comments on commit 7463104

Please sign in to comment.