diff --git a/README.md b/README.md index 5fcfb256a924060bb94a362af4629e1fd4a61a45..8f017c500317da48692baacb3e8723368ff65c0d 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,21 @@ This tool requires the following OAuth2 scopes to actually perform changes: See the section on preparing a service account for information on how to grant a service account those scopes on your domain. -## Preparing a service account +## Preparing a service account (Admin Roles) + +Google have [updated the API](https://gsuiteupdates.googleblog.com/2020/08/use-service-accounts-google-groups-without-domain-wide-delegation.html) +to allow service accounts direct access to the API without needing domain-wide delegation. + +1. Create a service account in the Google Console for this script. +2. Generate and download JSON credentials for the service account. +3. In the GSuite admin panel, go to "Account" > "Admin Roles" and create a new +custom role with the following Admin **API** privileges (not Console privileges): + * Users - Create, Read, Update and Update Custom Attributes + * Groups - All privileges +4. Add the service account to the role using the "Assign service accounts" option +when viewing the custom role's admins + +## Preparing a service account (Domain-wide Delegation) This tool assumes it will be acting as a service account user. It will use this service account user to then act on behalf of an admin user in GSuite. To @@ -88,8 +102,8 @@ prepare such a service account user: Delegation". Click "Save" to apply the changes. 4. Hover over the "?" symbol next to the generated client id and click "view client". Copy the Client ID from the popup panel. -5. In the GSuite admin panel, go to "Security Settings" > "Advanced Settings" > - "Manage API client access". +5. In the GSuite admin panel, go to "Security Settings" > "API Controls" > + "Manage Domain-Wide Delegation", and click "Add new". 6. Paste in the service account Client ID as "Client Name" and add a comma-separated list of scopes. See the section on required API scopes. diff --git a/configuration-example.yaml b/configuration-example.yaml index 04819734383cf1118e27fae9d5f638b634980046..fafa8a58db61be09d6a30a37a2065f372f5c0150 100644 --- a/configuration-example.yaml +++ b/configuration-example.yaml @@ -190,10 +190,14 @@ google_domain: # Name of the domain. name: 'example.com' - # Username within the GSuite for the user which has administration rights. + # If using a service account with Domain-Wide Delegation, set to the username + # within the GSuite for the user which has administration rights. # Should be an e-mail style name. E.g. "super-admin@example.com". The service # account credentials specified in the google_api.auth section are used to # perform admin actions as this user. + # If not using Domain-Wide Delegation (i.e. the service account executing this + # script has been made a member of an Admin Role), use null or comment out. + # Default: null admin_user: 'super-admin@example.com' # Secondary domain or domain alias for groups. If null, the value of "name" diff --git a/gsuitesync/gapidomain.py b/gsuitesync/gapidomain.py index 21d0923896d7e3456f76b8d44b1359828ce0ca22..a4dc18c3d12bf712f4ed9c7cc252104eb0aa51eb 100644 --- a/gsuitesync/gapidomain.py +++ b/gsuitesync/gapidomain.py @@ -17,10 +17,14 @@ class Configuration(ConfigurationDataclassMixin): # Name of the domain. (E.g. "example.com".) name: str - # Username within the GSuite for the user which has administration rights. Should be an e-mail - # style name. E.g. "super-admin@example.com". The service account credentials specified in the - # google_api.auth section are used to perform admin actions as this user. - admin_user: str + # If using a service account with Domain-Wide Delegation, set to the username + # within the GSuite for the user which has administration rights. + # Should be an e-mail style name. E.g. "super-admin@example.com". The service + # account credentials specified in the google_api.auth section are used to + # perform admin actions as this user. + # If not using Domain-Wide Delegation (i.e. using an Admin Role with the + # service account as a member), use null. + admin_user: str = None # Secondary domain or domain alias for groups. If None, the value of "name" is used. # Default: None diff --git a/gsuitesync/sync.py b/gsuitesync/sync.py index a7c5f61474f4d86ff429b507560a2ef2b53a953e..a8a6c4c3f4cd4f36440f3566db3fa72f391636ee 100644 --- a/gsuitesync/sync.py +++ b/gsuitesync/sync.py @@ -107,8 +107,10 @@ def sync(configuration, *, read_only=True): creds = ( gapi_auth_config.load_credentials(read_only=read_only) .with_scopes(READ_ONLY_SCOPES + ([] if read_only else WRITE_SCOPES)) - .with_subject(gapi_domain_config.admin_user) ) + # Use admin_user if using service account with Domain-Wide Delegation + if gapi_domain_config.admin_user: + creds = creds.with_subject(gapi_domain_config.admin_user) # Secondary domain for Google groups that come from Lookup groups groups_domain = ( @@ -173,6 +175,10 @@ def sync(configuration, *, read_only=True): eligible_uids = ldap_config.get_eligible_uids() LOG.info('Total LDAP user entries: %s', len(eligible_uids)) + # Sanity check: there are some eligible users (else LDAP lookup failure?) + if len(eligible_uids) == 0: + raise RuntimeError('Sanity check failed: no users in eligible set') + # Get a set containing all groupIDs. These are all the groups that are eligible to be in our # GSuite instance. If a group is in GSuite and is *not* present in this list then it is # deleted. diff --git a/setup.py b/setup.py index 5f6dbbc3df86964603fff3abe4c75483da3db027..88662b8632aa761f9a84e6146be8d8bfbbf7d655 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ def load_requirements(): setup( name='gsuitesync', - version='0.9.1', + version='0.9.2', packages=find_packages(), install_requires=load_requirements(), entry_points={