com.microsoft.intellij.helpers.o365.Office365ManagerImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.intellij.helpers.o365.Office365ManagerImpl.java

Source

/**
 * Copyright (c) Microsoft Corporation
 * <p/>
 * All rights reserved.
 * <p/>
 * MIT License
 * <p/>
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and
 * to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * <p/>
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of
 * the Software.
 * <p/>
 * THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
 * THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.microsoft.intellij.helpers.o365;

import com.google.common.base.*;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
import com.intellij.openapi.project.Project;
import com.microsoft.directoryservices.*;
import com.microsoft.directoryservices.odata.ApplicationFetcher;
import com.microsoft.directoryservices.odata.DirectoryClient;
import com.microsoft.directoryservices.odata.DirectoryObjectOperations;
import com.microsoft.intellij.helpers.graph.PluginDependencyResolver;
import com.microsoft.intellij.helpers.graph.ServicePermissionEntry;
import com.microsoft.services.odata.ODataCollectionFetcher;
import com.microsoft.services.odata.ODataEntityFetcher;
import com.microsoft.services.odata.ODataOperations;
import com.microsoft.tooling.msservices.components.AppSettingsNames;
import com.microsoft.tooling.msservices.components.DefaultLoader;
import com.microsoft.tooling.msservices.components.PluginSettings;
import com.microsoft.tooling.msservices.helpers.NotNull;
import com.microsoft.tooling.msservices.helpers.Nullable;
import com.microsoft.tooling.msservices.helpers.StringHelper;
import com.microsoft.tooling.msservices.helpers.auth.AADManager;
import com.microsoft.tooling.msservices.helpers.auth.AADManagerImpl;
import com.microsoft.tooling.msservices.helpers.auth.UserInfo;
import com.microsoft.tooling.msservices.helpers.azure.AzureCmdException;
import com.microsoft.tooling.msservices.model.Office365Permission;
import com.microsoft.tooling.msservices.model.Office365PermissionList;
import com.microsoft.tooling.msservices.model.Office365Service;

import java.util.List;
import java.util.UUID;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Office365ManagerImpl implements Office365Manager {
    public class ServiceAppIds {
        public static final String EXCHANGE = "00000002-0000-0ff1-ce00-000000000000";
        public static final String SHARE_POINT = "00000003-0000-0ff1-ce00-000000000000";
        public static final String AZURE_ACTIVE_DIRECTORY = "00000002-0000-0000-c000-000000000000";
    }

    public static final String GRAPH_API_URI_TEMPLATE = "{base_uri}{tenant_domain}?api-version={api_version}";
    public static final String PROJECT_APP_ID = "com.microsoft.intellij.ProjectAppId";

    private static Office365Manager instance;
    private static Gson gson;

    private AADManager aadManager;

    private ReentrantReadWriteLock authDataLock = new ReentrantReadWriteLock(false);

    private UserInfo userInfo;
    private String accessToken;
    private DirectoryClient directoryDataServiceClient;

    private Office365ManagerImpl() {
        aadManager = AADManagerImpl.getManager();

        String json = DefaultLoader.getIdeHelper().getProperty(AppSettingsNames.O365_USER_INFO);

        if (!StringHelper.isNullOrWhiteSpace(json)) {
            try {
                UserInfo userInfo = gson.fromJson(json, UserInfo.class);
                setUserInfo(userInfo);
            } catch (JsonSyntaxException ignored) {
                DefaultLoader.getIdeHelper().unsetProperty(AppSettingsNames.O365_USER_INFO);
            }
        }
    }

    @NotNull
    public static synchronized Office365Manager getManager() {
        if (instance == null) {
            gson = new Gson();
            instance = new Office365ManagerImpl();
        }

        return instance;
    }

    @Override
    public void authenticate() throws AzureCmdException {
        PluginSettings settings = DefaultLoader.getPluginComponent().getSettings();

        UserInfo userInfo = aadManager.authenticate(settings.getGraphApiUri(),
                "Sign in to your Office 365 account");

        setUserInfo(userInfo);
    }

    @Override
    public boolean authenticated() {
        return getUserInfo() != null;
    }

    @Override
    public void clearAuthentication() {
        setUserInfo(null);
    }

    @NotNull
    @Override
    public ListenableFuture<List<Application>> getApplicationList() {
        return requestFutureWithToken(new RequestCallback<ListenableFuture<List<Application>>>() {
            @Override
            public ListenableFuture<List<Application>> execute() throws Throwable {
                return getAllObjects(getDirectoryClient().getapplications());
            }
        });
    }

    @Override
    @NotNull
    public ListenableFuture<Application> getApplicationByObjectId(@NotNull final String objectId) {
        return requestFutureWithToken(new RequestCallback<ListenableFuture<Application>>() {
            @Override
            public ListenableFuture<Application> execute() throws Throwable {
                return getDirectoryClient().getapplications().getById(objectId).read();
            }
        });
    }

    @Override
    @NotNull
    public ListenableFuture<List<ServicePermissionEntry>> getO365PermissionsForApp(@NotNull final String objectId) {
        return requestFutureWithToken(new RequestCallback<ListenableFuture<List<ServicePermissionEntry>>>() {
            @Override
            public ListenableFuture<List<ServicePermissionEntry>> execute() throws Throwable {
                return Futures.transform(getApplicationByObjectId(objectId),
                        new AsyncFunction<Application, List<ServicePermissionEntry>>() {
                            @Override
                            public ListenableFuture<List<ServicePermissionEntry>> apply(Application application)
                                    throws Exception {

                                final String[] filterAppIds = new String[] { ServiceAppIds.SHARE_POINT,
                                        ServiceAppIds.EXCHANGE, ServiceAppIds.AZURE_ACTIVE_DIRECTORY };

                                // build initial list of permission from the app's permissions
                                final List<ServicePermissionEntry> servicePermissions = getO365PermissionsFromResourceAccess(
                                        application.getrequiredResourceAccess(), filterAppIds);

                                // get permissions list from O365 service principals
                                return Futures.transform(getServicePrincipalsForO365(),
                                        new AsyncFunction<List<ServicePrincipal>, List<ServicePermissionEntry>>() {
                                            @Override
                                            public ListenableFuture<List<ServicePermissionEntry>> apply(
                                                    List<ServicePrincipal> servicePrincipals) throws Exception {

                                                for (final ServicePrincipal servicePrincipal : servicePrincipals) {
                                                    // lookup this service principal in app's list of resources; if it's not found add an entry
                                                    ServicePermissionEntry servicePermissionEntry = Iterables.find(
                                                            servicePermissions,
                                                            new Predicate<ServicePermissionEntry>() {
                                                                @Override
                                                                public boolean apply(
                                                                        ServicePermissionEntry servicePermissionEntry) {
                                                                    return servicePermissionEntry.getKey().getId()
                                                                            .equals(servicePrincipal.getappId());
                                                                }
                                                            }, null);

                                                    if (servicePermissionEntry == null) {
                                                        servicePermissions.add(
                                                                servicePermissionEntry = new ServicePermissionEntry(
                                                                        new Office365Service(),
                                                                        new Office365PermissionList()));
                                                    }

                                                    Office365Service service = servicePermissionEntry.getKey();
                                                    Office365PermissionList permissionList = servicePermissionEntry
                                                            .getValue();
                                                    service.setId(servicePrincipal.getappId());
                                                    service.setName(servicePrincipal.getdisplayName());

                                                    List<OAuth2Permission> permissions = servicePrincipal
                                                            .getoauth2Permissions();
                                                    for (final OAuth2Permission permission : permissions) {
                                                        // lookup permission in permissionList
                                                        Office365Permission office365Permission = Iterables.find(
                                                                permissionList,
                                                                new Predicate<Office365Permission>() {
                                                                    @Override
                                                                    public boolean apply(
                                                                            Office365Permission office365Permission) {
                                                                        return office365Permission.getId().equals(
                                                                                permission.getid().toString());
                                                                    }
                                                                }, null);

                                                        if (office365Permission == null) {
                                                            permissionList.add(
                                                                    office365Permission = new Office365Permission());
                                                            office365Permission.setEnabled(false);
                                                        }

                                                        office365Permission.setId(permission.getid().toString());
                                                        office365Permission.setName(
                                                                getPermissionDisplayName(permission.getvalue()));
                                                        office365Permission.setDescription(
                                                                permission.getuserConsentDisplayName());
                                                    }
                                                }

                                                return Futures.immediateFuture(servicePermissions);
                                            }
                                        });
                            }
                        });
            }
        });
    }

    private String getPermissionDisplayName(String displayName) {
        // replace '.' and '_' with space characters and title case the display name
        return Joiner.on(' ')
                .join(Iterables.transform(
                        Splitter.on(' ').split(CharMatcher.anyOf("._").replaceFrom(displayName, ' ')),
                        new Function<String, String>() {
                            @Override
                            public String apply(String str) {
                                return Character.toUpperCase(str.charAt(0)) + str.substring(1);
                            }
                        }));
    }

    private List<ServicePermissionEntry> getO365PermissionsFromResourceAccess(
            List<RequiredResourceAccess> requiredResourceAccesses, String[] filterAppIds) {

        List<ServicePermissionEntry> entryList = Lists.newArrayList();
        if (requiredResourceAccesses == null) {
            return entryList;
        }

        for (final RequiredResourceAccess requiredResourceAccess : requiredResourceAccesses) {
            // we're interested in this resource only if it is one of the app id's in "filterAppIds"
            boolean isO365Resource = Iterators.any(Iterators.forArray(filterAppIds), new Predicate<String>() {
                @Override
                public boolean apply(String appId) {
                    return requiredResourceAccess.getresourceAppId().equals(appId);
                }
            });
            if (!isO365Resource) {
                continue;
            }

            Office365Service service = new Office365Service();
            Office365PermissionList permissions = new Office365PermissionList();
            entryList.add(new ServicePermissionEntry(service, permissions));

            service.setId(requiredResourceAccess.getresourceAppId());
            List<ResourceAccess> resourceAccesses = requiredResourceAccess.getresourceAccess();
            if (resourceAccesses == null) {
                continue;
            }
            for (ResourceAccess resourceAccess : resourceAccesses) {
                Office365Permission permission = new Office365Permission(resourceAccess.getid().toString(), "", "",
                        resourceAccess.gettype().equals("Scope"));
                permissions.add(permission);
            }
        }

        return entryList;
    }

    @Override
    @NotNull
    public ListenableFuture<Application> setO365PermissionsForApp(@NotNull Application application,
            @NotNull List<ServicePermissionEntry> permissionEntryList) {
        List<RequiredResourceAccess> requiredResourceAccesses = application.getrequiredResourceAccess();
        if (requiredResourceAccesses == null) {
            application.setrequiredResourceAccess(requiredResourceAccesses = Lists.newArrayList());
        }

        for (ServicePermissionEntry permissionEntry : permissionEntryList) {
            final Office365Service service = permissionEntry.getKey();

            // filter permissions for enabled permissions
            Iterable<Office365Permission> permissionList = Iterables.filter(permissionEntry.getValue(),
                    new Predicate<Office365Permission>() {
                        @Override
                        public boolean apply(Office365Permission office365Permission) {
                            return office365Permission.isEnabled();
                        }
                    });

            // transform Office365Permission objects into ResourceAccess objects
            List<ResourceAccess> resourceAccessList = Lists.newArrayList(
                    Iterables.transform(permissionList, new Function<Office365Permission, ResourceAccess>() {
                        @Override
                        public ResourceAccess apply(Office365Permission office365Permission) {
                            ResourceAccess resourceAccess = new ResourceAccess();
                            resourceAccess.setid(UUID.fromString(office365Permission.getId()));
                            resourceAccess.settype("Scope");
                            return resourceAccess;
                        }
                    }));

            // get reference to service from app in case it exists
            RequiredResourceAccess requiredResourceAccess = Iterables.find(requiredResourceAccesses,
                    new Predicate<RequiredResourceAccess>() {
                        @Override
                        public boolean apply(RequiredResourceAccess requiredResourceAccess) {
                            return requiredResourceAccess.getresourceAppId().equals(service.getId());
                        }
                    }, null);

            if (requiredResourceAccess == null && !resourceAccessList.isEmpty()) {
                requiredResourceAccesses.add(requiredResourceAccess = new RequiredResourceAccess());
                requiredResourceAccess.setresourceAppId(service.getId());
            }

            if (requiredResourceAccess != null) {
                if (resourceAccessList.isEmpty()) {
                    // remove requiredResourceAccess from requiredResourceAccesses
                    requiredResourceAccesses.remove(requiredResourceAccess);
                } else {
                    requiredResourceAccess.setresourceAccess(resourceAccessList);
                }
            }
        }

        return updateApplication(application);
    }

    @Override
    @NotNull
    public ListenableFuture<Application> updateApplication(@NotNull final Application application) {
        return requestFutureWithToken(new RequestCallback<ListenableFuture<Application>>() {
            @Override
            public ListenableFuture<Application> execute() throws Throwable {
                ApplicationFetcher appFetcher = getDirectoryClient().getapplications()
                        .getById(application.getobjectId());
                return appFetcher.update(application);
            }
        });
    }

    @Override
    @NotNull
    public ListenableFuture<List<ServicePrincipal>> getServicePrincipals() {
        return requestFutureWithToken(new RequestCallback<ListenableFuture<List<ServicePrincipal>>>() {
            @Override
            public ListenableFuture<List<ServicePrincipal>> execute() throws Throwable {
                return getAllObjects(getDirectoryClient().getservicePrincipals());
            }
        });
    }

    @Override
    @NotNull
    public ListenableFuture<List<ServicePrincipal>> getServicePrincipalsForO365() {
        return requestFutureWithToken(new RequestCallback<ListenableFuture<List<ServicePrincipal>>>() {
            @Override
            public ListenableFuture<List<ServicePrincipal>> execute() throws Throwable {
                // build the filter
                String[] appIds = new String[] { ServiceAppIds.AZURE_ACTIVE_DIRECTORY, ServiceAppIds.EXCHANGE,
                        ServiceAppIds.SHARE_POINT };
                String filter = "appId eq '" + Joiner.on("' or appId eq '").join(appIds) + "'";
                return getDirectoryClient().getservicePrincipals().filter(filter).read();
            }
        });
    }

    private <T> ListenableFuture<T> getFirstItem(ListenableFuture<List<T>> future) {
        return Futures.transform(future, new AsyncFunction<List<T>, T>() {
            @Override
            public ListenableFuture<T> apply(List<T> items) throws Exception {
                return Futures.immediateFuture((items != null && items.size() > 0) ? items.get(0) : null);
            }
        });
    }

    @Override
    @NotNull
    public ListenableFuture<List<OAuth2PermissionGrant>> getPermissionGrants() {
        return requestFutureWithToken(new RequestCallback<ListenableFuture<List<OAuth2PermissionGrant>>>() {
            @Override
            public ListenableFuture<List<OAuth2PermissionGrant>> execute() throws Throwable {
                return getDirectoryClient().getoauth2PermissionGrants().read();
            }
        });
    }

    private <E extends DirectoryObject, F extends ODataEntityFetcher<E, ? extends DirectoryObjectOperations>, O extends ODataOperations> ListenableFuture<List<E>> getAllObjects(
            final ODataCollectionFetcher<E, F, O> fetcher) {

        return Futures.transform(fetcher.read(), new AsyncFunction<List<E>, List<E>>() {
            @Override
            public ListenableFuture<List<E>> apply(List<E> entities) throws Exception {
                return Futures.successfulAsList(
                        Lists.transform(entities, new Function<E, ListenableFuture<? extends E>>() {
                            @Override
                            public ListenableFuture<? extends E> apply(E e) {
                                return fetcher.getById(e.getobjectId()).read();
                            }
                        }));
            }
        });
    }

    @Override
    @NotNull
    public ListenableFuture<Application> registerApplication(@NotNull final Application application) {
        return requestFutureWithToken(new RequestCallback<ListenableFuture<Application>>() {
            @Override
            public ListenableFuture<Application> execute() throws Throwable {
                // register the app and then create a service principal for the app if there isn't already one
                return Futures.transform(getDirectoryClient().getapplications().add(application),
                        new AsyncFunction<Application, Application>() {
                            @Override
                            public ListenableFuture<Application> apply(final Application application)
                                    throws Exception {
                                return Futures.transform(getServicePrincipalsForApp(application),
                                        new AsyncFunction<List<ServicePrincipal>, Application>() {
                                            @Override
                                            public ListenableFuture<Application> apply(
                                                    List<ServicePrincipal> servicePrincipals) throws Exception {
                                                if (servicePrincipals.size() == 0) {
                                                    return createServicePrincipalForApp(application);
                                                }

                                                return Futures.immediateFuture(application);
                                            }
                                        });
                            }
                        });
            }
        });
    }

    private ListenableFuture<Application> createServicePrincipalForApp(final Application application)
            throws AzureCmdException {
        ServicePrincipal servicePrincipal = new ServicePrincipal();
        servicePrincipal.setappId(application.getappId());

        servicePrincipal.setaccountEnabled(true);

        return Futures.transform(getDirectoryClient().getservicePrincipals().add(servicePrincipal),
                new AsyncFunction<ServicePrincipal, Application>() {
                    @Override
                    public ListenableFuture<Application> apply(ServicePrincipal servicePrincipal) throws Exception {
                        return Futures.immediateFuture(application);
                    }
                });
    }

    @Override
    @NotNull
    public ListenableFuture<Application> getApplicationForProject(@NotNull Project project) {
        final String appId = DefaultLoader.getIdeHelper().getProperty(project, PROJECT_APP_ID);
        if (StringHelper.isNullOrWhiteSpace(appId)) {
            return Futures.immediateFuture(null);
        }

        return requestFutureWithToken(new RequestCallback<ListenableFuture<Application>>() {
            @Override
            public ListenableFuture<Application> execute() throws Throwable {
                return getFirstItem(
                        getDirectoryClient().getapplications().filter("appId eq '" + appId + "'").read());
            }
        });
    }

    @Override
    public void setApplicationForProject(@NotNull Project project, @NotNull Application application) {
        DefaultLoader.getIdeHelper().setProperty(project, PROJECT_APP_ID, application.getappId());
        project.save();
    }

    @NotNull
    @Override
    public ListenableFuture<List<ServicePrincipal>> getServicePrincipalsForApp(
            @NotNull final Application application) {
        return requestFutureWithToken(new RequestCallback<ListenableFuture<List<ServicePrincipal>>>() {
            @Override
            public ListenableFuture<List<ServicePrincipal>> execute() throws Throwable {
                return getDirectoryClient().getservicePrincipals()
                        .filter("appId eq '" + application.getappId() + "'").read();
            }
        });
    }

    @Override
    @NotNull
    public ListenableFuture<List<ServicePrincipal>> getO365ServicePrincipalsForApp(
            @NotNull final Application application) {
        return requestFutureWithToken(new RequestCallback<ListenableFuture<List<ServicePrincipal>>>() {
            @Override
            public ListenableFuture<List<ServicePrincipal>> execute() throws Throwable {
                @SuppressWarnings("unchecked")
                ListenableFuture<List<ServicePrincipal>>[] futures = new ListenableFuture[] {
                        getServicePrincipalsForApp(application), getServicePrincipalsForO365() };

                final String[] filterAppIds = new String[] { ServiceAppIds.SHARE_POINT, ServiceAppIds.EXCHANGE,
                        ServiceAppIds.AZURE_ACTIVE_DIRECTORY };

                return Futures.transform(Futures.allAsList(futures),
                        new AsyncFunction<List<List<ServicePrincipal>>, List<ServicePrincipal>>() {
                            @Override
                            public ListenableFuture<List<ServicePrincipal>> apply(
                                    List<List<ServicePrincipal>> lists) throws Exception {
                                // According to Guava documentation for allAsList, the list of results is in the
                                // same order as the input list. So first we get the service principals for the app
                                // filtered for O365 and Graph service principals.
                                final List<ServicePrincipal> servicePrincipalsForApp = Lists.newArrayList(
                                        Iterables.filter(lists.get(0), new Predicate<ServicePrincipal>() {
                                            @Override
                                            public boolean apply(final ServicePrincipal servicePrincipal) {
                                                // we are only interested in O365 and Graph service principals
                                                return Iterators.any(Iterators.forArray(filterAppIds),
                                                        new Predicate<String>() {
                                                            @Override
                                                            public boolean apply(String appId) {
                                                                return appId.equals(servicePrincipal.getappId());
                                                            }
                                                        });
                                            }
                                        }));

                                // next we get the O365/graph service principals
                                final List<ServicePrincipal> servicePrincipalsForO365 = lists.get(1);

                                // then we add service principals from servicePrincipalsForO365 to servicePrincipalsForApp
                                // where the service principal is not available in the latter
                                Iterable<ServicePrincipal> servicePrincipalsToBeAdded = Iterables
                                        .filter(servicePrincipalsForO365, new Predicate<ServicePrincipal>() {
                                            @Override
                                            public boolean apply(ServicePrincipal servicePrincipal) {
                                                return !servicePrincipalsForApp.contains(servicePrincipal);
                                            }
                                        });
                                Iterables.addAll(servicePrincipalsForApp, servicePrincipalsToBeAdded);

                                // assign the appid to the service principal and reset permissions on new service principals;
                                // we do Lists.newArrayList calls below to create a copy of the service lists because Lists.transform
                                // invokes the transformation function lazily and this causes problems for us; we force immediate
                                // evaluation of our transfomer by copying the elements to a new list
                                List<ServicePrincipal> servicePrincipals = Lists
                                        .newArrayList(Lists.transform(servicePrincipalsForApp,
                                                new Function<ServicePrincipal, ServicePrincipal>() {
                                                    @Override
                                                    public ServicePrincipal apply(
                                                            ServicePrincipal servicePrincipal) {
                                                        if (!servicePrincipal.getappId()
                                                                .equals(application.getappId())) {
                                                            servicePrincipal.setappId(application.getappId());
                                                            servicePrincipal.setoauth2Permissions(
                                                                    Lists.newArrayList(Lists.transform(
                                                                            servicePrincipal.getoauth2Permissions(),
                                                                            new Function<OAuth2Permission, OAuth2Permission>() {
                                                                                @Override
                                                                                public OAuth2Permission apply(
                                                                                        OAuth2Permission oAuth2Permission) {
                                                                                    oAuth2Permission
                                                                                            .setisEnabled(false);
                                                                                    return oAuth2Permission;
                                                                                }
                                                                            })));
                                                        }

                                                        return servicePrincipal;
                                                    }
                                                }));

                                return Futures.immediateFuture(servicePrincipals);
                            }
                        });
            }
        });
    }

    @Override
    @NotNull
    public ListenableFuture<List<ServicePrincipal>> addServicePrincipals(
            @NotNull final List<ServicePrincipal> servicePrincipals) {

        return requestFutureWithToken(new RequestCallback<ListenableFuture<List<ServicePrincipal>>>() {
            @Override
            public ListenableFuture<List<ServicePrincipal>> execute() throws Throwable {
                List<ListenableFuture<ServicePrincipal>> futures = Lists.transform(servicePrincipals,
                        new Function<ServicePrincipal, ListenableFuture<ServicePrincipal>>() {
                            @Override
                            public ListenableFuture<ServicePrincipal> apply(ServicePrincipal servicePrincipal) {
                                return getDirectoryClient().getservicePrincipals().add(servicePrincipal);
                            }
                        });

                return Futures.allAsList(futures);
            }
        });
    }

    @Nullable
    private UserInfo getUserInfo() {
        authDataLock.readLock().lock();

        try {
            return userInfo;
        } finally {
            authDataLock.readLock().unlock();
        }
    }

    private void setUserInfo(@Nullable UserInfo userInfo) {
        authDataLock.writeLock().lock();

        try {
            this.userInfo = userInfo;
            setAccessToken(null);

            String json = gson.toJson(userInfo, UserInfo.class);
            DefaultLoader.getIdeHelper().setProperty(AppSettingsNames.O365_USER_INFO, json);
        } finally {
            authDataLock.writeLock().unlock();
        }
    }

    @Nullable
    private String getAccessToken() {
        authDataLock.readLock().lock();

        try {
            return accessToken;
        } finally {
            authDataLock.readLock().unlock();
        }
    }

    private void setAccessToken(@Nullable String accessToken) {
        authDataLock.writeLock().lock();

        try {
            this.accessToken = accessToken;

            DirectoryClient directoryDataServiceClient = null;

            if (accessToken != null) {
                PluginDependencyResolver dependencyResolver = new PluginDependencyResolver(accessToken);
                directoryDataServiceClient = new DirectoryClient(getGraphApiUri(), dependencyResolver);
            }

            setDirectoryDataServiceClient(directoryDataServiceClient);
        } finally {
            authDataLock.writeLock().unlock();
        }
    }

    // NOTE: The result of calling getDirectoryClient should never be cached. This is because of the following
    // reasons:
    //  [a] every directory client object is associated with an authentication token
    //  [b] as part of execution of the method, tokens might expire and be renewed in which case a new directory
    //      client will be instantiated; if we use cached objects then we'll continue using the client with the
    //      expired token instead of the new one
    private DirectoryClient getDirectoryClient() {
        authDataLock.readLock().lock();

        try {
            return directoryDataServiceClient;
        } finally {
            authDataLock.readLock().unlock();
        }
    }

    private void setDirectoryDataServiceClient(DirectoryClient directoryDataServiceClient) {
        authDataLock.writeLock().lock();

        try {
            this.directoryDataServiceClient = directoryDataServiceClient;
        } finally {
            authDataLock.writeLock().unlock();
        }
    }

    private String getGraphApiUri() {
        PluginSettings settings = DefaultLoader.getPluginComponent().getSettings();

        return GRAPH_API_URI_TEMPLATE.replace("{base_uri}", settings.getGraphApiUri())
                .replace("{tenant_domain}", getTenantDomain())
                .replace("{api_version}", settings.getGraphApiVersion());
    }

    private String getTenantDomain() {
        UserInfo userInfo = getUserInfo();

        if (userInfo == null) {
            throw new IllegalStateException("user is null");
        }

        return userInfo.getTenantId();
    }

    @NotNull
    private <V> ListenableFuture<V> requestFutureWithToken(
            @NotNull final RequestCallback<ListenableFuture<V>> requestCallback) {
        PluginSettings settings = DefaultLoader.getPluginComponent().getSettings();

        com.microsoft.tooling.msservices.helpers.auth.RequestCallback<ListenableFuture<V>> aadRequestCB = new com.microsoft.tooling.msservices.helpers.auth.RequestCallback<ListenableFuture<V>>() {
            @NotNull
            @Override
            public ListenableFuture<V> execute(@NotNull String accessToken) throws Throwable {
                if (!accessToken.equals(getAccessToken())) {
                    authDataLock.writeLock().lock();

                    try {
                        if (!accessToken.equals(getAccessToken())) {
                            setAccessToken(accessToken);
                        }
                    } finally {
                        authDataLock.writeLock().unlock();
                    }
                }

                return requestCallback.execute();
            }
        };

        return aadManager.requestFuture(userInfo, settings.getGraphApiUri(), "Sign in to your Office 365 account",
                aadRequestCB);
    }
}