fix: exclude Workspace-admin-only scopes from Recommended preset (#119) (#127)

Admin-only scopes (apps.*, cloud-identity.*, ediscovery, directory.readonly,
groups) require Workspace domain-admin access and cannot be granted to personal
@gmail.com accounts — Google returns 400 invalid_scope when they're included.

Changes:
- Add is_workspace_admin_scope() helper (mirrors is_app_only_scope())
  to identify scopes that fail for personal Google accounts
- Exclude these scopes from the template_selects of the 'Recommended' preset
  in run_discovery_scope_picker()
- Exclude them from the resolved scope list when the Recommended template
  is confirmed
- Add 8 unit tests covering the new helper

Workspace admins can still access these scopes via 'Full Access' template
or by selecting them individually in the picker.

Note: this is complementary to PR #108 which filters alertcenter scopes
at the API-discovery level. This PR handles the broader set at the
recommendation layer.

Addresses #119 (Bug 1: admin scopes in Recommended preset)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
zerone0x
2026-03-05 16:44:46 +08:00
committed by GitHub
parent 29a029aef4
commit fc6bc9593b
2 changed files with 127 additions and 2 deletions
+17
View File
@@ -0,0 +1,17 @@
---
"@googleworkspace/cli": patch
---
Exclude Workspace-admin-only scopes from the "Recommended" scope preset.
Scopes that require Google Workspace domain-admin access (`apps.*`,
`cloud-identity.*`, `ediscovery`, `directory.readonly`, `groups`) now return
`400 invalid_scope` when used by personal `@gmail.com` accounts. These scopes
are no longer included in the "Recommended" template, preventing login failures
for non-Workspace users.
Workspace admins can still select these scopes manually via the "Full Access"
template or by picking them individually in the scope picker.
Adds a new `is_workspace_admin_scope()` helper (mirroring the existing
`is_app_only_scope()`) that centralises this detection logic.
+110 -2
View File
@@ -569,7 +569,10 @@ fn run_discovery_scope_picker(
entry.classification != ScopeClassification::Restricted
};
if is_recommended && !entry.short.starts_with("admin.") {
if is_recommended
&& !entry.short.starts_with("admin.")
&& !is_workspace_admin_scope(&entry.url)
{
recommended_scopes.push(entry.short.to_string());
}
if entry.is_readonly {
@@ -684,7 +687,8 @@ fn run_discovery_scope_picker(
selected.push(entry.url.to_string());
}
} else if recommended && !full && !readonly {
// Recommended: non-restricted + readonly, but exclude admin.* scopes
// Recommended: non-restricted + readonly, but exclude admin.* and
// Workspace-admin-only scopes (require domain admin; fail for @gmail.com).
for entry in relevant_scopes {
if is_app_only_scope(&entry.url) {
continue;
@@ -692,6 +696,9 @@ fn run_discovery_scope_picker(
if entry.short.starts_with("admin.") {
continue;
}
if is_workspace_admin_scope(&entry.url) {
continue;
}
if entry.is_readonly || entry.classification != ScopeClassification::Restricted
{
selected.push(entry.url.to_string());
@@ -1350,6 +1357,30 @@ fn is_app_only_scope(url: &str) -> bool {
|| url.contains("/auth/apps.alerts")
}
/// Helper: check if a scope requires Workspace domain admin access and therefore
/// cannot be granted to personal `@gmail.com` accounts via standard user OAuth.
///
/// These scopes are valid in Workspace environments with a domain admin, but
/// Google returns `400 invalid_scope` when requested by personal accounts.
/// They are excluded from the "Recommended" preset to avoid login failures.
///
/// Affected scope families:
/// - `apps.*` — Alert Center, Groups Settings, Licensing, Reseller
/// - `cloud-identity.*` — Cloud Identity: devices, groups, inbound SSO, policies
/// - `ediscovery` — Google Vault
/// - `directory.readonly`— Admin SDK Directory (read-only)
/// - `groups` — Groups Management
fn is_workspace_admin_scope(url: &str) -> bool {
let short = url
.strip_prefix("https://www.googleapis.com/auth/")
.unwrap_or(url);
short.starts_with("apps.")
|| short.starts_with("cloud-identity.")
|| short == "ediscovery"
|| short == "directory.readonly"
|| short == "groups"
}
#[cfg(test)]
mod tests {
use super::*;
@@ -1657,4 +1688,81 @@ mod tests {
let data = r#"{"key":{"access_token":"ya29","refresh_token":"1//tok"}}"#;
assert_eq!(extract_refresh_token(data), Some("1//tok".to_string()));
}
// ── is_workspace_admin_scope tests ──────────────────────────────────
#[test]
fn is_workspace_admin_scope_apps_alerts() {
assert!(is_workspace_admin_scope(
"https://www.googleapis.com/auth/apps.alerts"
));
}
#[test]
fn is_workspace_admin_scope_apps_groups_settings() {
assert!(is_workspace_admin_scope(
"https://www.googleapis.com/auth/apps.groups.settings"
));
}
#[test]
fn is_workspace_admin_scope_apps_licensing() {
assert!(is_workspace_admin_scope(
"https://www.googleapis.com/auth/apps.licensing"
));
}
#[test]
fn is_workspace_admin_scope_cloud_identity() {
assert!(is_workspace_admin_scope(
"https://www.googleapis.com/auth/cloud-identity.groups"
));
assert!(is_workspace_admin_scope(
"https://www.googleapis.com/auth/cloud-identity.devices"
));
assert!(is_workspace_admin_scope(
"https://www.googleapis.com/auth/cloud-identity.policies"
));
}
#[test]
fn is_workspace_admin_scope_ediscovery() {
assert!(is_workspace_admin_scope(
"https://www.googleapis.com/auth/ediscovery"
));
}
#[test]
fn is_workspace_admin_scope_directory_readonly() {
assert!(is_workspace_admin_scope(
"https://www.googleapis.com/auth/directory.readonly"
));
}
#[test]
fn is_workspace_admin_scope_groups() {
assert!(is_workspace_admin_scope(
"https://www.googleapis.com/auth/groups"
));
}
#[test]
fn is_workspace_admin_scope_normal_scopes_not_admin() {
// Consumer/personal-account scopes must NOT be classified as admin-only
assert!(!is_workspace_admin_scope(
"https://www.googleapis.com/auth/drive"
));
assert!(!is_workspace_admin_scope(
"https://www.googleapis.com/auth/gmail.modify"
));
assert!(!is_workspace_admin_scope(
"https://www.googleapis.com/auth/calendar"
));
assert!(!is_workspace_admin_scope(
"https://www.googleapis.com/auth/spreadsheets"
));
assert!(!is_workspace_admin_scope(
"https://www.googleapis.com/auth/chat.messages"
));
}
}