public static StoredSubscriptionState CreateNew(string subscriptionId)
            {
                var newState = new StoredSubscriptionState();

                newState.RowKey         = subscriptionId;
                newState.SubscriptionId = subscriptionId;
                return(newState);
            }
        /// <summary>
        /// Request the delta stream from OneDrive to find files that have changed between notifications for this account
        /// </summary>
        /// <param name="state">Our internal state information for the subscription we're processing.</param>
        /// <param name="client">Graph client for the attached user.</param>
        /// <param name="log">Tracewriter for debug output</param>
        /// <returns></returns>
        private static async Task <List <DriveItem> > FindChangedDriveItemsAsync(StoredSubscriptionState state, GraphServiceClient client, TraceWriter log)
        {
            string DefaultLatestDeltaUrl = idaMicrosoftGraphUrl + "/v1.0/drives/" + state.DriveId + "/root/delta?token=latest";

            // We default to reading the "latest" state of the drive, so we don't have to process all the files in the drive
            // when a new subscription comes in.
            string           deltaUrl          = state?.LastDeltaToken ?? DefaultLatestDeltaUrl;
            List <DriveItem> changedDriveItems = new List <DriveItem>();

            // Create an SDK request using the URL, instead of building up the request using the SDK
            IDriveItemDeltaRequest request = new DriveItemDeltaRequest(deltaUrl, client, null);

            // We max out at 50 requests of delta responses, just for demo purposes.
            const int MaxLoopCount = 50;

            for (int loopCount = 0; loopCount < MaxLoopCount && request != null; loopCount++)
            {
                log.Info($"Making request for '{state.SubscriptionId}' to '{deltaUrl}' ");

                // Get the next page of delta results
                IDriveItemDeltaCollectionPage deltaResponse = await request.GetAsync();

                // Filter to the audio files we're interested in working with and add them to our list
                var changedFiles = (from f in deltaResponse
                                    where f.File != null &&
                                    f.Name != null &&
                                    (f.Name.EndsWith(acceptedAudioFileExtension) || f.Audio != null) &&
                                    f.Deleted == null
                                    select f);
                changedDriveItems.AddRange(changedFiles);

                // Figure out how to proceed, whether we have more pages of changes to retrieve or not.
                if (null != deltaResponse.NextPageRequest)
                {
                    request = deltaResponse.NextPageRequest;
                }
                else if (null != deltaResponse.AdditionalData["@odata.deltaLink"])
                {
                    string deltaLink = (string)deltaResponse.AdditionalData["@odata.deltaLink"];

                    log.Verbose($"All changes requested, nextDeltaUrl: {deltaLink}");
                    state.LastDeltaToken = deltaLink;

                    return(changedDriveItems);
                }
                else
                {
                    // Shouldn't get here, but just in case, we don't want to get stuck in a loop forever.
                    request = null;
                }
            }

            // If we exit the For loop without returning, that means we read MaxLoopCount pages without finding a deltaToken
            log.Info($"Read through MaxLoopCount pages without finding an end. Too much data has changed and we're going to start over on the next notification.");
            state.LastDeltaToken = DefaultLatestDeltaUrl;

            return(changedDriveItems);
        }
Beispiel #3
0
        /// <summary>
        /// This method is called by the JavaScript on the home page to activate the data robot
        /// </summary>
        /// <param name="driveId"></param>
        /// <returns></returns>
        public async Task <IHttpActionResult> ActivateRobot(string driveId)
        {
            // Make sure we still have a user signed in before we do anything (we'll need this to get auth tokens for MS Graph)
            var signedInUserId = AuthHelper.GetUserId();

            if (string.IsNullOrEmpty(signedInUserId))
            {
                return(Ok(new DataRobotSetup {
                    Success = false, Error = "User needs to sign in."
                }));
            }

            // Setup a Microsoft Graph client for calls to the graph
            var client = GetGraphClient();

            // Configure the document library with the custom columns, if they don't already exist.
            try
            {
                await ProvisionDocumentLibraryAsync(driveId, client);
            }
            catch (Exception ex)
            {
                return(Ok(new DataRobotSetup {
                    Success = false, Error = $"Unable to provision the selected document library: {ex.Message}"
                }));
            }

            // Check to see if this user already has a subscription, so we avoid duplicate subscriptions (this sample only allows a user to hook up the data robot to a single document library)
            var storedState  = StoredSubscriptionState.FindUser(signedInUserId, AzureTableContext.Default.SyncStateTable);
            var subscription = await CreateOrRefreshSubscriptionAsync(driveId, client, signedInUserId, storedState?.SubscriptionId);

            var results = new DataRobotSetup {
                SubscriptionId = subscription.Id
            };

            if (storedState == null)
            {
                storedState = StoredSubscriptionState.CreateNew(subscription.Id, driveId);
                storedState.SignInUserId = signedInUserId;
            }

            // Catch up our delta link so we only start working on files modified starting now
            var latestDeltaResponse = await client.Drives[driveId].Root.Delta("latest").Request().GetAsync();

            storedState.LastDeltaToken = latestDeltaResponse.AdditionalData["@odata.deltaLink"] as string;

            // Once we have a subscription, then we need to store that information into our Azure Table
            storedState.Insert(AzureTableContext.Default.SyncStateTable);

            results.Success            = true;
            results.ExpirationDateTime = subscription.ExpirationDateTime;

            return(Ok(results));
        }
Beispiel #4
0
        /// <summary>
        /// Called by the homepage to remove the subscription and disable the data robot on the previously selected document library.
        /// This does not deprovision the changes to the document library, since that would involve potentially removing user data.
        /// </summary>
        /// <returns></returns>
        public async Task <IHttpActionResult> DisableRobot()
        {
            // Make sure we still have a user signed in before we do anything (we'll need this to get auth tokens for MS Graph)
            var signedInUserId = AuthHelper.GetUserId();

            if (string.IsNullOrEmpty(signedInUserId))
            {
                return(Ok(new DataRobotSetup {
                    Success = false, Error = "User needs to sign in."
                }));
            }

            // Setup a Microsoft Graph client for calls to the graph
            var client = GetGraphClient();

            // See if the robot was previous activated for the signed in user.
            var robotSubscription = StoredSubscriptionState.FindUser(signedInUserId, AzureTableContext.Default.SyncStateTable);

            if (null == robotSubscription)
            {
                return(Ok(new DataRobotSetup {
                    Success = true, Error = "The robot wasn't activated for you anyway!"
                }));
            }

            // Remove the webhook subscription
            try
            {
                await client.Subscriptions[robotSubscription.SubscriptionId].Request().DeleteAsync();
            }
            catch
            {
                // If the subscription doesn't exist or we get an error, we'll just consider it OK.
            }

            // Remove the robotSubscription information
            try
            {
                robotSubscription.Delete(AzureTableContext.Default.SyncStateTable);
            } catch (Exception ex)
            {
                return(Ok(new DataRobotSetup {
                    Success = false, Error = $"Unable to delete subscription information in our database. Please try again. ({ex.Message})"
                }));
            }

            return(Ok(new DataRobotSetup {
                Success = true, Error = "The robot was deactivated from your account."
            }));
        }
        public async Task <IHttpActionResult> DisableRobot()
        {
            // Setup a Microsoft Graph client for calls to the graph
            string             graphBaseUrl = SettingsHelper.MicrosoftGraphBaseUrl;
            GraphServiceClient client       = new GraphServiceClient(new DelegateAuthenticationProvider(async(req) =>
            {
                // Get a fresh auth token
                var authToken = await AuthHelper.GetUserAccessTokenSilentAsync(graphBaseUrl);
                req.Headers.TryAddWithoutValidation("Authorization", $"Bearer {authToken.AccessToken}");
            }));

            // Get an access token and signedInUserId
            AuthTokens tokens = null;

            try
            {
                tokens = await AuthHelper.GetUserAccessTokenSilentAsync(graphBaseUrl);
            }
            catch (Exception ex)
            {
                return(Ok(new DataRobotSetup {
                    Success = false, Error = ex.Message
                }));
            }

            // See if the robot was previous activated for the signed in user.
            var robotSubscription = StoredSubscriptionState.FindUser(tokens.SignInUserId, AzureTableContext.Default.SyncStateTable);

            if (null == robotSubscription)
            {
                return(Ok(new DataRobotSetup {
                    Success = true, Error = "The robot wasn't activated for you anyway!"
                }));
            }

            // Remove the webhook subscription
            try
            {
                await client.Subscriptions[robotSubscription.SubscriptionId].Request().DeleteAsync();
            }
            catch { }

            // Remove the robotSubscription information
            robotSubscription.Delete(AzureTableContext.Default.SyncStateTable);

            return(Ok(new DataRobotSetup {
                Success = true, Error = "The robot was been deactivated from your account."
            }));
        }
        /// <summary>
        /// Look up the subscription in our state table and see if we know anything about the subscription.
        /// If the subscription is found, then check for changed files and process them accordingly.
        /// </summary>
        /// <param name="subscriptionId">The subscription ID that generated the notification</param>
        /// <param name="syncStateTable">An Azure Table that contains our sync state information for each subscription / user.</param>
        /// <param name="tokenCacheTable">An Azure Table we use to cache ADAL tokens.</param>
        /// <param name="log">TraceWriter for debug output</param>
        /// <returns></returns>
        private static async Task <bool> CheckForSubscriptionChangesAsync(string subscriptionId, CloudTable syncStateTable, CloudTable tokenCacheTable, TraceWriter log)
        {
            // Retrieve our stored state from an Azure Table
            StoredSubscriptionState state = StoredSubscriptionState.Open(subscriptionId, syncStateTable);

            if (state == null)
            {
                log.Info($"Unknown subscription ID: '{subscriptionId}'.");
                return(false);
            }

            log.Info($"Found subscription ID: '{subscriptionId}' with stored delta URL: '{state.LastDeltaToken}'.");

            // Create a new instance of the Graph SDK client for the user attached to this subscription
            GraphServiceClient client = new GraphServiceClient(new DelegateAuthenticationProvider(async(request) => {
                string accessToken = await RetrieveAccessTokenAsync(state.SignInUserId, tokenCacheTable, log);
                request.Headers.TryAddWithoutValidation("Authorization", $"Bearer {accessToken}");
            }));

            client.BaseUrl = "https://graph.microsoft.com/stagingv1.0";

            // Query for items that have changed since the last notification was received
            List <DriveItem> changedDriveItems = await FindChangedDriveItemsAsync(state, client, log);

            // Iterate over the changed items and perform our work
            foreach (var driveItem in changedDriveItems)
            {
                try
                {
                    log.Info($"Processing file: {driveItem.Name}");
                    await ProcessAudioFileAsync(client, driveItem, log);
                }
                catch (Exception ex)
                {
                    log.Info($"Error with file {driveItem.Name}: {ex.Message}");
                }
            }

            // Update our saved state for this subscription
            state.Insert(syncStateTable);
            return(true);
        }
        public async Task <IHttpActionResult> ActivateRobot()
        {
            // Setup a Microsoft Graph client for calls to the graph
            string             graphBaseUrl = SettingsHelper.MicrosoftGraphBaseUrl;
            GraphServiceClient client       = new GraphServiceClient(new DelegateAuthenticationProvider(async(req) =>
            {
                // Get a fresh auth token
                var authToken = await AuthHelper.GetUserAccessTokenSilentAsync(graphBaseUrl);
                req.Headers.TryAddWithoutValidation("Authorization", $"Bearer {authToken.AccessToken}");
            }));

            // Get an access token and signedInUserId
            AuthTokens tokens = null;

            try
            {
                tokens = await AuthHelper.GetUserAccessTokenSilentAsync(graphBaseUrl);
            }
            catch (Exception ex)
            {
                return(Ok(new DataRobotSetup {
                    Success = false, Error = ex.Message
                }));
            }

            // Check to see if this user is already wired up, so we avoid duplicate subscriptions
            var robotSubscription = StoredSubscriptionState.FindUser(tokens.SignInUserId, AzureTableContext.Default.SyncStateTable);

            var notificationSubscription = new Subscription()
            {
                ChangeType         = "updated",
                NotificationUrl    = SettingsHelper.NotificationUrl,
                Resource           = "/me/drive/root",
                ExpirationDateTime = DateTime.UtcNow.AddDays(3),
                ClientState        = "SecretClientState"
            };

            Subscription createdSubscription = null;

            if (null != robotSubscription)
            {
                // See if our existing subscription can be extended to today + 3 days
                try
                {
                    createdSubscription = await client.Subscriptions[robotSubscription.SubscriptionId].Request().UpdateAsync(notificationSubscription);
                }
                catch { }
            }

            if (null == createdSubscription)
            {
                // No existing subscription or we failed to update the existing subscription, so create a new one
                createdSubscription = await client.Subscriptions.Request().AddAsync(notificationSubscription);
            }

            var results = new DataRobotSetup()
            {
                SubscriptionId = createdSubscription.Id
            };

            if (robotSubscription == null)
            {
                robotSubscription = StoredSubscriptionState.CreateNew(createdSubscription.Id);
                robotSubscription.SignInUserId = tokens.SignInUserId;
            }

            // Get the latest delta URL
            var latestDeltaResponse = await client.Me.Drive.Root.Delta("latest").Request().GetAsync();

            robotSubscription.LastDeltaToken = latestDeltaResponse.AdditionalData["@odata.deltaLink"] as string;

            // Once we have a subscription, then we need to store that information into our Azure Table
            robotSubscription.Insert(AzureTableContext.Default.SyncStateTable);

            results.Success            = true;
            results.ExpirationDateTime = createdSubscription.ExpirationDateTime;

            return(Ok(results));
        }