/// <summary>
        /// Update existing audit log data after addition
        /// </summary>
        /// <param name="auditLogAnalyticsData"></param>
        /// <returns></returns>
        public bool updateAnalyticsDataToTable(AuditLogAnalyticsDataInfo auditLogAnalyticsData)
        {
            // Parse the connection string and return a reference to the storage account.
            CloudStorageAccount storageAccount = CloudStorageAccount.Parse(AuditLogStorageConnectionString);

            // Create the table client.
            CloudTableClient tableClient = storageAccount.CreateCloudTableClient();

            // Create the CloudTable object that represents the "Audit Log" table.
            CloudTable auditDataLogTable = tableClient.GetTableReference(AuditLogDataTableName);

            // Create the CloudTable object that represents the "Audit Log" table.
            CloudTable             auditOpsStatusTable = tableClient.GetTableReference(AuditLogAnalyticsTableName);
            O365AuditLogOperations auditOpstableData   = new O365AuditLogOperations(auditLogAnalyticsData.PartitionKey, auditLogAnalyticsData.RowPrefix);

            auditOpstableData.ExcludedOperations     = auditLogAnalyticsData.ExcludedOperations;
            auditOpstableData.IncludedOperations     = auditLogAnalyticsData.IncludedOperations;
            auditOpstableData.IncludedRecords        = auditLogAnalyticsData.IncludedRecords;
            auditOpstableData.ExcludedRecords        = auditLogAnalyticsData.ExcludedRecords;
            auditOpstableData.TotalRecords           = auditLogAnalyticsData.TotalRecords;
            auditOpstableData.RunDate                = auditLogAnalyticsData.RunDate;
            auditOpstableData.StartHour              = auditLogAnalyticsData.StartHour;
            auditOpstableData.EndHour                = auditLogAnalyticsData.EndHour;
            auditOpstableData.RunHour                = auditLogAnalyticsData.RunHour;
            auditOpstableData.RunFrequency           = auditLogAnalyticsData.RunFrequency;
            auditOpstableData.LogOperationStatus     = auditLogAnalyticsData.LogOperationStatus;
            auditOpstableData.LogOperationSuccessful = auditLogAnalyticsData.LogOperationSuccessful;
            TableOperation auditOpsinsertOperation = TableOperation.InsertOrMerge(auditOpstableData);

            log.LogInformation($"Inserting Operations Data");

            // Execute the insert operation.
            TableResult result = auditOpsStatusTable.ExecuteAsync(auditOpsinsertOperation).GetAwaiter().GetResult();

            if (result.HttpStatusCode.ToEnum <HttpStatusCode>() == HttpStatusCode.BadGateway || result.HttpStatusCode.ToEnum <HttpStatusCode>() == HttpStatusCode.BadRequest)
            {
                throw new Exception($"Table Update failed for Audit Log data entry. Captured error {result.HttpStatusCode} for Audit Log Analytics entry ");
            }
            return(true);
        }
        /// <summary>
        /// Insert the Audit detail data now into Azure Table
        /// </summary>
        /// <param name="auditDetailedReports"></param>
        /// <param name="auditLogDataAnalytics"></param>
        /// <returns></returns>
        public AuditLogAnalyticsDataInfo addDatatoAzureStore(List <AuditDetailedReport> auditDetailedReports, AuditLogAnalyticsDataInfo auditLogDataAnalytics)
        {
            try
            {
                List <string> operations         = getAuditOperations();
                List <string> IncludedOperations = new List <string>();
                List <string> ExcludedOperations = new List <string>();
                int           includedRecords    = 0;
                int           excludedRecords    = 0;

                // Parse the connection string and return a reference to the storage account.
                CloudStorageAccount storageAccount = CloudStorageAccount.Parse(AuditLogStorageConnectionString);

                // Create the table client.
                CloudTableClient tableClient = storageAccount.CreateCloudTableClient();

                // Create the CloudTable object that represents the "Audit Log" table.
                CloudTable auditDataLogTable = tableClient.GetTableReference(AuditLogDataTableName);

                //// Add the Audit log information to Azure Table -- Primary Table to capture Audit log data
                foreach (AuditDetailedReport auditDetailReport in auditDetailedReports)
                {
                    if (operations.Contains(auditDetailReport.Operation, StringComparer.OrdinalIgnoreCase))
                    {
                        includedRecords++;
                        string           auditlogpartitionPrefix = AuditLogPartitionKeyPrefix + "_" + getAuditShortDateStringFromUTC(auditDetailReport.CreationTime);
                        string           auditlogrowprefix       = auditDetailReport.Id;
                        O365AuditLogData auditLogTableData       = new O365AuditLogData(auditlogpartitionPrefix, auditlogrowprefix);
                        O365MgmtApiEntities <AuditDetailedReport, O365AuditLogData> .Copy(auditDetailReport, auditLogTableData);

                        auditLogTableData.CreationTime = auditLogTableData.CreationTime.ToLocalTime();
                        // Create the TableOperation object that inserts the customer entity.
                        TableOperation auditLoginsertOperation = TableOperation.InsertOrMerge(auditLogTableData);

                        log.LogInformation($"Inserting Operation - {auditDetailReport.Operation} Id - {auditDetailReport.Id} ");

                        // Execute the insert operation.
                        TableResult result = auditDataLogTable.ExecuteAsync(auditLoginsertOperation).GetAwaiter().GetResult();
                        if (result.HttpStatusCode.ToEnum <HttpStatusCode>() == HttpStatusCode.BadGateway || result.HttpStatusCode.ToEnum <HttpStatusCode>() == HttpStatusCode.BadRequest)
                        {
                            throw new Exception($"Table Update failed for Audit Log data entry. Captured error {result.HttpStatusCode} for Operation - { auditDetailReport.Operation },  Id {auditDetailReport.Id} ");
                        }

                        if (!IncludedOperations.Contains(auditDetailReport.Operation, StringComparer.OrdinalIgnoreCase))
                        {
                            IncludedOperations.Add(auditDetailReport.Operation);
                        }
                    }
                    else
                    {
                        excludedRecords++;
                        if (!ExcludedOperations.Contains(auditDetailReport.Operation, StringComparer.OrdinalIgnoreCase))
                        {
                            ExcludedOperations.Add(auditDetailReport.Operation);
                        }
                        log.LogInformation($"Record not entered. Operation - { auditDetailReport.Operation },  Id {auditDetailReport.Id} ");
                    }
                }


                //// Capture the audit log report analytics for each run into the table for future reference -- Secondary Table for capturing analytics of each run
                log.LogInformation($"\r\n Included Records count {includedRecords} and Excluded Records count {excludedRecords}");

                auditLogDataAnalytics.ExcludedOperations     = string.Join(",", ExcludedOperations.ToArray());
                auditLogDataAnalytics.IncludedOperations     = string.Join(",", IncludedOperations.ToArray());
                auditLogDataAnalytics.IncludedRecords        = includedRecords;
                auditLogDataAnalytics.ExcludedRecords        = excludedRecords;
                auditLogDataAnalytics.TotalRecords           = auditDetailedReports.Count;
                auditLogDataAnalytics.LogOperationStatus     = "Completed";
                auditLogDataAnalytics.LogOperationSuccessful = true;
            }
            catch (Exception ex)
            {
                log.LogError(ex.Message);
                auditLogDataAnalytics.LogOperationStatus     = "Failed";
                auditLogDataAnalytics.LogOperationSuccessful = false;
            }
            return(auditLogDataAnalytics);
        }
Ejemplo n.º 3
0
        //// Set the timer in the timer trigger to run - More Info at here - https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer#c-example
        //// Note: this Function App is using Azure Funciton 1.x because of SharePoint CSOM dependency but can be used on Azure Function 2.x
        public static void Run([TimerTrigger("0 0 */1 * * *", RunOnStartup = true)] TimerInfo myTimer, ILogger log, TraceWriter logWriter)
        {
            log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
            logWriter.Info($"C# Timer trigger function executed at: {DateTime.Now}");
            #region "Variables"

            //// Set the Variables. For local debugging, use localsettings.json (see below) and for Azure Function, use Configuration in Platform Features
            ///  for eg  - local.settings.json
            ///  Note : Please replace all the strings with < > with the open bracket and close brackets
            //{
            //    "IsEncrypted": false,
            //      "Values": {
            //        "AzureWebJobsStorage": "UseDevelopmentStorage=true",
            //        "AzureWebJobsDashboard": "UseDevelopmentStorage=true",
            //        "StorageConnectionString": "<connection string for Azure storage account in .net>",
            //         "FUNCTIONS_WORKER_RUNTIME": "dotnet",
            //         "AuditLogDataTable": "<Azure Table Name>",
            //         "AuditLogAnalyticsTable": "<O365 Audit log capture table for this function>",
            //          "AuditLogCSVExportLocation": "<Azure>",
            //          "SPUserName": "******",
            //          "SPUserPassword": "******",
            //          "AuditLogDataTablePrefix": "<Partition Key Prefix for easy retrival>",
            //           "AuditLogOpsTablePrefix": "<Analytics Partition Key Prefic for easy retrival>",
            //            "TenantID": "<Tenant ID for the App>",
            //             "AuditLogAuthUrl": "https://manage.office.com",
            //             "AzureADAppID": "<Azure App ID>",
            //             "AzureADAppSecret": "<Azure App secret>"
            //      }
            //}

            string TenantID     = System.Environment.GetEnvironmentVariable("TenantID");
            string authString   = "https://login.windows.net/" + TenantID;
            string SPServiceUrl = "https://manage.office.com/api/v1.0/" + TenantID + "/activity/feed/subscriptions/content";
            string authUrl      = System.Environment.GetEnvironmentVariable("AuditLogAuthUrl");
            string clientId     = System.Environment.GetEnvironmentVariable("AzureADAppID");
            string clientSecret = System.Environment.GetEnvironmentVariable("AzureADAppSecret");
            #endregion

            try
            {
                //// **** Get the Authentication Token ****
                var authenticationContext = new AuthenticationContext(authString, false);
                authenticationContext.ExtendedLifeTimeEnabled = true;

                //// Config for OAuth client credentials
                ClientCredential     clientCred           = new ClientCredential(clientId, clientSecret);
                AuthenticationResult authenticationResult = null;
                Task runTask = Task.Run(async() => authenticationResult = await authenticationContext.AcquireTokenAsync(authUrl, clientCred));
                runTask.Wait();
                string token = authenticationResult.AccessToken;

                int startCount = 160;
                int endCount   = 480;
                int increments = (endCount - startCount) / 20;

                int startHour = 0;
                int endHour   = 0;

                for (int i = startCount / 20; i <= increments; i++)
                {
                    endHour   = i * 20;
                    startHour = (i + 1) * 20;

                    try
                    {
                        log.LogInformation($"Started pulling for now - {startHour} to now - {endHour}");

                        O365MgmtAPIDataService    dataService = new O365MgmtAPIDataService(token, log);
                        AuditLogAnalyticsDataInfo auditLogAnalyticsDataInfo = dataService.getInitialAnalyticsInfo(startHour, endHour);

                        if (dataService.updateAnalyticsDataToTable(auditLogAnalyticsDataInfo))
                        {
                            //// Get the time zone of the destination tenant. The date and time used by Audit log service is UTC format.
                            TimeZoneInfo aestTimeZone    = TimeZoneInfo.FindSystemTimeZoneById("AUS Eastern Standard Time");
                            DateTime     startHourUTC    = TimeZoneInfo.ConvertTimeToUtc(auditLogAnalyticsDataInfo.StartHour, aestTimeZone);
                            DateTime     endHourUTC      = TimeZoneInfo.ConvertTimeToUtc(auditLogAnalyticsDataInfo.EndHour, aestTimeZone);
                            string       startDateString = startHourUTC.ToUniversalTime().ToString("yyyy-MM-ddTHH:00");
                            string       endDateString   = endHourUTC.ToString("yyyy-MM-ddTHH:00");

                            //// ****************** Step 1: Start the audit log gathering process *****************
                            log.LogInformation($"getting Data from {startDateString} to {endDateString}");
                            //// Here I am fetching SharePoint events.
                            string urlParameters = $"?contentType=Audit.SharePoint&startTime={startDateString}&endTime={endDateString}";

                            //// Initialize the audit log data information
                            AuditInitialDataObject     auditInitialDataObject  = new AuditInitialDataObject();
                            List <AuditDetailedReport> auditDetailReportsFinal = new List <AuditDetailedReport>();

                            //// Loop through the detail URI information provided by the initial data call till there is no next page to be read
                            do
                            {
                                auditInitialDataObject = dataService.getAuditInitalData(SPServiceUrl, urlParameters);
                                if (auditInitialDataObject != null)
                                {
                                    if (auditInitialDataObject.AuditNextPageUri != null && auditInitialDataObject.AuditNextPageUri != "")
                                    {
                                        urlParameters = "?" + auditInitialDataObject.AuditNextPageUri.Split('?')[1];
                                    }
                                    List <AuditInitialReport> auditInitialReports = auditInitialDataObject.AuditInitialDataObj;

                                    //// set batch size = 200. Note : Above 500 the speed decreases drastically as per my tests.
                                    int maxCalls = 200;
                                    int count    = 0;

                                    //// **************** Step 2: For each of the calls call the detailed data fetch api in batches. Here the batch = 200 ***********************
                                    Parallel.ForEach(auditInitialReports, new ParallelOptions {
                                        MaxDegreeOfParallelism = maxCalls
                                    }, (auditInitialReport) =>
                                    {
                                        int loopCount = count++;
                                        log.LogInformation("Looking at request " + loopCount);
                                        List <AuditDetailedReport> auditDetailReports = dataService.getAuditDetailData(auditInitialReport.ContentUri);
                                        log.LogInformation("Got Audit Detail Reports of " + auditDetailReports.Count + " for loop number " + loopCount);
                                        foreach (AuditDetailedReport auditDetailReport in auditDetailReports)
                                        {
                                            auditDetailReportsFinal.Add(auditDetailReport);
                                        }
                                    });
                                }
                            } while (auditInitialDataObject.AuditNextPageUri != "");
                            log.LogInformation("Final Audit Detail Reports" + auditDetailReportsFinal.Count);

                            //// *************** Step 3 : Update additional properties to the Audit log data ***************************
                            int maxAuditUpdateCalls = 200;
                            Parallel.ForEach(auditDetailReportsFinal, new ParallelOptions {
                                MaxDegreeOfParallelism = maxAuditUpdateCalls
                            }, (auditDetailReport) =>
                            {
                                auditDetailReport = dataService.mapOrUpdateProperties(auditDetailReport);
                            });

                            //// **************** Step 4 : Add Data to Azure Table ****************
                            auditLogAnalyticsDataInfo = dataService.addDatatoAzureStore(auditDetailReportsFinal, auditLogAnalyticsDataInfo);

                            //// **************** Step 5 : Update the Report analytics for each Audit log run *******************
                            dataService.updateAnalyticsDataToTable(auditLogAnalyticsDataInfo);
                        }
                    }
                    catch (Exception ex1)
                    {
                        log.LogError($"error occurred {ex1.Message}");
                    }
                }
            }
            catch (Exception ex)
            {
                log.LogError(ex.Message);
            }
        }