All Downloads are FREE. Search and download functionalities are using the official Maven repository.

edu.internet2.middleware.changelogconsumer.googleapps.GoogleAppsSdkUtils Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * Copyright 2015 Internet2
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package edu.internet2.middleware.changelogconsumer.googleapps;

import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.json.GoogleJsonError;
import com.google.api.client.googleapis.json.GoogleJsonResponseException;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.services.directory.Directory;
import com.google.api.services.directory.DirectoryRequest;
import com.google.api.services.directory.model.*;
import com.google.api.services.groupssettings.Groupssettings;
import com.google.api.services.groupssettings.GroupssettingsRequest;
import com.google.api.services.groupssettings.GroupssettingsScopes;

import edu.internet2.middleware.changelogconsumer.googleapps.utils.GoogleAppsSyncProperties;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

/**
 * GoogleAppsSdkUtils is a helper class that interfaces with the Google SDK Admin API and handles exponential back-off.
 * see https://developers.google.com/admin-sdk/directory/v1/guides/delegation
 *
 * @author John Gasper, Unicon
 */
public class GoogleAppsSdkUtils {

    private static final Logger LOG = LoggerFactory.getLogger(GoogleAppsChangeLogConsumer.class);

    private static final String[] groupssettingsScope = {GroupssettingsScopes.APPS_GROUPS_SETTINGS};

    private static final Random randomGenerator = new Random();

    /**
     * getGoogleDirectoryCredential creates a credential object that authenticates the REST API calls.
     * @param serviceAccountEmail the application's account email address provided by Google
     * @param serviceAccountPKCS12FilePath path of a private key (.p12) file provided by Google
     * @param serviceAccountUser a impersonation user account
     * @param httpTransport a httpTransport object
     * @param jsonFactory a jsonFactory object
     * @return a Google Credential
     * @throws GeneralSecurityException
     * @throws IOException
     */
    public static GoogleCredential getGoogleDirectoryCredential(String serviceAccountEmail, String serviceAccountPKCS12FilePath,
                                                                String serviceAccountUser, HttpTransport httpTransport, JsonFactory jsonFactory)
            throws GeneralSecurityException, IOException {

      return getGoogleDirectoryCredential(serviceAccountEmail, serviceAccountPKCS12FilePath, serviceAccountUser, httpTransport, jsonFactory, GoogleAppsSyncProperties.defaultDirectoryScopes);
    }
    
    /**
     * getGoogleDirectoryCredential creates a credential object that authenticates the REST API calls.
     * @param serviceAccountEmail the application's account email address provided by Google
     * @param serviceAccountPKCS12FilePath path of a private key (.p12) file provided by Google
     * @param serviceAccountUser a impersonation user account
     * @param httpTransport a httpTransport object
     * @param jsonFactory a jsonFactory object
     * @return a Google Credential
     * @throws GeneralSecurityException
     * @throws IOException
     */
    public static GoogleCredential getGoogleDirectoryCredential(String serviceAccountEmail, String serviceAccountPKCS12FilePath,
                                                                String serviceAccountUser, HttpTransport httpTransport, JsonFactory jsonFactory,
                                                                String[] directoryScopes)
            throws GeneralSecurityException, IOException {

        return new GoogleCredential.Builder()
                .setTransport(httpTransport)
                .setJsonFactory(jsonFactory)
                .setServiceAccountId(serviceAccountEmail)
                .setServiceAccountScopes(Arrays.asList(directoryScopes))
                .setServiceAccountUser(serviceAccountUser)
                .setServiceAccountPrivateKeyFromP12File(new File(serviceAccountPKCS12FilePath))
                .build();
    }
    
    /**
     * getGoogleDirectoryCredential creates a credential object that authenticates the REST API calls.
     * @param serviceAccountEmail the application's account email address provided by Google
     * @param serviceAccountPrivateKey private key provided by Google
     * @param serviceAccountUser a impersonation user account
     * @param httpTransport a httpTransport object
     * @param jsonFactory a jsonFactory object
     * @return a Google Credential
     * @throws GeneralSecurityException
     * @throws IOException
     */
    public static GoogleCredential getGoogleDirectoryCredential(String serviceAccountEmail, PrivateKey serviceAccountPrivateKey,
                                                                String serviceAccountUser, HttpTransport httpTransport, JsonFactory jsonFactory,
                                                                String[] directoryScopes)
            throws GeneralSecurityException, IOException {

        return new GoogleCredential.Builder()
                .setTransport(httpTransport)
                .setJsonFactory(jsonFactory)
                .setServiceAccountId(serviceAccountEmail)
                .setServiceAccountScopes(Arrays.asList(directoryScopes))
                .setServiceAccountUser(serviceAccountUser)
                .setServiceAccountPrivateKey(serviceAccountPrivateKey)
                .build();
    }


    /**
     * getGoogleDirectoryCredential creates a credential object that authenticates the REST API calls.
     * @param serviceAccountEmail the application's account email address provided by Google
     * @param serviceAccountPKCS12FilePath path of a private key (.p12) file provided by Google
     * @param serviceAccountUser a impersonation user account
     * @param httpTransport a httpTransport object
     * @param jsonFactory a jsonFactory object
     * @return a Google Credential
     * @throws GeneralSecurityException
     * @throws IOException
     */
    public static GoogleCredential getGoogleGroupssettingsCredential(String serviceAccountEmail, String serviceAccountPKCS12FilePath,
                                                                String serviceAccountUser, HttpTransport httpTransport, JsonFactory jsonFactory)
            throws GeneralSecurityException, IOException {

        return new GoogleCredential.Builder()
                .setTransport(httpTransport)
                .setJsonFactory(jsonFactory)
                .setServiceAccountId(serviceAccountEmail)
                .setServiceAccountScopes(Arrays.asList(groupssettingsScope))
                .setServiceAccountUser(serviceAccountUser)
                .setServiceAccountPrivateKeyFromP12File(new File(serviceAccountPKCS12FilePath))
                .build();
    }
    
    /**
     * getGoogleDirectoryCredential creates a credential object that authenticates the REST API calls.
     * @param serviceAccountEmail the application's account email address provided by Google
     * @param serviceAccountPrivateKey private key provided by Google
     * @param serviceAccountUser a impersonation user account
     * @param httpTransport a httpTransport object
     * @param jsonFactory a jsonFactory object
     * @return a Google Credential
     * @throws GeneralSecurityException
     * @throws IOException
     */
    public static GoogleCredential getGoogleGroupssettingsCredential(String serviceAccountEmail, PrivateKey serviceAccountPrivateKey,
                                                                String serviceAccountUser, HttpTransport httpTransport, JsonFactory jsonFactory)
            throws GeneralSecurityException, IOException {

        return new GoogleCredential.Builder()
                .setTransport(httpTransport)
                .setJsonFactory(jsonFactory)
                .setServiceAccountId(serviceAccountEmail)
                .setServiceAccountScopes(Arrays.asList(groupssettingsScope))
                .setServiceAccountUser(serviceAccountUser)
                .setServiceAccountPrivateKey(serviceAccountPrivateKey)
                .build();
    }

    /**
     * addUser creates a user to Google.
     * @param directoryClient a Directory (service) object
     * @param user a populated User object
     * @return the new User object created/returned by Google
     * @throws IOException
     */
    public static User addUser(Directory directoryClient, User user) throws IOException {
        LOG.debug("addUser() - {}", user);

        Directory.Users.Insert request = null;

        try {
            request = directoryClient.users().insert(user);
        } catch (IOException e) {
            LOG.error("An unknown error occurred: " + e);
        }

        return (User) execute(request);
    }

    /**
     * removeGroup removes a group from Google.
     * @param directoryClient a Directory (service) object
     * @param userKey an identifier for a user (e-mail address is the most popular)
     * @throws IOException
     */
    public static void removeUser(Directory directoryClient, String userKey) throws IOException {
        LOG.debug("removeUser() - {}", userKey);

        Directory.Users.Delete request = null;

        try {
            request = directoryClient.users().delete(userKey);
        } catch (IOException e) {
            LOG.error("An unknown error occurred: " + e);
        }

        execute(request);
    }


    /**
     * addGroup adds a group to Google.
     * @param directoryClient a Directory client
     * @param group a populated Group object
     * @return the new Group object created/returned by Google
     * @throws IOException
     */
    public static Group addGroup(Directory directoryClient, Group group) throws IOException {
        LOG.debug("addGroup() - {}", group);

        Directory.Groups.Insert request = null;

        try {
            request = directoryClient.groups().insert(group);
        } catch (IOException e) {
            LOG.error("An unknown error occurred: " + e);
        }

        return (Group) execute(request);
    }

    /**
     * removeGroup removes a group from Google.
     * @param directoryClient a Directory client
     * @param groupKey an identifier for a group (e-mail address is the most popular)
     * @throws IOException
     */
    public static void removeGroup(Directory directoryClient, String groupKey) throws IOException {
        LOG.debug("removeGroup() - {}", groupKey);

        Directory.Groups.Delete request = null;

        try {
            request = directoryClient.groups().delete(groupKey);
        } catch (IOException e) {
            LOG.error("An unknown error occurred: " + e);
        }

        execute(request);
    }

    /**
     * addGroup adds a group to Google.
     * @param directoryClient a Directory client
     * @param group a populated Group object
     * @return the new Group object created/returned by Google
     * @throws IOException
     */
    public static Group updateGroup(Directory directoryClient, String groupKey, Group group) throws IOException {
        LOG.debug("updateGooGroup() - {}", group);

        Directory.Groups.Update request = null;

        try {
            request = directoryClient.groups().update(groupKey, group);
        } catch (IOException e) {
            LOG.error("An unknown error occurred: " + e);
        }

        return (Group) execute(request);
    }

    /**
     * addGroup adds a group to Google.
     * @param groupssettingsClient a Groupssettings client
     * @param groupSettings a populated Groups (group settings) object
     * @return the new Group object created/returned by Google
     * @throws IOException
     */
    public static com.google.api.services.groupssettings.model.Groups updateGroupSettings(Groupssettings groupssettingsClient, String groupKey, com.google.api.services.groupssettings.model.Groups groupSettings) throws IOException {
        LOG.debug("updateGroupssettings() - {}", groupKey);

        Groupssettings.Groups.Update request = null;

        try {
            request = groupssettingsClient.groups().update(groupKey, groupSettings);
        } catch (IOException e) {
            LOG.error("An unknown error occurred: " + e);
        }

        return (com.google.api.services.groupssettings.model.Groups) execute(request);
    }

    /**
     * retrieveAllUsers returns all of the users from Google.
     * @param directoryClient a Directory client
     * @return a list of all the users in the directory
     * @throws IOException
     */
    public static List retrieveAllUsers(Directory directoryClient) throws IOException {
        LOG.debug("retrieveAllUsers()");

        List allUsers = new ArrayList();

        Directory.Users.List request = null;
        try {
            request = directoryClient.users().list().setCustomer("my_customer").setMaxResults(500);
        } catch (IOException e) {
            LOG.error("An unknown error occurred: " + e);
        }

        do { //continue until we have all the pages read in.
            Users currentPage = (Users)execute(request);

            final List users = currentPage.getUsers();
            if (users != null) {
                allUsers.addAll(users);
            }
            request.setPageToken(currentPage.getNextPageToken());

        } while (request.getPageToken() != null && request.getPageToken().length() > 0);

        return allUsers;
    }

    /**
     *
     * @param directoryClient a Directory (service) object
     * @param userKey an identifier for a user (e-mail address is the most popular)
     * @return the User object returned by Google.
     * @throws IOException
     */
    public static User retrieveUser(Directory directoryClient, String userKey) throws IOException {
        LOG.debug("retrieveUser() - {}", userKey);

        Directory.Users.Get request = null;

        try {
            request = directoryClient.users().get(userKey);
        } catch (IOException e) {
            LOG.error("An unknown error occurred: " + e);
        }

        return (User) execute(request);
    }

    /**
     *
     * @param directoryClient a Directory client
     * @return a list of all the groups in the directory
     * @throws IOException
     */
    public static List retrieveAllGroups(Directory directoryClient) throws IOException {
        LOG.debug("retrieveAllGroups()");

        final List allGroups = new ArrayList();

        Directory.Groups.List request = null;
        try {
            request = directoryClient.groups().list().setCustomer("my_customer").setMaxResults(1000000);
        } catch (IOException e) {
            LOG.error("An unknown error occurred: " + e);
        }

        do { //continue until we have all the pages read in.
            final Groups currentPage = (Groups)execute(request);

            final List groups = currentPage.getGroups();
            if (groups != null) {
                allGroups.addAll(groups);
            }
            request.setPageToken(currentPage.getNextPageToken());

        } while (request.getPageToken() != null && request.getPageToken().length() > 0);

        return allGroups;
    }

    /**
     * retrieveGroup returns a requested group.
     * @param directoryClient a Directory client
     * @param groupKey an identifier for a group (e-mail address is the most popular)
     * @return the Group object from Google
     * @throws IOException
     */
    public static Group retrieveGroup(Directory directoryClient, String groupKey) throws IOException {
        LOG.debug("retrieveGroup() - {}", groupKey);

        Directory.Groups.Get request = null;

        try {
            request = directoryClient.groups().get(groupKey);
        } catch (IOException e) {
            LOG.error("An unknown error occurred: " + e);
        }

        return (Group) execute(request);
    }

    /**
     * retrieveGroup returns a requested group.
     * @param groupssettingClient a Groupssettings client
     * @param groupKey an identifier for a group (e-mail address is the most popular)
     * @return the Groups object from Google
     * @throws IOException
     */
    public static com.google.api.services.groupssettings.model.Groups retrieveGroupSettings(Groupssettings groupssettingClient, String groupKey) throws IOException {
        LOG.debug("retrieveGroupssettings() - {}", groupKey);

        Groupssettings.Groups.Get request = null;

        try {
            request = groupssettingClient.groups().get(groupKey);
        } catch (IOException e) {
            LOG.error("An unknown error occurred: " + e);
        }

        return (com.google.api.services.groupssettings.model.Groups) execute(request);
    }

    /**
     * retrieveGroupMember returns a requested group member.
     * @param directoryClient a Directory client
     * @param groupKey an identifier for a group (e-mail address is the most popular)
     * @param userKey an identifier for a group (e-mail address is the most popular)
     * @return the Group object from Google
     * @throws IOException
     */
    public static Member retrieveGroupMember(Directory directoryClient, String groupKey, String userKey) throws IOException {
        LOG.debug("retrieveGroupMember() - {} in {}", userKey, groupKey);

        Directory.Members.Get request = null;

        try {
            request = directoryClient.members().get(groupKey, userKey);
        } catch (IOException e) {
            LOG.error("An unknown error occurred: " + e);
        }

        return (Member) execute(request);
    }

    /**
     * addGroup adds a group to Google.
     * @param directoryClient a Directory client
     * @param groupKey an identifier for a group (e-mail address is the most popular)
     * @param userKey an identifier for a user (e-mail address is the most popular)
     * @param member a populated member object
     * @return the new Group object created/returned by Google
     * @throws IOException
     */
    public static Member updateGroupMember(Directory directoryClient, String groupKey, String userKey, Member member) throws IOException {
        LOG.debug("updateGroupMember() - {}", member);

        Directory.Members.Update request = null;

        try {
            request = directoryClient.members().update(groupKey, userKey, member);
        } catch (IOException e) {
            LOG.error("An unknown error occurred: " + e);
        }

        return (Member) execute(request);
    }

    /**
     * retrieveGroupMembers returns a list of members of a group.
     * @param directoryClient a Directory client
     * @param groupKey an identifier for a group (e-mail address is the most popular)
     * @return a list of Members in the Group
     * @throws IOException
     */
    public static List retrieveGroupMembers(Directory directoryClient, String groupKey) throws IOException {
        LOG.debug("retrieveGroupMembers() - {}", groupKey);

        final List groupMembers = new ArrayList();

        Directory.Members.List request = null;
        try {
            request = directoryClient.members().list(groupKey);
        } catch (IOException e) {
            LOG.error("An unknown error occurred: " + e);
        }

        do { //continue until we have all the pages read in.
            try {
                final Members currentPage = (Members) execute(request);

                final List members = currentPage.getMembers();
                if (members != null) {
                    groupMembers.addAll(members);
                }

                request.setPageToken(currentPage.getNextPageToken());
            } catch (NullPointerException ex) {
                break;
            }

        } while (request.getPageToken() != null && request.getPageToken().length() > 0);

        return groupMembers;
    }

    /**
     * addGroupMember add an additional member to a group.
     * @param directoryClient a Directory client
     * @param groupKey an identifier for a group (e-mail address is the most popular)
     * @param member a Member object
     * @return a Member object stored on Google.
     * @throws IOException
     */
    public static Member addGroupMember(Directory directoryClient, String groupKey, Member member) throws IOException {
        LOG.debug("addGroupMember() - add {} to {}", member, groupKey);

        Directory.Members.Insert request = null;

        try {
            request = directoryClient.members().insert(groupKey, member);
        } catch (IOException e) {
            LOG.error("An unknown error occurred: " + e);
        }

        return (Member) execute(request);
    }

    /**
     * removeGroupMember removes a member of a group.
     * @param directoryClient a Directory client
     * @param groupKey an identifier for a user (e-mail address is the most popular)
     * @param memberKey an identifier for a user (e-mail address is the most popular)
     * @throws GoogleJsonResponseException
     */
    public static void removeGroupMember(Directory directoryClient, String groupKey, String memberKey) throws IOException {
        LOG.debug("removeGroupMember() - remove {} from {}", memberKey, groupKey);

        Directory.Members.Delete request = null;

        try {
            request = directoryClient.members().delete(groupKey, memberKey);
        } catch (IOException e) {
            LOG.error("An unknown error occurred: " + e);
        }

        execute(request);
    }

    /**
     * handleGoogleJsonResponseException makes the handling of exponential back-off easy.
     * @param ex the GoogleJsonResponseException being handled
     * @param interval the exponential back-off interval
     * @return true=no record found, false=everything was handled properly
     * @throws GoogleJsonResponseException
     */
    private static boolean handleGoogleJsonResponseException(GoogleJsonResponseException ex, int interval)
            throws GoogleJsonResponseException {

        final GoogleJsonError e = ex.getDetails();

        switch (e.getCode()) {
            case 403:
                if (e.getErrors().get(0).getReason().equals("rateLimitExceeded")
                    || e.getErrors().get(0).getReason().equals("userRateLimitExceeded")) {

                    try {
                        LOG.warn("handleGoogleJsonResponseException() - we've exceeded a rate limit ({}) so taking a nap. (You should see if you can get the rate limit increased by Google.)", e.getErrors().get(0).getReason());
                        Thread.sleep((1 << interval) * 1000 + randomGenerator.nextInt(1001));
                    } catch (InterruptedException ie) {
                        LOG.debug("handleGoogleJsonResponseException() - {}", ie);
                    }
                } else {
                    LOG.info("handleGoogleJsonResponseException() - Unknown 403 error: {}", e);
                }

                break;

            case 404: //Not found
                LOG.info("handleGoogleJsonResponseException() - Not found: {}", e);
                return true;

            case 503:
                if (e.getErrors().get(0).getReason().equals("backendError")) {
                    try {
                        LOG.warn("handleGoogleJsonResponseException() - service unavailable/backend error so taking a nap.");
                        Thread.sleep((1 << interval) * 1000 + randomGenerator.nextInt(1001));
                    } catch (InterruptedException ie) {
                        LOG.debug("handleGoogleJsonResponseException() - {}", ie);
                    }

                } else {
                    LOG.debug("handleGoogleJsonResponseException() - Unknown 503 error: {}", e);
                }
                break;

            default:
                // Other error, re-throw.
                throw ex;
        }
        return false;
    }

    /**
     * execute takes a DirectoryRequest and calls the execute() method and handles exponential back-off, etc.
     * @param request a populated DirectoryRequest object
     * @return an output Object that should be cast in the calling method
     * @throws IOException
     */
    private static Object execute(DirectoryRequest request) throws IOException {
        return execute(request, 1);
    }

    /**
     * execute takes a DirectoryRequest and calls the execute() method and handles exponential back-off, etc.
     * @param interval the count of attempts that this request has had.
     * @param request a populated DirectoryRequest object
     * @return an output Object that should be cast in the calling method
     * @throws IOException
     */
    private static Object execute(DirectoryRequest request, int interval) throws IOException {
        LOG.trace("execute() - {} request attempt #{}",request.getClass().getName().replace(request.getClass().getPackage().getName(), ""), interval);

        try {
            return request.execute();
        } catch (GoogleJsonResponseException ex) {
            if (interval == 7) {
                LOG.error("execute() - Retried attempt 7 times, failing request");
                throw ex;

            } else {
                if (handleGoogleJsonResponseException(ex, interval)) { //404's return true
                    return null;
                } else {
                    return execute(request, ++interval);
                }
            }
        } catch(IOException e) {
            LOG.error("execute() - An unknown IO error occurred: " + e);

            if (interval == 7) {
                LOG.error("Retried attempt 7 times, failing request");
                throw e;

            } else {
                return execute(request, ++interval);
            }
        }

    }


    /**
     * execute takes a GroupssettingsRequest and calls the execute() method and handles exponential back-off, etc.
     * @param request a populated GroupssettingsRequest object
     * @return an output Object that should be cast in the calling method
     * @throws IOException
     */
    private static Object execute(GroupssettingsRequest request) throws IOException {
        return execute(request, 1);
    }

    /**
     * execute takes a GroupssettingsRequest and calls the execute() method and handles exponential back-off, etc.
     * @param request a populated GroupsettingsRequest object
     * @param interval the count of attempts that this request has had.
     * @return an output Object that should be cast in the calling method
     * @throws IOException
     */
    private static Object execute(GroupssettingsRequest request, int interval) throws IOException {
        LOG.trace("execute() - {} request attempt #{}",request.getClass().getName().replace(request.getClass().getPackage().getName(), ""), interval);

        try {
            return request.execute();
        } catch (GoogleJsonResponseException ex) {
            if (interval == 7) {
                LOG.error("execute() - Retried attempt 7 times, failing request");
                throw ex;

            } else {
                if (handleGoogleJsonResponseException(ex, interval)) { //404's return true
                    return null;
                } else {
                    return execute(request, ++interval);
                }
            }
        } catch(IOException e) {
            LOG.error("execute() - An unknown IO error occurred: " + e);

            if (interval == 7) {
                LOG.error("Retried attempt 7 times, failing request");
                throw e;

            } else {
                return execute(request, ++interval);
            }
        }

    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy