git.iko.soy/typeable/oauth2-proxy
on master help sign in
feat: allow use more possible google admin-sdk api scopes (#2743)

* feat: Allow use more possible google admin-sdk api scopes. * reduce cognitive complexity Signed-off-by: Bob Du <i@bobdu.cc> * remove unnecessary else block / indentation Signed-off-by: Jan Larwig <jan@larwig.com> * add changelog entry Signed-off-by: Jan Larwig <jan@larwig.com> * slight formatting and error message rephrasing Signed-off-by: Jan Larwig <jan@larwig.com> --------- Signed-off-by: Bob Du <i@bobdu.cc> Signed-off-by: Jan Larwig <jan@larwig.com> Co-authored-by: Jan Larwig <jan@larwig.com>

commit4d17bc1d6834b62c49548b88eff3f8d2a666ef8d authorBob Du <i@bobdu.cc> dateMon 21 Jul 2025, 15:06 (1 year ago) parent3ac834d
4 files changed+65−23
.gitignore+1 CHANGELOG.md+1 docs/docs/configuration/providers/google.md+3−4 providers/google.go+60−19
.gitignore+1
@@ -17,6 +17,7 @@ c.out
1717# Folders
1818_obj
1919_test
20+.DS_Store
2021.idea/
2122.vscode/*
2223!/.vscode/tasks.json
CHANGELOG.md+1
@@ -10,6 +10,7 @@
1010
1111- [#2615](https://github.com/oauth2-proxy/oauth2-proxy/pull/2615) feat(cookies): add option to set a limit on the number of per-request CSRF cookies oauth2-proxy sets (@bh-tt)
1212- [#2605](https://github.com/oauth2-proxy/oauth2-proxy/pull/2605) fix: show login page on broken cookie (@Primexz)
13+- [#2743](https://github.com/oauth2-proxy/oauth2-proxy/pull/2743) feat: allow use more possible google admin-sdk api scopes (@BobDu)
1314
1415# V7.10.0
1516
docs/docs/configuration/providers/google.md+3−4
@@ -37,18 +37,17 @@ account is still authorized.
3737
3838#### Restrict auth to specific Google groups on your domain. (optional)
3939
40−1. Create a [service account](https://developers.google.com/identity/protocols/OAuth2ServiceAccount) and configure it
40+1. Create a [service account](https://developers.google.com/identity/protocols/oauth2/service-account) and configure it
4141 to use [Application Default Credentials / Workload Identity / Workload Identity Federation (recommended)](#using-application-default-credentials-adc--workload-identity--workload-identity-federation-recommended) or,
4242 alternatively download the JSON.
43432. Make note of the Client ID for a future step.
44443. Under "APIs & Auth", choose APIs.
45454. Click on Admin SDK and then Enable API.
46−5. Follow the steps on https://developers.google.com/admin-sdk/directory/v1/guides/delegation#delegate_domain-wide_authority_to_your_service_account
46+5. Follow the steps on [Set up domain-wide delegation for a service account](https://developers.google.com/workspace/guides/create-credentials#optional_set_up_domain-wide_delegation_for_a_service_account)
4747 and give the client id from step 2 the following oauth scopes:
4848
4949 ```
50− https://www.googleapis.com/auth/admin.directory.group.readonly
51− https://www.googleapis.com/auth/admin.directory.user.readonly
50+ https://www.googleapis.com/auth/admin.directory.group.member.readonly
5251 ```
5352
54536. Follow the steps on https://support.google.com/a/answer/60757 to enable Admin API access.
providers/google.go+60−19
@@ -229,38 +229,79 @@ func (p *GoogleProvider) setGroupRestriction(opts options.GoogleOptions) {
229229 }
230230}
231231
232−func getAdminService(opts options.GoogleOptions) *admin.Service {
233− ctx := context.Background()
234− var client *http.Client
232+// https://developers.google.com/admin-sdk/directory/reference/rest/v1/members/hasMember#authorization-scopes
233+var possibleScopesList = [...]string{
234+ admin.AdminDirectoryGroupMemberReadonlyScope,
235+ admin.AdminDirectoryGroupReadonlyScope,
236+ admin.AdminDirectoryGroupMemberScope,
237+ admin.AdminDirectoryGroupScope,
238+}
239+
240+func getOauth2TokenSource(ctx context.Context, opts options.GoogleOptions, scope string) oauth2.TokenSource {
235241 if opts.UseApplicationDefaultCredentials {
236242 ts, err := impersonate.CredentialsTokenSource(ctx, impersonate.CredentialsConfig{
237243 TargetPrincipal: getTargetPrincipal(ctx, opts),
238− Scopes: []string{admin.AdminDirectoryGroupReadonlyScope, admin.AdminDirectoryUserReadonlyScope},
244+ Scopes: []string{scope},
239245 Subject: opts.AdminEmail,
240246 })
241247 if err != nil {
242248 logger.Fatal("failed to fetch application default credentials: ", err)
243249 }
244− client = oauth2.NewClient(ctx, ts)
245− } else {
246− credentialsReader, err := os.Open(opts.ServiceAccountJSON)
247− if err != nil {
248− logger.Fatal("couldn't open Google credentials file: ", err)
249− return nil
250− }
250+ return ts
251+ }
251252
252− data, err := io.ReadAll(credentialsReader)
253− if err != nil {
254− logger.Fatal("can't read Google credentials file:", err)
253+ credentialsReader, err := os.Open(opts.ServiceAccountJSON)
254+ if err != nil {
255+ logger.Fatal("couldn't open Google credentials file: ", err)
256+ }
257+
258+ data, err := io.ReadAll(credentialsReader)
259+ if err != nil {
260+ logger.Fatal("can't read Google credentials file:", err)
261+ }
262+
263+ conf, err := google.JWTConfigFromJSON(data, scope)
264+ if err != nil {
265+ logger.Fatal("can't load Google credentials file:", err)
266+ }
267+
268+ conf.Subject = opts.AdminEmail
269+ return conf.TokenSource(ctx)
270+}
271+
272+func getAdminService(opts options.GoogleOptions) *admin.Service {
273+ ctx := context.Background()
274+ var client *http.Client
275+
276+ for _, scope := range possibleScopesList {
277+
278+ ts := getOauth2TokenSource(ctx, opts, scope)
279+ _, err := ts.Token()
280+
281+ if err == nil {
282+ client = oauth2.NewClient(ctx, ts)
283+ break
255284 }
256285
257− conf, err := google.JWTConfigFromJSON(data, admin.AdminDirectoryUserReadonlyScope, admin.AdminDirectoryGroupReadonlyScope)
258− if err != nil {
259− logger.Fatal("can't load Google credentials file:", err)
286+ if retrieveErr, ok := err.(*oauth2.RetrieveError); ok {
287+ retrieveErrBody := map[string]interface{}{}
288+
289+ if err := json.Unmarshal(retrieveErr.Body, &retrieveErrBody); err != nil {
290+ logger.Fatal("error unmarshalling retrieveErr body:", err)
291+ }
292+
293+ if retrieveErrBody["error"] == "unauthorized_client" && retrieveErrBody["error_description"] == "Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested." {
294+ continue
295+ }
296+
297+ logger.Fatal("error retrieving token:", err)
260298 }
261− conf.Subject = opts.AdminEmail
262− client = conf.Client(ctx)
263299 }
300+
301+ if client == nil {
302+ logger.Fatal("error: google credentials do not have enough permissions to access admin API scope")
303+ }
304+
264305 adminService, err := admin.NewService(ctx, option.WithHTTPClient(client))
265306 if err != nil {
266307 logger.Fatal(err)