from __future__ import annotations from typing import Dict, List, Optional, Tuple # Friendly Google OAuth scope map. # # ``SCOPE_MAP`` groups every Google OAuth scope under a human-friendly category # and gives each scope a short alias. Clients can request scopes by alias in # POST /google/oauth/auth-url instead of pasting long scope URIs, e.g. # ``["openid", "email", "gmail_full"]``. # # Generic full scope URIs are also accepted verbatim (the map is a convenience, # not a restriction). Validation only rejects empty/unknown scope names. SCOPE_MAP: Dict[str, Dict[str, str]] = { "authentication": { "openid": "openid", "email": "https://www.googleapis.com/auth/userinfo.email", "profile": "https://www.googleapis.com/auth/userinfo.profile", "userinfo_email": "https://www.googleapis.com/auth/userinfo.email", "userinfo_profile": "https://www.googleapis.com/auth/userinfo.profile", }, "gmail": { "gmail_full": "https://www.googleapis.com/auth/gmail.modify", "gmail_readonly": "https://www.googleapis.com/auth/gmail.readonly", "gmail_send": "https://www.googleapis.com/auth/gmail.send", "gmail_compose": "https://www.googleapis.com/auth/gmail.compose", "gmail_metadata": "https://www.googleapis.com/auth/gmail.metadata", "gmail_insert": "https://www.googleapis.com/auth/gmail.insert", "gmail_labels": "https://www.googleapis.com/auth/gmail.labels", "gmail_settings_basic": "https://www.googleapis.com/auth/gmail.settings.basic", "gmail_settings_sharing": "https://www.googleapis.com/auth/gmail.settings.sharing", "gmail_permanent_delete": "https://mail.google.com/", "gmail_addons_current_action_compose": "https://www.googleapis.com/auth/gmail.addons.current.action.compose", "gmail_addons_current_message_action": "https://www.googleapis.com/auth/gmail.addons.current.message.action", "gmail_addons_current_message_metadata": "https://www.googleapis.com/auth/gmail.addons.current.message.metadata", "gmail_addons_current_message_readonly": "https://www.googleapis.com/auth/gmail.addons.current.message.readonly", }, "google_sheets": { "sheets_readwrite": "https://www.googleapis.com/auth/spreadsheets", "sheets_readonly": "https://www.googleapis.com/auth/spreadsheets.readonly", "sheets_drive_file": "https://www.googleapis.com/auth/drive.file", "sheets_drive_readonly": "https://www.googleapis.com/auth/drive.readonly", "sheets_drive_full": "https://www.googleapis.com/auth/drive", }, "google_drive": { "drive_file": "https://www.googleapis.com/auth/drive.file", "drive_metadata_readonly": "https://www.googleapis.com/auth/drive.metadata.readonly", "drive_readonly": "https://www.googleapis.com/auth/drive.readonly", "drive_full": "https://www.googleapis.com/auth/drive", "drive_metadata": "https://www.googleapis.com/auth/drive.metadata", "drive_appdata": "https://www.googleapis.com/auth/drive.appdata", "drive_apps_readonly": "https://www.googleapis.com/auth/drive.apps.readonly", "drive_scripts": "https://www.googleapis.com/auth/drive.scripts", "drive_install": "https://www.googleapis.com/auth/drive.install", "drive_activity": "https://www.googleapis.com/auth/drive.activity", "drive_activity_readonly": "https://www.googleapis.com/auth/drive.activity.readonly", "drive_photos_readonly": "https://www.googleapis.com/auth/drive.photos.readonly", "drive_meet_readonly": "https://www.googleapis.com/auth/drive.meet.readonly", }, "google_docs": { "docs_readwrite": "https://www.googleapis.com/auth/documents", "docs_readonly": "https://www.googleapis.com/auth/documents.readonly", "docs_drive_file": "https://www.googleapis.com/auth/drive.file", "docs_drive_readonly": "https://www.googleapis.com/auth/drive.readonly", "docs_drive_full": "https://www.googleapis.com/auth/drive", }, "google_slides": { "slides_readwrite": "https://www.googleapis.com/auth/presentations", "slides_readonly": "https://www.googleapis.com/auth/presentations.readonly", "slides_drive_file": "https://www.googleapis.com/auth/drive.file", "slides_drive_readonly": "https://www.googleapis.com/auth/drive.readonly", "slides_drive_full": "https://www.googleapis.com/auth/drive", }, "google_calendar": { "calendar_full": "https://www.googleapis.com/auth/calendar", "calendar_readonly": "https://www.googleapis.com/auth/calendar.readonly", "calendar_events": "https://www.googleapis.com/auth/calendar.events", "calendar_events_readonly": "https://www.googleapis.com/auth/calendar.events.readonly", "calendar_events_owned": "https://www.googleapis.com/auth/calendar.events.owned", "calendar_events_owned_readonly": "https://www.googleapis.com/auth/calendar.events.owned.readonly", "calendar_events_public_readonly": "https://www.googleapis.com/auth/calendar.events.public.readonly", "calendar_events_freebusy": "https://www.googleapis.com/auth/calendar.events.freebusy", "calendar_freebusy": "https://www.googleapis.com/auth/calendar.freebusy", "calendar_settings_readonly": "https://www.googleapis.com/auth/calendar.settings.readonly", "calendar_calendarlist": "https://www.googleapis.com/auth/calendar.calendarlist", "calendar_calendarlist_readonly": "https://www.googleapis.com/auth/calendar.calendarlist.readonly", "calendar_calendars": "https://www.googleapis.com/auth/calendar.calendars", "calendar_calendars_readonly": "https://www.googleapis.com/auth/calendar.calendars.readonly", "calendar_acls": "https://www.googleapis.com/auth/calendar.acls", "calendar_acls_readonly": "https://www.googleapis.com/auth/calendar.acls.readonly", "calendar_app_created": "https://www.googleapis.com/auth/calendar.app.created", "calendar_addons_execute": "https://www.googleapis.com/auth/calendar.addons.execute", "calendar_addons_current_event_read": "https://www.googleapis.com/auth/calendar.addons.current.event.read", "calendar_addons_current_event_write": "https://www.googleapis.com/auth/calendar.addons.current.event.write", }, "google_contacts": { "contacts_full": "https://www.googleapis.com/auth/contacts", "contacts_readonly": "https://www.googleapis.com/auth/contacts.readonly", "contacts_other_readonly": "https://www.googleapis.com/auth/contacts.other.readonly", "contacts_directory_readonly": "https://www.googleapis.com/auth/directory.readonly", }, "google_tasks": { "tasks_readwrite": "https://www.googleapis.com/auth/tasks", "tasks_readonly": "https://www.googleapis.com/auth/tasks.readonly", }, "google_photos": { "photos_library_full": "https://www.googleapis.com/auth/photoslibrary", "photos_library_readonly": "https://www.googleapis.com/auth/photoslibrary.readonly", "photos_library_appendonly": "https://www.googleapis.com/auth/photoslibrary.appendonly", "photos_library_edit_appcreated": "https://www.googleapis.com/auth/photoslibrary.edit.appcreateddata", "photos_library_readonly_appcreated": "https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata", "photos_library_sharing": "https://www.googleapis.com/auth/photoslibrary.sharing", }, "google_chat": { "chat_messages": "https://www.googleapis.com/auth/chat.messages", "chat_messages_readonly": "https://www.googleapis.com/auth/chat.messages.readonly", "chat_spaces": "https://www.googleapis.com/auth/chat.spaces", "chat_spaces_readonly": "https://www.googleapis.com/auth/chat.spaces.readonly", "chat_import": "https://www.googleapis.com/auth/chat.import", "chat_apps_configuration": "https://www.googleapis.com/auth/chat.apps.configuration", "chat_apps_configuration_readonly": "https://www.googleapis.com/auth/chat.apps.configuration.readonly", "chat_apps_readonly": "https://www.googleapis.com/auth/chat.apps.readonly", "chat_pushes": "https://www.googleapis.com/auth/chat.pushes", "chat_meetings": "https://www.googleapis.com/auth/chat.meetings", "chat_meetings_readonly": "https://www.googleapis.com/auth/chat.meetings.readonly", }, "google_meet": { "meet_recordings_readonly": "https://www.googleapis.com/auth/drive.meet.readonly", }, "google_forms": { "forms_full": "https://www.googleapis.com/auth/forms", "forms_responses_readonly": "https://www.googleapis.com/auth/forms.responses.readonly", }, "google_classroom": { "classroom_courses": "https://www.googleapis.com/auth/classroom.courses", "classroom_courses_readonly": "https://www.googleapis.com/auth/classroom.courses.readonly", "classroom_rosters": "https://www.googleapis.com/auth/classroom.rosters", "classroom_rosters_readonly": "https://www.googleapis.com/auth/classroom.rosters.readonly", "classroom_profile_emails": "https://www.googleapis.com/auth/classroom.profile.emails", "classroom_profile_photos": "https://www.googleapis.com/auth/classroom.profile.photos", "classroom_announcements": "https://www.googleapis.com/auth/classroom.announcements", "classroom_announcements_readonly": "https://www.googleapis.com/auth/classroom.announcements.readonly", "classroom_topics": "https://www.googleapis.com/auth/classroom.topics", "classroom_topics_readonly": "https://www.googleapis.com/auth/classroom.topics.readonly", "classroom_coursework_students": "https://www.googleapis.com/auth/classroom.coursework.students", "classroom_coursework_students_readonly": "https://www.googleapis.com/auth/classroom.coursework.students.readonly", "classroom_coursework_me": "https://www.googleapis.com/auth/classroom.coursework.me", "classroom_coursework_me_readonly": "https://www.googleapis.com/auth/classroom.coursework.me.readonly", "classroom_courseworkmaterials": "https://www.googleapis.com/auth/classroom.courseworkmaterials", "classroom_courseworkmaterials_readonly": "https://www.googleapis.com/auth/classroom.courseworkmaterials.readonly", "classroom_guardianlinks_students": "https://www.googleapis.com/auth/classroom.guardianlinks.students", "classroom_guardianlinks_students_readonly": "https://www.googleapis.com/auth/classroom.guardianlinks.students.readonly", "classroom_student_submissions_me_readonly": "https://www.googleapis.com/auth/classroom.student-submissions.me.readonly", "classroom_student_submissions_students_readonly": "https://www.googleapis.com/auth/classroom.student-submissions.students.readonly", }, "google_analytics": { "analytics_full": "https://www.googleapis.com/auth/analytics", "analytics_readonly": "https://www.googleapis.com/auth/analytics.readonly", "analytics_edit": "https://www.googleapis.com/auth/analytics.edit", "analytics_manage_users": "https://www.googleapis.com/auth/analytics.manage.users", "analytics_manage_users_readonly": "https://www.googleapis.com/auth/analytics.manage.users.readonly", "analytics_manage_edit": "https://www.googleapis.com/auth/analytics.manage.edit", "analytics_manage_partners": "https://www.googleapis.com/auth/analytics.manage.partners", "analytics_provision": "https://www.googleapis.com/auth/analytics.provision", }, "google_ads": { "google_ads_full": "https://www.googleapis.com/auth/adwords", "admanager_readonly": "https://www.googleapis.com/auth/admanager.readonly", }, "google_cloud": { "bigquery_readonly": "https://www.googleapis.com/auth/bigquery.readonly", "bigquery_full": "https://www.googleapis.com/auth/bigquery", "bigquery_insertdata": "https://www.googleapis.com/auth/bigquery.insertdata", "cloud_platform": "https://www.googleapis.com/auth/cloud-platform", "cloud_platform_readonly": "https://www.googleapis.com/auth/cloud-platform.read-only", "devstorage_read_only": "https://www.googleapis.com/auth/devstorage.read_only", "devstorage_read_write": "https://www.googleapis.com/auth/devstorage.read_write", "devstorage_full_control": "https://www.googleapis.com/auth/devstorage.full_control", "compute_readonly": "https://www.googleapis.com/auth/compute.readonly", "compute_full": "https://www.googleapis.com/auth/compute", "datastore": "https://www.googleapis.com/auth/datastore", "sqlservice_admin": "https://www.googleapis.com/auth/sqlservice.admin", "vision": "https://www.googleapis.com/auth/cloud-vision", "translate": "https://www.googleapis.com/auth/cloud-translation", "language": "https://www.googleapis.com/auth/cloud-language", "genai": "https://www.googleapis.com/auth/genai", }, "google_maps": {}, "youtube": { "youtube_readonly": "https://www.googleapis.com/auth/youtube.readonly", "youtube_full": "https://www.googleapis.com/auth/youtube", "youtube_upload": "https://www.googleapis.com/auth/youtube.upload", "youtube_force_ssl": "https://www.googleapis.com/auth/youtube.force-ssl", "youtube_channel_memberships_creator": "https://www.googleapis.com/auth/youtube.channel-memberships.creator", "youtube_partner": "https://www.googleapis.com/auth/youtubepartner", "youtube_partner_channel_audit": "https://www.googleapis.com/auth/youtubepartner-channel-audit", }, "google_play": { "play_androidpublisher": "https://www.googleapis.com/auth/androidpublisher", "play_games": "https://www.googleapis.com/auth/playgames", "play_games_readonly": "https://www.googleapis.com/auth/playgames.readonly", }, "firebase": { "firebase_full": "https://www.googleapis.com/auth/firebase", "firebase_readonly": "https://www.googleapis.com/auth/firebase.readonly", "firebase_database": "https://www.googleapis.com/auth/firebase.database", "firebase_database_readonly": "https://www.googleapis.com/auth/firebase.database.readonly", "firebase_messaging": "https://www.googleapis.com/auth/firebase.messaging", "firebase_rules": "https://www.googleapis.com/auth/firebase.rules", "firebase_rules_readonly": "https://www.googleapis.com/auth/firebase.rules.readonly", "firebase_analytics": "https://www.googleapis.com/auth/firebase.analytics", }, "admin_sdk": { "admin_directory_user_readonly": "https://www.googleapis.com/auth/admin.directory.user.readonly", "admin_directory_user": "https://www.googleapis.com/auth/admin.directory.user", "admin_directory_group_readonly": "https://www.googleapis.com/auth/admin.directory.group.readonly", "admin_directory_group": "https://www.googleapis.com/auth/admin.directory.group", "admin_directory_group_member_readonly": "https://www.googleapis.com/auth/admin.directory.group.member.readonly", "admin_directory_group_member": "https://www.googleapis.com/auth/admin.directory.group.member", "admin_directory_device_chromeos_readonly": "https://www.googleapis.com/auth/admin.directory.device.chromeos.readonly", "admin_directory_device_chromeos": "https://www.googleapis.com/auth/admin.directory.device.chromeos", "admin_directory_orgunit_readonly": "https://www.googleapis.com/auth/admin.directory.orgunit.readonly", "admin_directory_orgunit": "https://www.googleapis.com/auth/admin.directory.orgunit", "admin_directory_rolemanagement_readonly": "https://www.googleapis.com/auth/admin.directory.rolemanagement.readonly", "admin_directory_rolemanagement": "https://www.googleapis.com/auth/admin.directory.rolemanagement", "admin_directory_domain_readonly": "https://www.googleapis.com/auth/admin.directory.domain.readonly", "admin_directory_customer_readonly": "https://www.googleapis.com/auth/admin.directory.customer.readonly", "admin_directory_userschema_readonly": "https://www.googleapis.com/auth/admin.directory.userschema.readonly", "admin_reports_audit_readonly": "https://www.googleapis.com/auth/admin.reports.audit.readonly", "admin_reports_usage_readonly": "https://www.googleapis.com/auth/admin.reports.usage.readonly", "admin_datatransfer": "https://www.googleapis.com/auth/admin.datatransfer", }, "people_api": { "people_contacts_full": "https://www.googleapis.com/auth/contacts", "people_contacts_readonly": "https://www.googleapis.com/auth/contacts.readonly", "people_othercontacts": "https://www.googleapis.com/auth/people.otherContacts", "people_othercontacts_readonly": "https://www.googleapis.com/auth/people.otherContacts.readonly", "people_email_readonly": "https://www.googleapis.com/auth/people.emailAddresses.readonly", "people_phone_readonly": "https://www.googleapis.com/auth/people.phoneNumbers.readonly", "people_contactinfo": "https://www.googleapis.com/auth/people.contactInfo", "people_contactinfo_readonly": "https://www.googleapis.com/auth/people.contactInfo.readonly", "people_directory_readonly": "https://www.googleapis.com/auth/people.directory.readonly", "user_emails_readonly": "https://www.googleapis.com/auth/user.emails.readonly", "user_phonenumbers_readonly": "https://www.googleapis.com/auth/user.phonenumbers.readonly", }, "business_profile": { "business_manage": "https://www.googleapis.com/auth/business.manage", }, "other": { "openid": "openid", "email": "https://www.googleapis.com/auth/userinfo.email", "profile": "https://www.googleapis.com/auth/userinfo.profile", }, } CATEGORY_ORDER: List[str] = list(SCOPE_MAP.keys()) ALIAS_TO_URI: Dict[str, str] = { alias: uri for category in SCOPE_MAP.values() for alias, uri in category.items() } ALL_ALIASES: List[str] = sorted(ALIAS_TO_URI) # Scope containment for UX normalization. When a broader scope is already in # the request, narrower scopes of the same family are redundant and are dropped # so the Google consent screen stays minimal. Only documented supersets are # listed; generic/unknown scopes are never touched. _SCOPE_INCLUDED_BY: Dict[str, Tuple[str, ...]] = { # --- Gmail family --- "https://www.googleapis.com/auth/gmail.metadata": ( "https://www.googleapis.com/auth/gmail.readonly", "https://www.googleapis.com/auth/gmail.modify", "https://mail.google.com/", ), "https://www.googleapis.com/auth/gmail.readonly": ( "https://www.googleapis.com/auth/gmail.modify", "https://mail.google.com/", ), "https://www.googleapis.com/auth/gmail.labels": ( "https://www.googleapis.com/auth/gmail.modify", "https://mail.google.com/", ), "https://www.googleapis.com/auth/gmail.send": ( "https://www.googleapis.com/auth/gmail.compose", "https://www.googleapis.com/auth/gmail.modify", "https://mail.google.com/", ), "https://www.googleapis.com/auth/gmail.compose": ( "https://www.googleapis.com/auth/gmail.modify", "https://mail.google.com/", ), "https://www.googleapis.com/auth/gmail.modify": ( "https://mail.google.com/", ), # --- Sheets / Drive family --- "https://www.googleapis.com/auth/spreadsheets.readonly": ( "https://www.googleapis.com/auth/spreadsheets", ), "https://www.googleapis.com/auth/drive.metadata.readonly": ( "https://www.googleapis.com/auth/drive.readonly", "https://www.googleapis.com/auth/drive.metadata", "https://www.googleapis.com/auth/drive", ), "https://www.googleapis.com/auth/drive.metadata": ( "https://www.googleapis.com/auth/drive", ), "https://www.googleapis.com/auth/drive.readonly": ( "https://www.googleapis.com/auth/drive", ), "https://www.googleapis.com/auth/drive.file": ( "https://www.googleapis.com/auth/drive", ), "https://www.googleapis.com/auth/drive.appdata": ( "https://www.googleapis.com/auth/drive", ), "https://www.googleapis.com/auth/drive.scripts": ( "https://www.googleapis.com/auth/drive", ), "https://www.googleapis.com/auth/drive.activity.readonly": ( "https://www.googleapis.com/auth/drive.activity", "https://www.googleapis.com/auth/drive", ), "https://www.googleapis.com/auth/drive.activity": ( "https://www.googleapis.com/auth/drive", ), "https://www.googleapis.com/auth/drive.photos.readonly": ( "https://www.googleapis.com/auth/drive", ), "https://www.googleapis.com/auth/drive.meet.readonly": ( "https://www.googleapis.com/auth/drive", ), # --- Calendar family --- "https://www.googleapis.com/auth/calendar.events.readonly": ( "https://www.googleapis.com/auth/calendar.events", "https://www.googleapis.com/auth/calendar.readonly", "https://www.googleapis.com/auth/calendar", ), "https://www.googleapis.com/auth/calendar.events": ( "https://www.googleapis.com/auth/calendar", ), "https://www.googleapis.com/auth/calendar.events.owned.readonly": ( "https://www.googleapis.com/auth/calendar.events.owned", "https://www.googleapis.com/auth/calendar.events.readonly", "https://www.googleapis.com/auth/calendar.readonly", "https://www.googleapis.com/auth/calendar", ), "https://www.googleapis.com/auth/calendar.events.owned": ( "https://www.googleapis.com/auth/calendar.events", "https://www.googleapis.com/auth/calendar", ), "https://www.googleapis.com/auth/calendar.events.public.readonly": ( "https://www.googleapis.com/auth/calendar.readonly", "https://www.googleapis.com/auth/calendar", ), "https://www.googleapis.com/auth/calendar.events.freebusy": ( "https://www.googleapis.com/auth/calendar.freebusy", "https://www.googleapis.com/auth/calendar", ), "https://www.googleapis.com/auth/calendar.readonly": ( "https://www.googleapis.com/auth/calendar", ), "https://www.googleapis.com/auth/calendar.freebusy": ( "https://www.googleapis.com/auth/calendar", ), "https://www.googleapis.com/auth/calendar.settings.readonly": ( "https://www.googleapis.com/auth/calendar", ), "https://www.googleapis.com/auth/calendar.calendarlist.readonly": ( "https://www.googleapis.com/auth/calendar.calendarlist", "https://www.googleapis.com/auth/calendar", ), "https://www.googleapis.com/auth/calendar.calendarlist": ( "https://www.googleapis.com/auth/calendar", ), "https://www.googleapis.com/auth/calendar.calendars.readonly": ( "https://www.googleapis.com/auth/calendar.calendars", "https://www.googleapis.com/auth/calendar", ), "https://www.googleapis.com/auth/calendar.calendars": ( "https://www.googleapis.com/auth/calendar", ), "https://www.googleapis.com/auth/calendar.acls.readonly": ( "https://www.googleapis.com/auth/calendar.acls", "https://www.googleapis.com/auth/calendar", ), "https://www.googleapis.com/auth/calendar.acls": ( "https://www.googleapis.com/auth/calendar", ), # --- Contacts / People family --- "https://www.googleapis.com/auth/contacts.readonly": ( "https://www.googleapis.com/auth/contacts", ), "https://www.googleapis.com/auth/contacts.other.readonly": ( "https://www.googleapis.com/auth/contacts", ), # --- Tasks family --- "https://www.googleapis.com/auth/tasks.readonly": ( "https://www.googleapis.com/auth/tasks", ), # --- YouTube family --- "https://www.googleapis.com/auth/youtube.readonly": ( "https://www.googleapis.com/auth/youtube.force-ssl", "https://www.googleapis.com/auth/youtube", ), "https://www.googleapis.com/auth/youtube.upload": ( "https://www.googleapis.com/auth/youtube", ), "https://www.googleapis.com/auth/youtube.force-ssl": ( "https://www.googleapis.com/auth/youtube", ), "https://www.googleapis.com/auth/youtube.channel-memberships.creator": ( "https://www.googleapis.com/auth/youtube", ), "https://www.googleapis.com/auth/youtubepartner-channel-audit": ( "https://www.googleapis.com/auth/youtubepartner", ), # --- Forms family --- "https://www.googleapis.com/auth/forms.responses.readonly": ( "https://www.googleapis.com/auth/forms", ), # --- Analytics family --- "https://www.googleapis.com/auth/analytics.readonly": ( "https://www.googleapis.com/auth/analytics.edit", "https://www.googleapis.com/auth/analytics", ), "https://www.googleapis.com/auth/analytics.edit": ( "https://www.googleapis.com/auth/analytics", ), "https://www.googleapis.com/auth/analytics.manage.users.readonly": ( "https://www.googleapis.com/auth/analytics.manage.users", ), # --- BigQuery / Cloud family --- "https://www.googleapis.com/auth/bigquery.readonly": ( "https://www.googleapis.com/auth/bigquery", ), "https://www.googleapis.com/auth/bigquery.insertdata": ( "https://www.googleapis.com/auth/bigquery", ), "https://www.googleapis.com/auth/cloud-platform.read-only": ( "https://www.googleapis.com/auth/cloud-platform", ), "https://www.googleapis.com/auth/devstorage.read_only": ( "https://www.googleapis.com/auth/devstorage.read_write", "https://www.googleapis.com/auth/devstorage.full_control", ), "https://www.googleapis.com/auth/devstorage.read_write": ( "https://www.googleapis.com/auth/devstorage.full_control", ), # --- Admin SDK family --- "https://www.googleapis.com/auth/admin.directory.user.readonly": ( "https://www.googleapis.com/auth/admin.directory.user", ), "https://www.googleapis.com/auth/admin.directory.group.readonly": ( "https://www.googleapis.com/auth/admin.directory.group", ), "https://www.googleapis.com/auth/admin.directory.group.member.readonly": ( "https://www.googleapis.com/auth/admin.directory.group.member", ), "https://www.googleapis.com/auth/admin.directory.device.chromeos.readonly": ( "https://www.googleapis.com/auth/admin.directory.device.chromeos", ), "https://www.googleapis.com/auth/admin.directory.orgunit.readonly": ( "https://www.googleapis.com/auth/admin.directory.orgunit", ), "https://www.googleapis.com/auth/admin.directory.rolemanagement.readonly": ( "https://www.googleapis.com/auth/admin.directory.rolemanagement", ), } def is_generic_scope(value: str) -> bool: """True when the value is a full scope URI the client typed directly.""" return value.startswith(("http://", "https://", "mail.google.com/")) or value == "openid" def _normalize_scopes(resolved: List[str]) -> Tuple[List[str], List[str]]: """Drop scopes already covered by a broader scope in the same request. Returns ``(kept_scopes, dropped_scopes)``. Order of the remaining scopes is preserved. Unknown/generic scopes (not in the containment table) are never dropped. """ present = set(resolved) kept: List[str] = [] dropped: List[str] = [] for uri in resolved: supersets = _SCOPE_INCLUDED_BY.get(uri, ()) if any(superset in present for superset in supersets): dropped.append(uri) continue kept.append(uri) return kept, dropped def resolve_scope_list( scopes: Optional[List[str]], *, normalize: bool = True ) -> Tuple[List[str], List[str], List[str]]: """Resolve a list of scope aliases / URIs into a de-duplicated list of URIs. Returns ``(resolved_uris, errors, dropped_scopes)``. Generic full scope URIs are accepted verbatim; only empty or unknown scope names produce errors. When ``normalize`` is true, narrower scopes that are fully covered by a broader scope already present are dropped (best UX: minimal consent screen). """ resolved: List[str] = [] errors: List[str] = [] dropped: List[str] = [] seen = set() for raw in scopes or []: value = str(raw or "").strip() if not value: errors.append("Scope name is missing (empty entry).") continue if value in ALIAS_TO_URI: uri = ALIAS_TO_URI[value] elif is_generic_scope(value): uri = value else: errors.append( f"Unknown scope '{value}'. It is not a known alias and not a full scope URI. " f"Valid aliases: {', '.join(ALL_ALIASES)}" ) continue if uri not in seen: seen.add(uri) resolved.append(uri) if normalize: resolved, dropped = _normalize_scopes(resolved) return resolved, errors, dropped