Skip to content

Commit 79e5650

Browse files
committed
feat(auth): add support for OAuth2 Client Credentials
Authenticating using a bare OAuth2 Access Token is not very convenient since it has limited validity and needs to be aquired by other means outside of the provider. Bitbucket supports authentication via Client Credentials Grant, which is easier to use. This change adds support for the `oauth_client_id` and `oauth_client_secret` provider configuration fields (and environment variables) and updates the provider documentation. This is an alternative to the existing auth via `username`/`password` or `oauth_token`.
1 parent e37f974 commit 79e5650

File tree

5 files changed

+102
-21
lines changed

5 files changed

+102
-21
lines changed

Diff for: README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Requirements
1111
------------
1212

1313
- [Terraform](https://www.terraform.io/downloads.html) 1.x
14-
- [Go](https://golang.org/doc/install) 9 (to build the provider plugin)
14+
- [Go](https://golang.org/doc/install) 1.19 (to build the provider plugin)
1515

1616
Building The Provider
1717
---------------------

Diff for: bitbucket/client.go

+17-6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"io"
88
"log"
99
"net/http"
10+
11+
"golang.org/x/oauth2"
1012
)
1113

1214
// Error represents a error from the bitbucket api.
@@ -31,15 +33,15 @@ const (
3133
// Client is the base internal Client to talk to bitbuckets API. This should be a username and password
3234
// the password should be a app-password.
3335
type Client struct {
34-
Username *string
35-
Password *string
36-
OAuthToken *string
37-
HTTPClient *http.Client
36+
Username *string
37+
Password *string
38+
OAuthToken *string
39+
OAuthTokenSource oauth2.TokenSource
40+
HTTPClient *http.Client
3841
}
3942

4043
// Do Will just call the bitbucket api but also add auth to it and some extra headers
4144
func (c *Client) Do(method, endpoint string, payload *bytes.Buffer, addJsonHeader bool) (*http.Response, error) {
42-
4345
absoluteendpoint := BitbucketEndpoint + endpoint
4446
log.Printf("[DEBUG] Sending request to %s %s", method, absoluteendpoint)
4547

@@ -62,10 +64,19 @@ func (c *Client) Do(method, endpoint string, payload *bytes.Buffer, addJsonHeade
6264

6365
if c.OAuthToken != nil {
6466
log.Printf("[DEBUG] Setting Bearer Token")
65-
var bearer = "Bearer " + *c.OAuthToken
67+
bearer := "Bearer " + *c.OAuthToken
6668
req.Header.Add("Authorization", bearer)
6769
}
6870

71+
if c.OAuthTokenSource != nil {
72+
token, err := c.OAuthTokenSource.Token()
73+
if err != nil {
74+
return nil, err
75+
}
76+
77+
token.SetAuthHeader(req)
78+
}
79+
6980
if payload != nil && addJsonHeader {
7081
// Can cause bad request when putting default reviews if set.
7182
req.Header.Add("Content-Type", "application/json")

Diff for: bitbucket/provider.go

+43-6
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88

99
"github.com/DrFaust92/bitbucket-go-client"
1010
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
11+
oauth2bitbucket "golang.org/x/oauth2/bitbucket"
12+
oauth2clientcreds "golang.org/x/oauth2/clientcredentials"
1113
)
1214

1315
type ProviderConfig struct {
@@ -20,30 +22,48 @@ type Clients struct {
2022
httpClient Client
2123
}
2224

23-
// Provider will create the necessary terraform provider to talk to the Bitbucket APIs you should
24-
// specify a USERNAME and PASSWORD or a OAUTH Token
25+
// Provider will create the necessary terraform provider to talk to the
26+
// Bitbucket APIs you should either specify Username and App Password, OAuth
27+
// Client Credentials or a valid OAuth Access Token.
28+
//
29+
// See the Bitbucket authentication documentation for more:
30+
// https://developer.atlassian.com/cloud/bitbucket/rest/intro/#authentication
2531
func Provider() *schema.Provider {
2632
return &schema.Provider{
2733
Schema: map[string]*schema.Schema{
2834
"username": {
2935
Optional: true,
3036
Type: schema.TypeString,
3137
DefaultFunc: schema.EnvDefaultFunc("BITBUCKET_USERNAME", nil),
32-
ConflictsWith: []string{"oauth_token"},
38+
ConflictsWith: []string{"oauth_client_id", "oauth_client_secret", "oauth_token"},
3339
RequiredWith: []string{"password"},
3440
},
3541
"password": {
3642
Type: schema.TypeString,
3743
Optional: true,
3844
DefaultFunc: schema.EnvDefaultFunc("BITBUCKET_PASSWORD", nil),
39-
ConflictsWith: []string{"oauth_token"},
45+
ConflictsWith: []string{"oauth_client_id", "oauth_client_secret", "oauth_token"},
4046
RequiredWith: []string{"username"},
4147
},
48+
"oauth_client_id": {
49+
Type: schema.TypeString,
50+
Optional: true,
51+
DefaultFunc: schema.EnvDefaultFunc("BITBUCKET_OAUTH_CLIENT_ID", nil),
52+
ConflictsWith: []string{"username", "password", "oauth_token"},
53+
RequiredWith: []string{"oauth_client_secret"},
54+
},
55+
"oauth_client_secret": {
56+
Type: schema.TypeString,
57+
Optional: true,
58+
DefaultFunc: schema.EnvDefaultFunc("BITBUCKET_OAUTH_CLIENT_SECRET", nil),
59+
ConflictsWith: []string{"username", "password", "oauth_token"},
60+
RequiredWith: []string{"oauth_client_id"},
61+
},
4262
"oauth_token": {
4363
Type: schema.TypeString,
4464
Optional: true,
4565
DefaultFunc: schema.EnvDefaultFunc("BITBUCKET_OAUTH_TOKEN", nil),
46-
ConflictsWith: []string{"username", "password"},
66+
ConflictsWith: []string{"username", "password", "oauth_client_id", "oauth_client_secret"},
4767
},
4868
},
4969
ConfigureFunc: providerConfigure,
@@ -86,7 +106,6 @@ func Provider() *schema.Provider {
86106
}
87107

88108
func providerConfigure(d *schema.ResourceData) (interface{}, error) {
89-
90109
authCtx := context.Background()
91110

92111
client := &Client{
@@ -118,6 +137,24 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) {
118137
authCtx = context.WithValue(authCtx, bitbucket.ContextAccessToken, token)
119138
}
120139

140+
if clientID, ok := d.GetOk("oauth_client_id"); ok {
141+
clientSecret, ok := d.GetOk("oauth_client_secret")
142+
if !ok {
143+
return nil, fmt.Errorf("found client ID for OAuth via Client Credentials Grant, but client secret was not specified")
144+
}
145+
146+
config := &oauth2clientcreds.Config{
147+
ClientID: clientID.(string),
148+
ClientSecret: clientSecret.(string),
149+
TokenURL: oauth2bitbucket.Endpoint.TokenURL,
150+
}
151+
152+
tokenSource := config.TokenSource(authCtx)
153+
154+
client.OAuthTokenSource = tokenSource
155+
authCtx = context.WithValue(authCtx, bitbucket.ContextOAuth2, tokenSource)
156+
}
157+
121158
conf := bitbucket.NewConfiguration()
122159
apiClient := ProviderConfig{
123160
ApiClient: bitbucket.NewAPIClient(conf),

Diff for: docs/index.md

+40-7
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,48 @@ resource "bitbucket_project" "project" {
4141

4242
The following arguments are supported in the `provider` block:
4343

44-
* `username` - (Optional) Your username used to connect to bitbucket. You can
45-
also set this via the environment variable. `BITBUCKET_USERNAME`
44+
* `username` - (Optional) Username to use for authentication via [Basic
45+
Auth](https://developer.atlassian.com/cloud/bitbucket/rest/intro/#basic-auth).
46+
You can also set this via the `BITBUCKET_USERNAME` environment variable.
47+
If configured, requires `password` to be configured as well.
4648

47-
* `password` - (Optional) Your password used to connect to bitbucket. You can
48-
also set this via the environment variable. `BITBUCKET_PASSWORD`
49+
* `password` - (Optional) Password to use for authentication via [Basic
50+
Auth](https://developer.atlassian.com/cloud/bitbucket/rest/intro/#basic-auth).
51+
Please note that this has to be an [App
52+
Password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/)
53+
that has to be created in the [Account
54+
Settings](https://bitbucket.org/account/settings/app-passwords/). If
55+
configured, requires `username` to be configured as well.
4956

50-
* `oauth_token` - (Optional) Your password used to connect to bitbucket. You can
51-
also set this via the environment variable. `BITBUCKET_OAUTH_TOKEN`
57+
* `oauth_client_id` - (Optional) OAuth client ID to use for authentication via
58+
[Client Credentials
59+
Grant](https://developer.atlassian.com/cloud/bitbucket/rest/intro/#3--client-credentials-grant--4-4-).
60+
You can also set this via the `BITBUCKET_OAUTH_CLIENT_ID` environment
61+
variable. If configured, requires `oauth_client_secret` to be configured as
62+
well.
63+
64+
* `oauth_client_secret` - (Optional) OAuth client secret to use for authentication via
65+
[Client Credentials
66+
Grant](https://developer.atlassian.com/cloud/bitbucket/rest/intro/#3--client-credentials-grant--4-4-).
67+
You can also set this via the `BITBUCKET_OAUTH_CLIENT_SECRET` environment
68+
variable. If configured, requires `oauth_client_id` to be configured as well.
69+
70+
* `oauth_token` - (Optional) An OAuth access token used for authentication via
71+
[OAuth](https://developer.atlassian.com/cloud/bitbucket/rest/intro/#oauth-2-0).
72+
You can also set this via the `BITBUCKET_OAUTH_TOKEN` environment variable.
5273

5374
## OAuth2 Scopes
5475

55-
To interacte with the Bitbucket API, an [App Password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/) is required. App passwords are limited in scope, each API requires certain scopse to interact with, each resource doc will specifiy what are the scopes required to use that resource. See [Docs](https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/) for more inforamtion on scopes.
76+
To interacte with the Bitbucket API, an [App
77+
Password](https://support.atlassian.com/bitbucket-cloud/docs/app-passwords/) or
78+
[OAuth Client
79+
Credentials](https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/)
80+
are required.
81+
82+
App passwords and OAuth client credentials are limited in scope, each API
83+
requires certain scope to interact with, each resource doc will specify what
84+
are the scopes required to use that resource.
85+
86+
See the [Bitbucket OAuth
87+
Documentation](https://support.atlassian.com/bitbucket-cloud/docs/use-oauth-on-bitbucket-cloud/)
88+
for more information on scopes.

Diff for: go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ require (
66
github.com/hashicorp/terraform-plugin-sdk/v2 v2.24.1
77
github.com/satori/go.uuid v1.2.0
88
golang.org/x/crypto v0.0.0-20220817201139-bc19a97f63c8
9+
golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7
910
)
1011

1112
require (
@@ -48,7 +49,6 @@ require (
4849
github.com/vmihailenco/tagparser v0.1.2 // indirect
4950
github.com/zclconf/go-cty v1.12.1 // indirect
5051
golang.org/x/net v0.0.0-20220812174116-3211cb980234 // indirect
51-
golang.org/x/oauth2 v0.0.0-20220808172628-8227340efae7 // indirect
5252
golang.org/x/sys v0.0.0-20220818161305-2296e01440c6 // indirect
5353
golang.org/x/text v0.3.7 // indirect
5454
google.golang.org/appengine v1.6.7 // indirect

0 commit comments

Comments
 (0)