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:
@@ -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
@@ -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"
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user