public void GraphApiHelperTestSetup() { var logger = new Mock <ILogger <GraphApiHelper> >().Object; var memoryCache = new Mock <IMemoryCache>().Object; graphApiHelper = new GraphApiHelper(logger, memoryCache, ConfigurationData.botOptions); }
public async Task <ActionResult> SendMessageSubmit(UserInfo userInfo) { // After Index method renders the View, user clicks Send Mail, which comes in here. EnsureUser(ref userInfo); SendMessageResponse sendMessageResult = new SendMessageResponse(); // Send email using the Microsoft Graph API. var token = Data.GetUserSessionTokenAny(Settings.GetUserAuthStateId(ControllerContext.HttpContext)); if (token.Provider == Settings.AzureADAuthority || token.Provider == Settings.AzureAD2Authority) { sendMessageResult = await GraphApiHelper.SendMessageAsync( token.AccessToken, GenerateEmail(userInfo)); } else if (token.Provider == Settings.GoogleAuthority) { sendMessageResult = await GoogleApiHelper.SendMessageAsync(token.AccessToken, GenerateEmail(userInfo), token.Username); } // Reuse the Index view for messages (sent, not sent, fail) . // Redirect to tell the browser to call the app back via the Index method. return(RedirectToAction(nameof(Index), new RouteValueDictionary(new Dictionary <string, object> { { "Status", sendMessageResult.Status }, { "StatusMessage", sendMessageResult.StatusMessage }, { "Address", userInfo.Address }, }))); }
private async Task LoadAndShowProfileInfoAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) { var tokenState = (TokenState)stepContext.Values["accessToken"]; var currentUser = await GraphApiHelper.GetCurrentUserAsync(tokenState, cancellationToken); await stepContext.Context.SendActivityAsync($"You are {currentUser.DisplayName}.", cancellationToken : cancellationToken); }
public static async Task CheckinFromDrive(this Record record, string driveId, string token, bool saveRecord = false) { string downloadUrl = GraphApiHelper.GetOneDriveItemContentIdUrl(driveId); var fileResult = await ODataHelper.GetItem <OneDriveItem>(GraphApiHelper.GetOneDriveItemIdUrl(driveId), token, null); string filePath = Path.Combine(TrimApplication.WebServerWorkPath, fileResult.Name); await ODataHelper.GetItem <string>(downloadUrl, token, filePath); var inputDocument = new InputDocument(filePath); inputDocument.CheckinAs = record.SuggestedFileName; record.SetDocument(inputDocument, true, false, "checkin from Word Online"); string pdfPath = Path.Combine(TrimApplication.WebServerWorkPath, Path.ChangeExtension(fileResult.Name, "pdf")); string pdfUrl = GraphApiHelper.GetOneDriveItemContentIdUrl(driveId, "pdf"); await ODataHelper.GetItem <string>(pdfUrl, token, pdfPath); var rendition = record.ChildRenditions.NewRendition(pdfPath, RenditionType.Longevity, "Preview"); if (saveRecord) { record.Save(); File.Delete(filePath); File.Delete(pdfPath); } return; }
/// <summary> /// Gets all the charts in the specified Excel workbook and presents them in a view. /// </summary> /// <param name="id">The internal ID of the workbook.</param> /// <returns>The view with the list of charts.</returns> public async Task <ActionResult> Index(string id) { // Get access token from the local database var token = Data.GetUserSessionToken(Settings.GetUserAuthStateId(ControllerContext.HttpContext), Settings.AzureADAuthority); var sheetsUrl = GraphApiHelper.GetSheetsWithChartsUrl(id, "&$select=name,id"); var sheets = await ODataHelper.GetItems <ExcelSheet>(sheetsUrl, token.AccessToken); // Merge the charts from each worksheet into a single list List <Chart> allChartsInWorkbook = new List <Chart>(); foreach (var sheet in sheets) { var chartsFromSheet = sheet.Charts; // The GetChartImage method requires a clean charts URL, that is, no $select option. string cleanFullChartsUrl = GraphApiHelper.GetChartsUrl(id, sheet.Id, null); foreach (var chart in chartsFromSheet) { // string singleChartImageUrl = GraphApiHelper.GetSingleChartImageUrl(cleanFullChartsUrl, chart.Id); chart.ImageAsBase64String = await GraphApiHelper.GetChartImage(cleanFullChartsUrl, chart.Id, token.AccessToken); } allChartsInWorkbook = allChartsInWorkbook.Concat(chartsFromSheet).ToList(); } return(View(allChartsInWorkbook)); }
public void GraphApiHelper_ThrowsArgumentNullException() { var logger = new Mock <ILogger <GraphApiHelper> >().Object; var memoryCache = new Mock <IMemoryCache>().Object; constructor_ArgumentNullException = new GraphApiHelper(logger, memoryCache, null); }
// GET api/values public async Task <HttpResponseMessage> Get() { // OWIN middleware validated the audience, but the scope must also be validated. It must contain "access_as_user". string[] addinScopes = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/scope").Value.Split(' '); if (!(addinScopes.Contains("access_as_user"))) { return(HttpErrorHelper.SendErrorToClient(HttpStatusCode.Unauthorized, null, "Missing access_as_user.")); } // Assemble all the information that is needed to get a token for Microsoft Graph using the "on behalf of" flow. // Beginning with MSAL.NET 3.x.x, the bootstrapContext is just the bootstrap token itself. string bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext.ToString(); UserAssertion userAssertion = new UserAssertion(bootstrapContext); var cca = ConfidentialClientApplicationBuilder.Create(ConfigurationManager.AppSettings["ida:ClientID"]) .WithRedirectUri("https://localhost:44355") .WithClientSecret(ConfigurationManager.AppSettings["ida:Password"]) .WithAuthority(ConfigurationManager.AppSettings["ida:Authority"]) .Build(); // MSAL.NET adds the profile, offline_access, and openid scopes itself. It will throw an error if you add // them redundantly here. string[] graphScopes = { "https://graph.microsoft.com/Files.Read.All" }; // Get the access token for Microsoft Graph. AcquireTokenOnBehalfOfParameterBuilder parameterBuilder = null; AuthenticationResult authResult = null; try { parameterBuilder = cca.AcquireTokenOnBehalfOf(graphScopes, userAssertion); authResult = await parameterBuilder.ExecuteAsync(); } catch (MsalServiceException e) { // Handle request for multi-factor authentication. if (e.Message.StartsWith("AADSTS50076")) { string responseMessage = String.Format("{{\"AADError\":\"AADSTS50076\",\"Claims\":{0}}}", e.Claims); return(HttpErrorHelper.SendErrorToClient(HttpStatusCode.Forbidden, null, responseMessage)); // The client should recall the getAccessToken function and pass the claims string as the // authChallenge value in the function's Options parameter. } // Handle lack of consent (AADSTS65001) and invalid scope (permission). if ((e.Message.StartsWith("AADSTS65001")) || (e.Message.StartsWith("AADSTS70011: The provided value for the input parameter 'scope' is not valid."))) { return(HttpErrorHelper.SendErrorToClient(HttpStatusCode.Forbidden, e, null)); } // Handle all other MsalServiceExceptions. else { throw e; } } return(await GraphApiHelper.GetOneDriveFileNames(authResult.AccessToken)); }
// GET api/values public async Task <IEnumerable <string> > Get() { // OWIN middleware validated the audience and issuer, but the scope must also be validated; must contain "access_as_user". string[] addinScopes = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/scope").Value.Split(' '); if (addinScopes.Contains("access_as_user")) { // Get the raw token that the add-in page received from the Office host. var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as BootstrapContext; UserAssertion userAssertion = new UserAssertion(bootstrapContext.Token); // Get the access token for MS Graph. ClientCredential clientCred = new ClientCredential(ConfigurationManager.AppSettings["ida:Password"]); ConfidentialClientApplication cca = new ConfidentialClientApplication(ConfigurationManager.AppSettings["ida:ClientID"], "https://localhost:44355", clientCred, null, null); string[] graphScopes = { "Files.Read.All" }; AuthenticationResult result = null; try { // The AcquireTokenOnBehalfOfAsync method will first look in the MSAL in memory cache for a // matching access token. Only if there isn't one, does it initiate the "on behalf of" flow // with the Azure AD V2 endpoint. result = await cca.AcquireTokenOnBehalfOfAsync(graphScopes, userAssertion, "https://login.microsoftonline.com/common/oauth2/v2.0"); } catch (MsalUiRequiredException e) { // If multi-factor authentication is required by the MS Graph resource an // the user has not yet provided it, AAD will throw an exception containing a // Claims property. if (String.IsNullOrEmpty(e.Claims)) { throw e; } else { // The Claims property value must be passed to the client which will pass it // to the Office host, which will then include it in a request for a new token. // AAD will prompt the user for all required forms of authentication. throw new HttpException(e.Claims); } } // Get the names of files and folders in OneDrive for Business by using the Microsoft Graph API. Select only properties needed. var fullOneDriveItemsUrl = GraphApiHelper.GetOneDriveItemNamesUrl("?$select=name&$top=3"); var getFilesResult = await ODataHelper.GetItems <OneDriveItem>(fullOneDriveItemsUrl, result.AccessToken); // The returned JSON includes OData metadata and eTags that the add-in does not use. // Return to the client-side only the filenames. List <string> itemNames = new List <string>(); foreach (OneDriveItem item in getFilesResult) { itemNames.Add(item.Name); } return(itemNames); } return(new string[] { "Error", "Microsoft Office does not have permission to get Microsoft Graph data on behalf of the current user." }); }
/// <summary> /// Gets emails with conversation id /// </summary> /// <returns>Emails with the specific conversation id.</returns> public async Task <JsonResult> ConversationMessages(string convoId) { // Get access token var token = Data.GetUserSessionToken(Settings.GetUserAuthStateId(ControllerContext.HttpContext), Settings.AzureADAuthority); var messages = await GraphApiHelper.getConversationIdMessages(token.AccessToken, convoId); return(Json(messages, JsonRequestBehavior.AllowGet)); }
public async Task <dynamic> deleteEmailAttachments(string[] attachmentIds, string emailId, string[] attachmentUrls) { // Get access token var token = Data.GetUserSessionToken(Settings.GetUserAuthStateId(ControllerContext.HttpContext), Settings.AzureADAuthority); var attachments = await GraphApiHelper.deleteEmailAttachments(token.AccessToken, attachmentIds, emailId, attachmentUrls); return(Json(attachments, JsonRequestBehavior.AllowGet)); }
private async Task LoadAndShowOneDriveItemsAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken) { var tokenState = (TokenState)stepContext.Values["accessToken"]; var oneDriveItems = await GraphApiHelper.GetOneDriveFilesListAsync(tokenState, cancellationToken); await Cards.ShowActivityWithAttachmentsAsync( stepContext.Context, Cards.BuildOneDriveAttachmentList(oneDriveItems), cancellationToken : cancellationToken); }
/// <summary> /// Retrieves all messages in a specific user mailFolder /// </summary> public async Task <Microsoft.Graph.IMailFolderMessagesCollectionPage> getMailFolderMessages(string folderId, string requestUri, string callbackToken) { // Get access token var token = Data.GetUserSessionToken(Settings.GetUserAuthStateId(ControllerContext.HttpContext), Settings.AzureADAuthority); // TODO: Comes in chunks of 10, need to iterate through all... var messages = await GraphApiHelper.getMailFolderMessages(folderId, token.AccessToken, requestUri, callbackToken); return(messages); }
/// <summary> /// Recursively searches OneDrive for Business. /// </summary> /// <returns>A view listing the workbooks in OneDrive for Business.</returns> public async Task <ActionResult> OneDriveFiles() { // Get access token var token = Data.GetUserSessionToken(Settings.GetUserAuthStateId(ControllerContext.HttpContext), Settings.AzureADAuthority); // Get all the Excel files in OneDrive for Business by using the Microsoft Graph API. Select only properties needed. var fullWorkbooksSearchUrl = GraphApiHelper.GetWorkbookSearchUrl("?$select=name,id"); var getFilesResult = await ODataHelper.GetItems <ExcelWorkbook>(fullWorkbooksSearchUrl, token.AccessToken); return(View(getFilesResult)); }
/// <summary> /// Adds hyperlink to OneDrive in email's body if attachments present. /// </summary> /// <returns> Email's new body with hyperlink </returns> public async Task <string> addAttachmentsToBody(string attachmentsLocation, string emailId, string accessToken) { // Get access token // Was commented out var token = Data.GetUserSessionToken(Settings.GetUserAuthStateId(ControllerContext.HttpContext), Settings.AzureADAuthority); string body = await GraphApiHelper.getMessageBody(token.AccessToken, emailId); // For embedded images, remove <img > before adding links string img; string hyperlink; bool oneLinkAvailable = true; do { img = Format.getBetween(body, "<img ", ">"); // Replace with hyperlink to attachment string delete = "<img " + img + ">"; hyperlink = "<a href=\"" + attachmentsLocation + "\"> View Attachments </a>"; // Replace first <img > with hyperlink if (oneLinkAvailable && img != "") { body = body.Replace(delete, hyperlink); oneLinkAvailable = false; } else { body = body.Replace(delete, ""); } } while (img != ""); string pageBreak = "<div style=\"font - family:Calibri,Arial,Helvetica,sans - serif; font - size:12pt; color: rgb(0, 0, 0)\"><br></div>"; // If no embedded attachments, add a link at the beginning of the email if (oneLinkAvailable) { string oldBody = Format.getBetween(body, "<body dir=\"ltr\">", "</body>"); string newBody = hyperlink + pageBreak + oldBody; oldBody = "<body dir=\"ltr\">" + oldBody + "</body>"; newBody = "<body dir=\"ltr\">" + newBody + "</body>"; body = body.Replace(oldBody, newBody); } return(body); }
/// <summary> /// Recursively searches OneDrive for Business. /// </summary> /// <returns>The names of the first three workbooks in OneDrive for Business.</returns> public async Task <JsonResult> OneDriveFiles() { // Get access token var token = Data.GetUserSessionToken(Settings.GetUserAuthStateId(ControllerContext.HttpContext), Settings.AzureADAuthority); // Get all the Excel files in OneDrive for Business by using the Microsoft Graph API. Select only properties needed. var fullWorkbooksSearchUrl = GraphApiHelper.GetWorkbookSearchUrl("?$select=name,id&top=3"); var filesResult = await ODataHelper.GetItems <ExcelWorkbook>(fullWorkbooksSearchUrl, token.AccessToken); List <string> fileNames = new List <string>(); foreach (ExcelWorkbook workbook in filesResult) { fileNames.Add(workbook.Name); } return(Json(fileNames, JsonRequestBehavior.AllowGet)); }
public async Task <GraphApiResponseDTO> GetAuthorizedUserDetails(string token) { GraphApiResponseDTO graphApiResponse = new GraphApiResponseDTO(); var client = new GraphApiHelper(token); string url = "https://graph.microsoft.com/v1.0/me"; using HttpResponseMessage response = await client.ApiClient.GetAsync(url); if (response.IsSuccessStatusCode) { ExternalLoginSsoDTO user = await JsonSerializer.DeserializeAsync <ExternalLoginSsoDTO>(await response.Content.ReadAsStreamAsync()); graphApiResponse.externalLoginUser = user; return(graphApiResponse); } else { graphApiResponse.Error = response.ReasonPhrase; return(graphApiResponse); } }
void worker_DoWork(object sender, DoWorkEventArgs e) { try { Console.WriteLine("Running sync"); BackgroundWorker w = (BackgroundWorker)sender; var token = Tokens.getApplicationToken(); using (TrimHelper trimHelper = new TrimHelper()) { foreach (var doc in trimHelper.GetDeleteableDocuments()) { Console.WriteLine($"rec: {doc.Id}"); try { if (!string.IsNullOrWhiteSpace(doc.Id)) { var fileResult = ODataHelper.GetItem <OneDriveItem>(GraphApiHelper.GetOneDriveItemIdUrl(doc.Id), token, null); fileResult.Wait(); var item = fileResult.Result; var isLocked = ODataHelper.IsLocked(GraphApiHelper.GetOneDriveItemIdUrlForDelete(doc.Id), item.Name, token); isLocked.Wait(); if (isLocked.Result == true) { Console.WriteLine("Item is locked will try again later"); } else { var modified = doc.DateModified.ToUniversalTime(); if (item.LastModifiedDateTime > modified) { trimHelper.CheckinFromDrive(doc, token); } StringContent content = new StringContent($"[TrimLink]{Environment.NewLine}Uri={doc.Uri}", Encoding.UTF8, "text/plain"); string url = GraphApiHelper.GetOneDriveFileUploadUrlFromId(item.ParentReference.DriveId, item.ParentReference.Id, doc.LinkFileName); // delete original file var deleteResult = ODataHelper.DeleteWithToken(GraphApiHelper.GetOneDriveItemIdUrlForDelete(doc.Id), token); deleteResult.Wait(); // Create link in Drive var uploadResult = ODataHelper.SendRequestWithAccessToken(url, token, content, method: HttpMethod.Put); uploadResult.Wait(); trimHelper.ClearDriveId(doc); trimHelper.ResetDeleteNow(doc); Console.WriteLine(fileResult.Result.ParentReference.Id); } } else { trimHelper.ResetDeleteNow(doc); } } catch (Exception ex) { if (ex.InnerException != null) { Console.WriteLine(ex.InnerException.Message); Console.WriteLine(ex.InnerException.StackTrace); } else { Console.WriteLine(ex.Message); Console.WriteLine(ex.StackTrace); } } } } // while (/*condition*/) // { //check if cancellation was requested if (w.CancellationPending) { //take any necessary action upon cancelling (rollback, etc.) //notify the RunWorkerCompleted event handler //that the operation was cancelled e.Cancel = true; return; } } catch (Exception ex) { Console.WriteLine(ex.Message); Console.WriteLine(ex.StackTrace); } //report progress; this method has an overload which can also take //custom object (usually representing state) as an argument // w.ReportProgress(/*percentage*/); //do whatever You want the background thread to do... //} }
public async Task <Document> GetDocument(string token) { if (_recordUri < 1) { throw new ApplicationException("Invalid Uri"); } if (_database == null || !_database.IsConnected) { throw new ApplicationException("Invalid database"); } var response = new Document() { UserHasAccess = true }; var record = new Record(_database, _recordUri); string driveId = record.GetDriveId(); if (!string.IsNullOrWhiteSpace(driveId)) { OneDriveItem fileResult = null; try { fileResult = await ODataHelper.GetItem <OneDriveItem>(GraphApiHelper.GetOneDriveItemIdUrl(driveId), token, null); } catch (Exception ex) { response.UserHasAccess = false; } if (response.UserHasAccess == false) { token = Tokens.getApplicationToken(); fileResult = await ODataHelper.GetItem <OneDriveItem>(GraphApiHelper.GetOneDriveItemIdUrl(driveId), token, null); } response.WebUrl = fileResult.WebUrl; response.WebDavUrl = fileResult.WebDavUrl; response.MimeType = fileResult.File.MimeType; } else if (record.IsElectronic) { try { string folderId = string.Empty; var documentFolder = await ODataHelper.PostFolder <OneDriveItem>(GraphApiHelper.GetOneDriveChildrenUrl(), token); folderId = documentFolder.Id; if (!record.IsDocumentInClientCache) { record.LoadDocumentIntoClientCache(); } string fileName = record.GetFileName(); var uploadedFile = await doUpload(record.DocumentPathInClientCache, fileName, token); bool checkout = true; if (record.IsCheckedOut && record.CheckedOutTo.Uri == _database.CurrentUser.Uri) { checkout = false; } record.GetDocument(null, checkout, null, uploadedFile.ParentReference.DriveId + "/items/" + uploadedFile.Id); record.SetDriveId(uploadedFile.ParentReference.DriveId + "/items/" + uploadedFile.Id); // uploadedFile. fileItem.getDriveAndId(); record.Save(); response.WebUrl = uploadedFile.WebUrl; response.WebDavUrl = uploadedFile.WebDavUrl; } catch { try { record.UndoCheckout(null); } catch { } // return new Error throw; } } else { throw new Exception("Record is not a valid document."); } return(response); }
private async Task <Microsoft.Graph.DriveItem> doUpload(string filePath, string fileName, string token) { var graphServiceClient = getClient(token); string userFolder = Path.Combine("ForUser", _database.CurrentUser.Uri.ToString()); string fullUserFolder = Path.Combine(_uploadBasePath, userFolder); //string fileName = $"{Guid.NewGuid()}.docx"; if (!System.IO.Directory.Exists(fullUserFolder)) { System.IO.Directory.CreateDirectory(fullUserFolder); } string tempPath = Path.Combine(fullUserFolder, Path.GetFileName(filePath)); System.IO.File.Copy(filePath, tempPath, true); FileInfo fileInfo = new FileInfo(tempPath); fileInfo.IsReadOnly = false; autoOpen(tempPath); // autoOpen(tempPath); using (var file = System.IO.File.OpenRead(tempPath)) { var documentFolder = await ODataHelper.PostFolder <OneDriveItem>(GraphApiHelper.GetOneDriveChildrenUrl(), token); var uploadSession = await graphServiceClient.Drives[documentFolder.ParentReference.DriveId].Items[documentFolder.Id].ItemWithPath(fileName).CreateUploadSession().Request().PostAsync(); string ul = uploadSession.UploadUrl += "&$select=Id,ParentReference,WebUrl,WebDavUrl"; var maxChunkSize = (320 * 1024) * 10; // 5000 KB - Change this to your chunk size. 5MB is the default. var provider = new ChunkedUploadProvider(uploadSession, graphServiceClient, file, maxChunkSize); try { // Setup the chunk request necessities var chunkRequests = provider.GetUploadChunkRequests(); var readBuffer = new byte[maxChunkSize]; var trackedExceptions = new List <Exception>(); DriveItem itemResult = null; //upload the chunks foreach (var request in chunkRequests) { // Do your updates here: update progress bar, etc. // ... // Send chunk request var result = await provider.GetChunkRequestResponseAsync(request, readBuffer, trackedExceptions); if (result.UploadSucceeded) { itemResult = result.ItemResponse; } } // Check that upload succeeded if (itemResult != null) { return(itemResult); } } catch { await provider.DeleteSession(); throw; } } System.IO.File.Delete(tempPath); throw new ApplicationException("Upload failed."); }
// GET api/files public async Task <HttpResponseMessage> Get() { string accessToken = Request.Headers.Authorization.ToString().Split(' ')[1]; return(await GraphApiHelper.GetOneDriveFileNames(accessToken)); }
// GET api/values public async Task <HttpResponseMessage> Get() { Dictionary <string, string> errorObj = new Dictionary <string, string>(); // OWIN middleware validated the audience and issuer, but the scope must also be validated; must contain "access_as_user". string[] addinScopes = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/scope").Value.Split(' '); if (addinScopes.Contains("access_as_user")) { // Get the raw token that the add-in page received from the Office host. var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as BootstrapContext; // Get the access token for MS Graph. string[] graphScopes = { "Files.Read.All" }; GraphToken result = null; try { // The AcquireTokenOnBehalfOfAsync method will initiate the "on behalf of" flow // with the Azure AD V2 endpoint. result = await GraphTokenHelper.AcquireTokenOnBehalfOfAsync(bootstrapContext.Token, graphScopes); } catch (GraphTokenException e) { // Even we have the access token from Office, it's possible the Azure AD fails the request. // Potential reasons could be: 1) Require MFA, 2) Missing consent // Why missing consent could happen? // To get an access token from Office client, user only needs to grant the "access_as_user" scope. // It doesnt mean user has granted all the required scopes. // If the required scopes have updated (for example, the developer change the scopes in http://apps.dev.microsoft.com/, // user has to consent again for those new scopes. // Therefore, the server should send the error to the client and let the client to handle it. errorObj["claims"] = e.Claims; errorObj["message"] = e.Message; errorObj["errorCode"] = e.ErrorCode; errorObj["suberror"] = e.SubError; return(SendErrorToClient(HttpStatusCode.Unauthorized, errorObj)); } catch (Exception e) { errorObj["errorCode"] = "unknown_error"; errorObj["message"] = e.Message; return(SendErrorToClient(HttpStatusCode.InternalServerError, errorObj)); } // Get the names of files and folders in OneDrive for Business by using the Microsoft Graph API. Select only properties needed. var fullOneDriveItemsUrl = GraphApiHelper.GetOneDriveItemNamesUrl("?$select=name&$top=3"); IEnumerable <OneDriveItem> filesResult; try { filesResult = await ODataHelper.GetItems <OneDriveItem>(fullOneDriveItemsUrl, result.AccessToken); } // If the token is invalid, MS Graph sends a "401 Unauthorized" error with the code // "InvalidAuthenticationToken". ASP.NET then throws a RuntimeBinderException. This // is also what happens when the token is expired, although MSAL should prevent that // from ever happening. In either case, the client should start the process over by // re-calling getAccessTokenAsync. catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException e) { errorObj["errorCode"] = "invalid_graph_token"; errorObj["message"] = e.Message; return(SendErrorToClient(HttpStatusCode.Unauthorized, errorObj)); } // The returned JSON includes OData metadata and eTags that the add-in does not use. // Return to the client-side only the filenames. List <string> itemNames = new List <string>(); foreach (OneDriveItem item in filesResult) { itemNames.Add(item.Name); } var requestMessage = new HttpRequestMessage(); requestMessage.SetConfiguration(new HttpConfiguration()); var response = requestMessage.CreateResponse <List <string> >(HttpStatusCode.OK, itemNames); return(response); } else { // The token from the client does not have "access_as_user" permission. errorObj["errorCode"] = "invalid_access_token"; errorObj["message"] = "Missing access_as_user. Microsoft Office does not have permission to get Microsoft Graph data on behalf of the current user."; return(SendErrorToClient(HttpStatusCode.Unauthorized, errorObj)); } }
public async Task <string> SaveAttachmentOneDrive([FromBody] SaveAttachmentRequest request) { if (request == null || !request.IsValid() || request.attachmentIds.Length == 0) { return(null); } string attachmentsUrl = null; using (var client = new HttpClient()) { // Get content bytes string baseAttachmentUri = request.outlookRestUrl; if (!baseAttachmentUri.EndsWith("/")) { baseAttachmentUri += "/"; } baseAttachmentUri += "v2.0/me/messages/" + request.messageId + "/attachments/"; var i = 0; foreach (string attachmentId in request.attachmentIds) { var getAttachmentReq = new HttpRequestMessage(HttpMethod.Get, baseAttachmentUri + attachmentId); // Headers getAttachmentReq.Headers.Authorization = new AuthenticationHeaderValue("Bearer", request.outlookToken); getAttachmentReq.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); var result = await client.SendAsync(getAttachmentReq); string json = await result.Content.ReadAsStringAsync(); OutlookAttachment attachment = JsonConvert.DeserializeObject <OutlookAttachment>(json); // For files, build a stream directly from ContentBytes if (attachment.Size < (4 * 1024 * 1024)) { MemoryStream fileStream = new MemoryStream(Convert.FromBase64String(attachment.ContentBytes)); // Get access token from SQL database var token = Data.GetUserSessionToken(Settings.GetUserAuthStateId(ControllerContext.HttpContext), Settings.AzureADAuthority); // TODO: Check if the file already exists attachmentsUrl = await GraphApiHelper.saveAttachmentOneDrive(token.AccessToken, Format.MakeFileNameValid(request.filenames[i]), fileStream, Format.MakeFileNameValid(request.subject)); // Format string delete = "/" + request.filenames[i]; attachmentsUrl = attachmentsUrl.Replace(delete, ""); i++; } else { // Functionality to support > 4 MB files // See https://docs.microsoft.com/en-us/graph/api/driveitem-createuploadsession?view=graph-rest-1.0 var token = Data.GetUserSessionToken(Settings.GetUserAuthStateId(ControllerContext.HttpContext), Settings.AzureADAuthority); DriveItem folder = await GraphApiHelper.searchFileOneDrive(token.AccessToken, "Outlook Attachments"); var url = "https://graph.microsoft.com/v1.0" + $"/me/drive/items/{folder.Id}:/{attachment.Name}:/createUploadSession"; var uploadReq = new HttpRequestMessage(HttpMethod.Post, url); // Headers uploadReq.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.AccessToken); // Send request var sessionResponse = client.SendAsync(uploadReq).Result.Content.ReadAsStringAsync().Result; var uploadSession = JsonConvert.DeserializeObject <UploadSessionResponse>(sessionResponse); Upload upload = new Upload(); HttpResponseMessage response = upload.UploadFileBySession(uploadSession.uploadUrl, Convert.FromBase64String(attachment.ContentBytes)); return(folder.WebUrl); } } return(attachmentsUrl); } }
// GET api/values public async Task <HttpResponseMessage> Get() { // OWIN middleware validated the audience and issuer, but the scope must also be validated; must contain "access_as_user". string[] addinScopes = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/scope").Value.Split(' '); if (addinScopes.Contains("access_as_user")) { // Get the raw token that the add-in page received from the Office host. var bootstrapContext = ClaimsPrincipal.Current.Identities.First().BootstrapContext as BootstrapContext; UserAssertion userAssertion = new UserAssertion(bootstrapContext.Token); // Get the access token for MS Graph. ClientCredential clientCred = new ClientCredential(ConfigurationManager.AppSettings["ida:Password"]); ConfidentialClientApplication cca = new ConfidentialClientApplication(ConfigurationManager.AppSettings["ida:ClientID"], "https://localhost:44355", clientCred, null, null); string[] graphScopes = { "Files.Read.All" }; AuthenticationResult result = null; try { // The AcquireTokenOnBehalfOfAsync method will first look in the MSAL in memory cache for a // matching access token. Only if there isn't one, does it initiate the "on behalf of" flow // with the Azure AD V2 endpoint. result = await cca.AcquireTokenOnBehalfOfAsync(graphScopes, userAssertion, "https://login.microsoftonline.com/common/oauth2/v2.0"); } catch (MsalServiceException e) { // If multi-factor authentication is required by the MS Graph resource and the user // has not yet provided it, AAD will return "400 Bad Request" with error AADSTS50076 // and a Claims property. MSAL throws a MsalUiRequiredException (which inherits // from MsalServiceException) with this information. The Claims property value must // be passed to the client which should pass it to the Office host, which then // includes it in a request for a new token. AAD will prompt the user for all // required forms of authentication. if (e.Message.StartsWith("AADSTS50076")) { // The APIs that create HTTP Responses from exceptions don't know about the // Claims property, so they don't include it. We have to manually create a message // that includes it. A custom Message property, however, blocks the creation of an // ExceptionMessage property, so the only way to get the error AADSTS50076 to the // client is to add it to the custom Message. JavaScript in the client will need // to discover if a response has a Message or ExceptionMessage, so it knows which // to read. string responseMessage = String.Format("{{\"AADError\":\"AADSTS50076\",\"Claims\":{0}}}", e.Claims); return(SendErrorToClient(HttpStatusCode.Forbidden, null, responseMessage)); } // If the call to AAD contained at least one scope (permission) for which neither // the user nor a tenant administrator has consented (or consent was revoked. // AAD will return "400 Bad Request" with error AADSTS65001. MSAL throws a // MsalUiRequiredException with this information. The client should re-call // getAccessTokenAsync with the option { forceConsent: true }. if ((e.Message.StartsWith("AADSTS65001")) // If the call to AAD contained at least one scope that AAD does not recognize, // AAD returns "400 Bad Request" with error AADSTS70011. MSAL throws a // MsalUiRequiredException (which inherits from MsalServiceException) with this // information. The client should inform the user. || (e.Message.StartsWith("AADSTS70011: The provided value for the input parameter 'scope' is not valid."))) { return(SendErrorToClient(HttpStatusCode.Forbidden, e, null)); } else { // Rethrowing the MsalServiceException will not relay the original // "400 Bad Request" exception to the client. Instead a "500 Server Error" // is sent. throw e; } } // Get the names of files and folders in OneDrive for Business by using the Microsoft Graph API. Select only properties needed. // Note that the parameter is hardcoded. If you reuse this code in a production add-in and any part of the query parameter comes // from user input, be sure that it is sanitized so that it cannot be used in a Response header injection attack. var fullOneDriveItemsUrl = GraphApiHelper.GetOneDriveItemNamesUrl("?$select=name&$top=3"); IEnumerable <OneDriveItem> filesResult; try { filesResult = await ODataHelper.GetItems <OneDriveItem>(fullOneDriveItemsUrl, result.AccessToken); } // If the token is invalid, MS Graph sends a "401 Unauthorized" error with the code // "InvalidAuthenticationToken". ASP.NET then throws a RuntimeBinderException. This // is also what happens when the token is expired, although MSAL should prevent that // from ever happening. In either case, the client should start the process over by // re-calling getAccessTokenAsync. catch (Microsoft.CSharp.RuntimeBinder.RuntimeBinderException e) { return(SendErrorToClient(HttpStatusCode.Unauthorized, e, null)); } // The returned JSON includes OData metadata and eTags that the add-in does not use. // Return to the client-side only the filenames. List <string> itemNames = new List <string>(); foreach (OneDriveItem item in filesResult) { itemNames.Add(item.Name); } var requestMessage = new HttpRequestMessage(); requestMessage.SetConfiguration(new HttpConfiguration()); var response = requestMessage.CreateResponse <List <string> >(HttpStatusCode.OK, itemNames); return(response); } // The token from the client does not have "access_as_user" permission. return(SendErrorToClient(HttpStatusCode.Unauthorized, null, "Missing access_as_user.")); }