Skip to content

Commit 9753ba4

Browse files
oauth2-client-credentials-generic
Summary: - Support for configurable `oauth2` client credentials pattern. - CICD dead code removed. - Added robot test `Oauth2 CLient Credentials Auth Should Succeed with Valid Config`. - Added robot test `Oauth2 CLient Credentials Auth Should Fail with Invalid Config`.
1 parent 1f02226 commit 9753ba4

File tree

26 files changed

+1146
-39
lines changed

26 files changed

+1146
-39
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: build
1+
name: buildexpt
22

33
on:
44
push:
Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ jobs:
3333
winbuild:
3434
name: Windows Build
3535
runs-on: windows-latest
36+
timeout-minutes: ${{ vars.DEFAULT_JOB_TIMEOUT_MIN == '' && 120 || vars.DEFAULT_JOB_TIMEOUT_MIN }}
3637
steps:
3738

3839
- name: Get rid of disruptive line endings before checkout
@@ -78,7 +79,7 @@ jobs:
7879
- name: Set up mingw
7980
uses: egor-tensin/setup-mingw@v2.2.0
8081
id: gccsetup
81-
timeout-minutes: 20
82+
timeout-minutes: ${{ vars.DEFAULT_STEP_TIMEOUT_MIN == '' && 20 || vars.DEFAULT_STEP_TIMEOUT_MIN }}
8283
with:
8384
version: '8.1.0'
8485

@@ -93,13 +94,13 @@ jobs:
9394
9495
- name: Choco install openssl
9596
uses: crazy-max/ghaction-chocolatey@v3.0.0
96-
timeout-minutes: 40
97+
timeout-minutes: ${{ vars.DEFAULT_LONG_STEP_TIMEOUT_MIN == '' && 40 || vars.DEFAULT_LONG_STEP_TIMEOUT_MIN }}
9798
with:
9899
args: "install --force openssl --version 3.1.1"
99100

100101
- name: Choco install packages
101102
uses: crazy-max/ghaction-chocolatey@v3.0.0
102-
timeout-minutes: 40
103+
timeout-minutes: ${{ vars.DEFAULT_LONG_STEP_TIMEOUT_MIN == '' && 40 || vars.DEFAULT_LONG_STEP_TIMEOUT_MIN }}
103104
with:
104105
args: "install --force postgresql13 sqlite"
105106

@@ -206,6 +207,7 @@ jobs:
206207
linuxbuild:
207208
name: Linux Build
208209
runs-on: ubuntu-latest
210+
timeout-minutes: ${{ vars.DEFAULT_JOB_TIMEOUT_MIN == '' && 120 || vars.DEFAULT_JOB_TIMEOUT_MIN }}
209211
steps:
210212

211213
- name: Check out code into the Go module directory
@@ -463,6 +465,7 @@ jobs:
463465
linuxarmbuild:
464466
name: Linux arm64 Build
465467
runs-on: arm-ubuntu-22-04-runner-one
468+
timeout-minutes: ${{ vars.DEFAULT_JOB_TIMEOUT_MIN == '' && 120 || vars.DEFAULT_JOB_TIMEOUT_MIN }}
466469
steps:
467470

468471
- name: Check out code into the Go module directory
@@ -746,6 +749,7 @@ jobs:
746749
name: WSL Test
747750
runs-on: windows-latest
748751
needs: linuxbuild
752+
timeout-minutes: ${{ vars.DEFAULT_JOB_TIMEOUT_MIN == '' && 120 || vars.DEFAULT_JOB_TIMEOUT_MIN }}
749753
steps:
750754

751755
- name: Get rid of disruptive line endings before checkout
@@ -860,6 +864,7 @@ jobs:
860864
macosbuild:
861865
name: MacOS Build
862866
runs-on: macos-12
867+
timeout-minutes: ${{ vars.DEFAULT_JOB_TIMEOUT_MIN == '' && 120 || vars.DEFAULT_JOB_TIMEOUT_MIN }}
863868
steps:
864869
- name: Check out code into the Go module directory
865870
uses: actions/checkout@v4.1.1
@@ -998,6 +1003,7 @@ jobs:
9981003
macosarmbuild:
9991004
name: MacOS ARM Build
10001005
runs-on: macos-latest
1006+
timeout-minutes: ${{ vars.DEFAULT_JOB_TIMEOUT_MIN == '' && 120 || vars.DEFAULT_JOB_TIMEOUT_MIN }}
10011007
steps:
10021008

10031009
- name: Check out code into the Go module directory
@@ -1075,6 +1081,7 @@ jobs:
10751081
dockerbuild:
10761082
name: Docker Build
10771083
runs-on: ubuntu-latest-m
1084+
timeout-minutes: ${{ vars.DEFAULT_JOB_TIMEOUT_MIN == '' && 120 || vars.DEFAULT_JOB_TIMEOUT_MIN }}
10781085
steps:
10791086

10801087
- name: Check out code into the Go module directory
@@ -1227,13 +1234,13 @@ jobs:
12271234
12281235
- name: Run robot mocked functional tests
12291236
if: success()
1230-
timeout-minutes: 20
1237+
timeout-minutes: ${{ vars.DEFAULT_STEP_TIMEOUT_MIN == '' && 20 || vars.DEFAULT_STEP_TIMEOUT_MIN }}
12311238
run: |
12321239
python cicd/python/build.py --robot-test --config='{ "variables": { "EXECUTION_PLATFORM": "docker" } }'
12331240
12341241
- name: Run POSTGRES BACKEND robot mocked functional tests
12351242
if: success()
1236-
timeout-minutes: 20
1243+
timeout-minutes: ${{ vars.DEFAULT_STEP_TIMEOUT_MIN == '' && 20 || vars.DEFAULT_STEP_TIMEOUT_MIN }}
12371244
run: |
12381245
echo "## Stray docker containers before postgres robot tests ##"
12391246
docker container ls --format "table {{.ID}}\t{{.Names}}\t{{.Ports}}" -a

cicd/requirements.txt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
Flask==3.0.3
12
psycopg2-binary>=2.9.9
23
psycopg[binary]>=3.1.16
34
PyYaml>=6.0.1
4-
robotframework>=6.1.1
5-
sqlalchemy==1.4.44
5+
requests==2.32.3
6+
robotframework==6.1.1
7+
sqlalchemy==1.4.44

cicd/version.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
MajorVersion=0
2-
MinorVersion=5
2+
MinorVersion=6

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ require (
2121
github.com/spf13/cobra v1.4.0
2222
github.com/spf13/pflag v1.0.5
2323
github.com/spf13/viper v1.10.1
24-
github.com/stackql/any-sdk v0.0.3-beta17
24+
github.com/stackql/any-sdk v0.0.3-beta21
2525
github.com/stackql/go-suffix-map v0.0.1-alpha01
2626
github.com/stackql/psql-wire v0.1.1-alpha07
2727
github.com/stackql/stackql-parser v0.0.14-alpha04

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -471,8 +471,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
471471
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
472472
github.com/spf13/viper v1.10.1 h1:nuJZuYpG7gTj/XqiUwg8bA0cp1+M2mC3J4g5luUYBKk=
473473
github.com/spf13/viper v1.10.1/go.mod h1:IGlFPqhNAPKRxohIzWpI5QEy4kuI7tcl5WvR+8qy1rU=
474-
github.com/stackql/any-sdk v0.0.3-beta17 h1:eajJfNOZBvWwaUD9WdsS81PAKHFxyHndmpdOo3P867A=
475-
github.com/stackql/any-sdk v0.0.3-beta17/go.mod h1:CIMFo3fC2ScpqzkzeCkzUQQuzYA1VuqpG0p1EZXN+wY=
474+
github.com/stackql/any-sdk v0.0.3-beta21 h1:1x76S9scXukHKcBUmzSVYpwWG8TnZXMhlgU0HHcTO2g=
475+
github.com/stackql/any-sdk v0.0.3-beta21/go.mod h1:CIMFo3fC2ScpqzkzeCkzUQQuzYA1VuqpG0p1EZXN+wY=
476476
github.com/stackql/go-suffix-map v0.0.1-alpha01 h1:TDUDS8bySu41Oo9p0eniUeCm43mnRM6zFEd6j6VUaz8=
477477
github.com/stackql/go-suffix-map v0.0.1-alpha01/go.mod h1:QAi+SKukOyf4dBtWy8UMy+hsXXV+yyEE4vmBkji2V7g=
478478
github.com/stackql/psql-wire v0.1.1-alpha07 h1:LQWVUlx4Bougk6dztDNG5tmXxpIVeeTSsInTj801xCs=

internal/stackql/dto/auth_ctx.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package dto
33
import (
44
"encoding/base64"
55
"fmt"
6+
"net/url"
67
"os"
78
"strings"
89
)
@@ -42,6 +43,14 @@ type AuthCtx struct {
4243
Active bool `json:"-" yaml:"-"`
4344
Location string `json:"location" yaml:"location"`
4445
Name string `json:"name" yaml:"name"`
46+
TokenURL string `json:"token_url" yaml:"token_url"`
47+
GrantType string `json:"grant_type" yaml:"grant_type"`
48+
ClientID string `json:"client_id" yaml:"client_id"`
49+
ClientSecret string `json:"client_secret" yaml:"client_secret"`
50+
ClientIDEnvVar string `json:"client_id_env_var" yaml:"client_id_env_var"`
51+
ClientSecretEnvVar string `json:"client_secret_env_var" yaml:"client_secret_env_var"`
52+
Values url.Values `json:"values" yaml:"values"`
53+
AuthStyle int `json:"auth_style" yaml:"auth_style"`
4554
}
4655

4756
func (ac *AuthCtx) GetSQLCfg() (SQLBackendCfg, bool) {
@@ -78,10 +87,26 @@ func (ac *AuthCtx) Clone() *AuthCtx {
7887
EncodedBasicCredentials: ac.EncodedBasicCredentials,
7988
Location: ac.Location,
8089
Name: ac.Name,
90+
Subject: ac.Subject,
91+
TokenURL: ac.TokenURL,
92+
GrantType: ac.GrantType,
93+
ClientID: ac.ClientID,
94+
ClientSecret: ac.ClientSecret,
95+
ClientIDEnvVar: ac.ClientIDEnvVar,
96+
ClientSecretEnvVar: ac.ClientSecretEnvVar,
97+
Values: ac.Values,
98+
AuthStyle: ac.AuthStyle,
8199
}
82100
return rv
83101
}
84102

103+
func (ac *AuthCtx) GetValues() url.Values {
104+
if ac.Values == nil {
105+
return url.Values{}
106+
}
107+
return ac.Values
108+
}
109+
85110
func (ac *AuthCtx) GetSuccessor() (*AuthCtx, bool) {
86111
if ac.Successor != nil {
87112
return ac.Successor, true
@@ -188,6 +213,46 @@ func (ac *AuthCtx) GetCredentialsBytes() ([]byte, error) {
188213
return nil, fmt.Errorf("no credentials found")
189214
}
190215

216+
func (ac *AuthCtx) GetClientID() (string, error) {
217+
if ac.ClientIDEnvVar != "" {
218+
rv := os.Getenv(ac.ClientIDEnvVar)
219+
if rv == "" {
220+
return "", fmt.Errorf("client_id_env_var references empty string")
221+
}
222+
return rv, nil
223+
}
224+
if ac.ClientID == "" {
225+
return "", fmt.Errorf("client_id is empty")
226+
}
227+
return ac.ClientID, nil
228+
}
229+
230+
func (ac *AuthCtx) GetClientSecret() (string, error) {
231+
if ac.ClientSecretEnvVar != "" {
232+
rv := os.Getenv(ac.ClientSecretEnvVar)
233+
if rv == "" {
234+
return "", fmt.Errorf("client_secret_env_var references empty string")
235+
}
236+
return rv, nil
237+
}
238+
if ac.ClientSecret == "" {
239+
return "", fmt.Errorf("client_secret is empty")
240+
}
241+
return ac.ClientSecret, nil
242+
}
243+
244+
func (ac *AuthCtx) GetGrantType() string {
245+
return ac.GrantType
246+
}
247+
248+
func (ac *AuthCtx) GetTokenURL() string {
249+
return ac.TokenURL
250+
}
251+
252+
func (ac *AuthCtx) GetAuthStyle() int {
253+
return ac.AuthStyle
254+
}
255+
191256
func (ac *AuthCtx) GetCredentialsSourceDescriptorString() string {
192257
if ac.KeyEnvVar != "" {
193258
return fmt.Sprintf("credentialsenvvar:%s", ac.KeyEnvVar)

internal/stackql/dto/dto.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const (
1616
APIRequestTimeoutKey string = "apirequesttimeout"
1717
CacheKeyCountKey string = "cachekeycount"
1818
CacheTTLKey string = "metadatattl"
19+
ClientCredentialsStr string = "client_credentials"
1920
ColorSchemeKey string = "colorscheme" // deprecated
2021
ConfigFilePathKey string = "configfile"
2122
CPUProfileKey string = "cpuprofile"
@@ -37,6 +38,7 @@ const (
3738
AllowInsecureKey string = "tls.allowInsecure"
3839
InfilePathKey string = "infile"
3940
LogLevelStrKey string = "loglevel"
41+
OAuth2Str string = "oauth2"
4042
OutfilePathKey string = "outfile"
4143
OutputFormatKey string = "output"
4244
ApplicationFilesRootPathKey string = "approot"

internal/stackql/handler/handler.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,14 @@ func transformOpenapiStackqlAuthToLocal(authDTO anysdk.AuthDTO) *dto.AuthCtx {
575575
EncodedBasicCredentials: authDTO.GetInlineBasicCredentials(),
576576
Location: authDTO.GetLocation(),
577577
Name: authDTO.GetName(),
578+
TokenURL: authDTO.GetTokenURL(),
579+
GrantType: authDTO.GetGrantType(),
580+
ClientID: authDTO.GetClientID(),
581+
ClientSecret: authDTO.GetClientSecret(),
582+
ClientIDEnvVar: authDTO.GetClientIDEnvVar(),
583+
ClientSecretEnvVar: authDTO.GetClientSecretEnvVar(),
584+
Values: authDTO.GetValues(),
585+
AuthStyle: authDTO.GetAuthStyle(),
578586
}
579587
successor, successorExists := authDTO.GetSuccessor()
580588
currentParent := rv

internal/stackql/provider/auth_util.go

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"regexp"
1616

1717
"golang.org/x/oauth2"
18+
"golang.org/x/oauth2/clientcredentials"
1819
"golang.org/x/oauth2/google"
1920
"golang.org/x/oauth2/jwt"
2021
)
@@ -161,11 +162,21 @@ func parseServiceAccountFile(ac *dto.AuthCtx) (serviceAccount, error) {
161162
return c, json.Unmarshal(b, &c)
162163
}
163164

164-
func getJWTConfig(provider string, credentialsBytes []byte, scopes []string, subject string) (*jwt.Config, error) {
165+
func getGoogleJWTConfig(
166+
provider string,
167+
credentialsBytes []byte,
168+
scopes []string,
169+
subject string,
170+
) (*jwt.Config, error) {
165171
switch provider {
166172
case "google", "googleads", "googleanalytics",
167173
"googledevelopers", "googlemybusiness", "googleworkspace",
168174
"youtube", "googleadmin":
175+
if scopes == nil {
176+
scopes = []string{
177+
"https://www.googleapis.com/auth/cloud-platform",
178+
}
179+
}
169180
rv, err := google.JWTConfigFromJSON(credentialsBytes, scopes...)
170181
if err != nil {
171182
return nil, err
@@ -179,7 +190,31 @@ func getJWTConfig(provider string, credentialsBytes []byte, scopes []string, sub
179190
}
180191
}
181192

182-
func oauthServiceAccount(
193+
func getGenericClientCredentialsConfig(authCtx *dto.AuthCtx, scopes []string) (*clientcredentials.Config, error) {
194+
clientID, clientIDErr := authCtx.GetClientID()
195+
if clientIDErr != nil {
196+
return nil, clientIDErr
197+
}
198+
clientSecret, secretErr := authCtx.GetClientSecret()
199+
if secretErr != nil {
200+
return nil, secretErr
201+
}
202+
rv := &clientcredentials.Config{
203+
ClientID: clientID,
204+
ClientSecret: clientSecret,
205+
Scopes: scopes,
206+
TokenURL: authCtx.GetTokenURL(),
207+
}
208+
if len(authCtx.GetValues()) > 0 {
209+
rv.EndpointParams = authCtx.GetValues()
210+
}
211+
if authCtx.GetAuthStyle() > 0 {
212+
rv.AuthStyle = oauth2.AuthStyle(authCtx.GetAuthStyle())
213+
}
214+
return rv, nil
215+
}
216+
217+
func googleOauthServiceAccount(
183218
provider string,
184219
authCtx *dto.AuthCtx,
185220
scopes []string,
@@ -189,14 +224,27 @@ func oauthServiceAccount(
189224
if err != nil {
190225
return nil, fmt.Errorf("service account credentials error: %w", err)
191226
}
192-
config, errToken := getJWTConfig(provider, b, scopes, authCtx.Subject)
227+
config, errToken := getGoogleJWTConfig(provider, b, scopes, authCtx.Subject)
193228
if errToken != nil {
194229
return nil, errToken
195230
}
196231
activateAuth(authCtx, "", dto.AuthServiceAccountStr)
197232
httpClient := netutils.GetHTTPClient(runtimeCtx, http.DefaultClient)
198-
//nolint:staticcheck // TODO: fix this
199-
return config.Client(context.WithValue(oauth2.NoContext, oauth2.HTTPClient, httpClient)), nil
233+
return config.Client(context.WithValue(context.Background(), oauth2.HTTPClient, httpClient)), nil
234+
}
235+
236+
func genericOauthClientCredentials(
237+
authCtx *dto.AuthCtx,
238+
scopes []string,
239+
runtimeCtx dto.RuntimeCtx,
240+
) (*http.Client, error) {
241+
config, errToken := getGenericClientCredentialsConfig(authCtx, scopes)
242+
if errToken != nil {
243+
return nil, errToken
244+
}
245+
activateAuth(authCtx, "", dto.ClientCredentialsStr)
246+
httpClient := netutils.GetHTTPClient(runtimeCtx, http.DefaultClient)
247+
return config.Client(context.WithValue(context.Background(), oauth2.HTTPClient, httpClient)), nil
200248
}
201249

202250
func apiTokenAuth(authCtx *dto.AuthCtx, runtimeCtx dto.RuntimeCtx, enforceBearer bool) (*http.Client, error) {

0 commit comments

Comments
 (0)