I am inserting calendar events into our domain users O365 calendars with Microsoft Graph API. I need to determine if the event exists, but my research has only shown how to use the GraphClient.Me.Events scenario to search. I don't believe this would work as we have global access to all calendars (Calendars.ReadWrite) within our domain.
Is there any way to search for the event in the applicable domain users calendar before sync?
var scopes = new string[] { "https://graph.microsoft.com/.default" };
var confidentialClient = ConfidentialClientApplicationBuilder.Create(clientId).WithTenantId(tenantId).WithClientSecret(clientSecret).Build();
var authResult = await confidentialClient.AcquireTokenForClient(scopes).ExecuteAsync();
using (HttpClient c = new HttpClient())
{
string url = "https://graph.microsoft.com/v1.0/users/" + userEmail + " /calendar/events";
ToOutlookCalendar createOutlookEvent = CreateEvent();
HttpContent httpContent = new StringContent(JsonConvert.SerializeObject(createOutlookEvent), Encoding.UTF8, "application/json");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url);
request.Content = httpContent;
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
var response = await c.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
}
The calendar event is currently very simple for testing
public static ToOutlookCalendar CreateEvent()
{
ToOutlookCalendar outlookEvent = new ToOutlookCalendar
{
Subject = "Code test migration appt",
Body = new Body
{
ContentType = "HTML",
Content = "Testing API with application client authorization"
},
Start = new End
{
DateTime = "2020-06-22T12:30:00",TimeZone = System.TimeZone.CurrentTimeZone.StandardName
},
End = new End
{
DateTime = "2020-06-22T14:00:00",TimeZone = System.TimeZone.CurrentTimeZone.StandardName
},
Location = new LocationName
{
DisplayName = "Sample Location"
}
};
return outlookEvent;
}
Assuming you're targeting the default calendar for the user, yes.
the /me path segment is an alias to the upn or userId, so something like:
"https://graph.microsoft.com/v1.0/users/" + userEmail + "/calendar/events?$filter=subject eq '" + knownTitle + "'"
Should work just fine if you're using an app only token with sufficent permissions
Related
We have an API that utilizes a service account (work account type - not a personal account) to do the following:
Using our Service Account, Gets a Token from our Azure AD with scopes: ["user.read", "User.Read.All", "Files.ReadWrite.All", "ChatMessage.Send", "Chat.Create", "Chat.ReadWrite"]
Use that token to upload a file to our SharePoint. -> This succeeds
Use that token to Get the UserID from email (UserPrincipalNames map in our AD, so this is not a concern) -> This succeeds
Use that token to Create a new chat/Get the existing chat between the user & our service account (using POST /v1.0/chats) -> This returns 401 Unauthorized
Send the message from service account to user (using POST /v1.0/chats/{chatID returned from step 4}/messages)
The users we are attemping to send these messages to are all in our Azure AD.
We are unsure how the previous requests with this token succeed, and the Create/Get Chat fails with a 401. We have confirmed that the token is correctly being set in the authorization header (we are using the exact same process as the previous requests).
Also, we have these API Permissions set in our Azure AD for this application:
If we use jwt.io to examine the token, we do see the scope is set in the token being set in the Authorization header of the request.
Additionally the audience is set to Graph API:
Here is our code:
using Microsoft.Graph;
using Newtonsoft.Json;
using OurAPI.Helpers.IHelpers;
using OurAPI.Models;
using OurAPI.Repositories.IRepositories;
using System.Text;
namespace OurAPI.Repositories
{
public class TeamsRepository : ITeamsRepository
{
private readonly IConfiguration _config;
private readonly ITokenHelper _tokenHelper;
private readonly IHttpClientFactory _httpClientFactory;
public TeamsRepository(IConfiguration config, ITokenHelper tokenHelper, IHttpClientFactory httpClientFactory)
{
_config = config;
_tokenHelper = tokenHelper;
_httpClientFactory = httpClientFactory;
}
public async Task<object> GeneratePDFAndSendToTeams(TeamsPostRequest request)
{
// Authenticate
string token = await _tokenHelper.GetMicrosoftGraphAccessToken();
// Convert from Base64 to Memory Stream
var file = GeneratePDF(request.Base64);
// Upload PDF to SharePoint
var sharepointFile = await UploadFile(file, request, token);
// Get UserID from email
var user = await GetUser(request.User, token);
// Create a new chat with service account or Retrieve Chat if already exists
var chat = await GetChat(user.Id, token);
// Post to chat between service account and requesting user
var chatMessage = await SendToTeams(sharepointFile, chat, token);
return chat;
}
public async Task<object> GetChats(string token)
{
using var request = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/users/serviceaccount#domain.com/chats");
request.Headers.Add("authorization", "Bearer " + token);
request.Headers.Add("accept", "application/json");
var httpClient = _httpClientFactory.CreateClient();
var response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var contents = await response.Content.ReadAsStringAsync();
var chats = JsonConvert.DeserializeObject(contents);
return chats;
}
public async Task<User> GetUser(string userEmail, string token)
{
using var request = new HttpRequestMessage(HttpMethod.Get, "https://graph.microsoft.com/v1.0/users/" + userEmail);
request.Headers.Add("authorization", "Bearer " + token);
request.Headers.Add("accept", "application/json");
var httpClient = _httpClientFactory.CreateClient();
var response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var contents = await response.Content.ReadAsStringAsync();
var user = JsonConvert.DeserializeObject<User>(contents);
return user;
}
public async Task<Chat> GetChat(string userID, string token)
{
var chatRequest = new Models.ChatRequest(userID);
chatRequest.ChatType = "oneOnOne";
var chatMember = new ChatMember();
chatMember.Type = "#microsoft.graph.aadUserConversationMember";
chatMember.Roles = new List<string>() { "owner" };
chatMember.User = $"https://graph.microsoft.com/beta/users('{userID}')";
var serviceAccount = new ChatMember();
serviceAccount.Type = "#microsoft.graph.aadUserConversationMember";
serviceAccount.Roles = new List<string>() { "owner" };
serviceAccount.User = "https://graph.microsoft.com/beta/users('{OurServiceAccountID}')";
chatRequest.Members = new List<ChatMember> { chatMember, serviceAccount };
using var request = new HttpRequestMessage(HttpMethod.Post, "https://graph.microsoft.com/v1.0/chats");
request.Headers.Add("authorization", "Bearer " + token);
request.Headers.Add("accept", "application/json");
string content = JsonConvert.SerializeObject(chatRequest);
request.Content = new StringContent(content, Encoding.UTF8, "application/json");
var httpClient = _httpClientFactory.CreateClient();
var response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var contents = await response.Content.ReadAsStringAsync();
var chat = JsonConvert.DeserializeObject<Chat>(contents);
return chat;
}
// POST /chats/{chat-id}/messages
public async Task<ChatMessage> SendToTeams(DriveItem sharePointFile, Chat chat, string token)
{
var chatMessageRequest = new Models.ChatMessageRequest(sharePointFile);
using var request = new HttpRequestMessage(HttpMethod.Post, "https://graph.microsoft.com/v1.0/chats/" + chat.Id + "/messages");
request.Headers.Add("authorization", "Bearer " + token);
request.Headers.Add("accept", "application/json");
request.Content = new StringContent(JsonConvert.SerializeObject(chatMessageRequest), Encoding.UTF8, "application/json");
var httpClient = _httpClientFactory.CreateClient();
var response = await httpClient.SendAsync(request);
//response.EnsureSuccessStatusCode();
var contents = await response.Content.ReadAsStringAsync();
var chatMessageResponse = JsonConvert.DeserializeObject<ChatMessage>(contents);
return chatMessageResponse;
}
public async Task<DriveItem> UploadFile(MemoryStream file, TeamsPostRequest teamsPostRequest, string token)
{
string siteID = "{ourSiteID}";
string PTparentID = "{ourParentID}"
string custNameForFile = teamsPostRequest.CustomerName;
using var request = new HttpRequestMessage(HttpMethod.Put, _config["Graph:BaseUrl"] + "/sites/" + siteID + "/drive/items/" + PTparentID + ":/" + custNameForFile + teamsPostRequest.PickTicketNo + ".pdf:/content");
request.Headers.Add("authorization", "Bearer " + token);
request.Headers.Add("accept", "application/json");
request.Content = new StreamContent(file);
var httpClient = _httpClientFactory.CreateClient();
var response = await httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var contents = await response.Content.ReadAsStringAsync();
var driveItem = JsonConvert.DeserializeObject<DriveItem>(contents);
return driveItem;
}
private static MemoryStream GeneratePDF(string base64)
{
byte[] bytes = Convert.FromBase64String(base64);
MemoryStream ms = new(bytes);
return ms;
}
}
}
Here is the request we are making to get the token:
public async Task<string> GetMicrosoftGraphAccessToken()
{
string authority = "https://login.microsoftonline.com/{ourTenantID}/";
string[] scopes = new string[] { "user.read", "User.Read.All", "Files.ReadWrite.All", "ChatMessage.Send", "Chat.Create", "Chat.ReadWrite" };
IPublicClientApplication app = PublicClientApplicationBuilder.Create(_config["AzureAD:ClientId"])
.WithAuthority(authority)
.Build();
var securePassword = new SecureString();
foreach (char c in _config["MicrosoftGraph:Password"])
{
securePassword.AppendChar(c);
}
AuthenticationResult result = await app.AcquireTokenByUsernamePassword(scopes, _config["MicrosoftGraph:Username"], securePassword)
.ExecuteAsync();
return result.AccessToken;
}
We are at a lose for what could be going on here. Any ideas would be greatly appreciated!
It helps to see the details of the token.
Copy the token, paste it at https://jwt.ms and inspect the details.
You’re probably interested in the scopes.
I guess the account you’re using should be one of the participants of the chat, this cannot be used to create chats between other users.
https://learn.microsoft.com/en-us/graph/api/chat-post you seem to be doing the right request.
I have 2 projects one Web API that have a simple /Token api that returns a token for the logged in user
and the second project is .NET Core that will use the URL/Token method in the login form.
Here is the code on the method that is used to login in the second project
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
var requestBody = new { grant_type = "password", username = Input.Email, password = Input.Password };
var data = Newtonsoft.Json.JsonConvert.SerializeObject(requestBody);
using (var client = new HttpClient())
using (var req = new HttpRequestMessage(HttpMethod.Get, "http://localhost:49470/Token"))
{
req.Content = new StringContent("{\"grant_type\":\"password\",\"username\":" + Input.Email + ",\"password\":" + Input.Password + "}", Encoding.UTF8, "application/json");
using (var rep = await client.SendAsync(req))
{
rep.EnsureSuccessStatusCode();
var content = await rep.Content.ReadAsStringAsync();
}
}
}
}
Error i'm facing:
The problem that i'm facing i always get a 400 (Bad Request) Error, ps: i'm new in .netcore
I've solved it, the problem was the api\Token body didn't accept the structure of the json parsed string
Simply i've used
var response = client.PostAsync("http://localhost:49470/Token", new StringContent("grant_type=password&username=" + Input.Email + "&password=" + Input.Password, Encoding.UTF8, "application/json")).Result;
I'm using MS directline to integrate custom channel with a chatbot based on botframework.
I'm using below functions to generate token & start conversation, but none of them allows to set Context.Activity.From.Id, MembersAdded.FirstOrDefault().Id , nor Activity.Recipient.Id
GenerateTokenForNewConversationAsync()
StartConversationAsync()
I Know we can control the ID when we send the first user message through directline , but I want to control any of the above IDs before even sending a message from the user. I want to set a specific ID and be able to capture it on the BOT event OnTurnAsync and Activity of type ActivityTypes.ConversationUpdate .. what should I do ?
Regarding my comment, I decided I'd just provide how to do what you want with both packages. Again, if it isn't too much trouble to switch, I highly, highly recommend using Microsoft.Bot.Connector (newer and more frequently updated) over Microsoft.Bot.Connector.DirectLine (older, not updated in 2.5 years, and deprecated until/unless we open-source it Update: This isn't actually deprecated, yet. We're currently working on open-sourcing this, but it's a low-priority task).
Recommended: Microsoft.Bot.Connector
Create the conversation with To and From, all-in-one.
var userAccount = new ChannelAccount(toId,toName);
var botAccount = new ChannelAccount(fromId, fromName);
var connector = new ConnectorClient(new Uri(serviceUrl));
IMessageActivity message = Activity.CreateMessageActivity();
if (!string.IsNullOrEmpty(conversationId) && !string.IsNullOrEmpty(channelId))
{
message.ChannelId = channelId;
}
else
{
conversationId = (await connector.Conversations.CreateDirectConversationAsync( botAccount, userAccount)).Id;
}
message.From = botAccount;
message.Recipient = userAccount;
message.Conversation = new ConversationAccount(id: conversationId);
message.Text = "Hello, this is a notification";
message.Locale = "en-Us";
await connector.Conversations.SendToConversationAsync((Activity)message);
Credit
Not Recommended: Microsoft.Bot.Connector.DirectLine
This is kind of a hacky workaround, but basically, you create the conversation and then send a ConversationUpdate activity.
//server side, retrieve token from secret
string directLineSecret = ConfigurationManager.AppSettings["DirectLineSecret"];
HttpClient httpClient = new HttpClient();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post,$"https://directline.botframework.com/v3/directline/tokens/generate");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", directLineSecret);
var fromUser = $"dl_{Guid.NewGuid()}";
request.Content = new StringContent(
JsonConvert.SerializeObject(
new { User = new { Id = fromUser } }),
Encoding.UTF8,
"application/json");
var response = await httpClient.SendAsync(request);
DirectLineToken dlToken = null;
if (response.IsSuccessStatusCode)
{
var body = await response.Content.ReadAsStringAsync();
dlToken = JsonConvert.DeserializeObject<DirectLineToken>(body);
}
string token = dlToken.token;
//create DirectLineClient from token, client side
DirectLineClient client = new DirectLineClient(token);
var conversation = await client.Conversations.StartConversationAsync();
new System.Threading.Thread(async () => await ReadBotMessagesAsync(client, conversation.ConversationId)).Start();
//send conversationUpdate
var user = new ChannelAccount(fromUser);
await client.Conversations.PostActivityAsync(conversation.ConversationId,
new Activity
{
From = user,
Text = string.Empty,
Type = ActivityTypes.ConversationUpdate,
MembersAdded = new[] { user }
}
);
TimeSpan delayTime = TimeSpan.FromSeconds(dlToken.expires_in) - TimeSpan.FromMinutes(5);
Task.Factory.StartNew(async () =>
{
while (!_getTokenAsyncCancellation.IsCancellationRequested)
{
var t = await client.Tokens.RefreshTokenAsync().ConfigureAwait(false);
await Task.Delay(delayTime, _getTokenAsyncCancellation.Token).ConfigureAwait(false);
}
}).ConfigureAwait(false);
Credit
I am trying to call this code
string accessToken = #".."; //valid token with right scopes
public string EventsUrl = #"https://outlook.office.com/api/v2.0/me/events";
// generate body
var postBody = JsonBody(invite);
using (var client = new HttpClient())
{
using (var request = new HttpRequestMessage(HttpMethod.Post, EventsUrl))
{
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var content = new StringContent(postBody, Encoding.UTF8, "application/json");
request.Content = content;
var response = await client.SendAsync(request);
return (response.IsSuccessStatusCode);
}
}
The method that create JsonBody is,
public string JsonBody(User user, Session session){
var invite = new EventInvite
{
Attendees = new Attendee[1]
};
invite.Attendees[0] = new Attendee
{
Type = "Required",
EmailAddress = new Emailaddress { Name = user.GetName(), Address = user.GetEmail() }
};
invite.Start = new Start { DateTime = session.DateTime_Start };
invite.End = new End { DateTime = session.DateTime_Start.AddMinutes(15) };
invite.Subject = session.Name;
invite.Body = new Body { ContentType = "HTML", Content = $"Some Content" };
return JsonConvert.SerializeObject(eventInvite);
}
I am getting a Bad Request as response. Is there any alternative to build an Event? I want this code to be very thin as this is accessed in non UI based application
What is the best way to create Calendar Event?
The fix involves setting up the Timezone in the Start and End
string timeZone="Singapore Standard Time";
invite.Start = new Start { DateTime = session.DateTime_Start, TimeZone = timeZone };
invite.End = new End { DateTime = session.DateTime_Start.AddMinutes(60), TimeZone = timeZone };
var postBody = JsonConvert.SerializeObject(invite, Formatting.Indented);
I am trying to embed PowerBI dashboards into my customer MVC portal. My customers don't have AAD accounts, so they can't login to Live when they come to the website, they log into my MVC website with individual authority.
I have registered my App on PowerBI/AAD and have the ClientID and Secret. I make the call to AAD and get an Authorization Code which I then use to get an Athentication Token which the is returned successfully.
When ever I use the access token to get a dashboard it is continually rejected with a 403 Forbidden.
I have gone through all the samples from Microsoft, but they require a user login prompt. I have reviewed the ADAL2.0 code which refers to the AcquireToken Method, but this was deprecated in ADAL3 and replaced with AcquireTokenAsync which has different parameters and I am using this in my example below.
Here is the function to get the token:
protected AuthenticationResult GetAccessToken()
{
string pBiUser = Properties.Settings.Default.PowerBIUser;
string pBiPwd = Properties.Settings.Default.PowerBIPwd;
string pBiClientId = Properties.Settings.Default.PowerBIClientId;
string pBiSecret = Properties.Settings.Default.PowerBIClientSecret;
TokenCache TC = new TokenCache();
ClientCredential CC = new ClientCredential(pBiClientId,pBiSecret);
string AU = Properties.Settings.Default.PowerBIAuthority;
Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext authenticationContext
= new Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext(AU, TC);
AuthenticationResult result = authenticationContext.AcquireTokenAsync("https://analysis.windows.net/powerbi/api"
,CC).Result;
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the PowerBI token");
}
return result;
}
I then take the result token and call. The response receives the 403:
protected PBIDashboards GetDashboards(AuthenticationResult authResult)
{
PBIDashboards pbiDashboards = new PBIDashboards();
var baseAddress = new Uri("https://api.powerbi.com");
using (var httpClient = new System.Net.Http.HttpClient {BaseAddress = baseAddress})
{
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("authorization",
"Bearer " + authResult.AccessToken);
using (**var response** = httpClient.GetAsync("v1.0/myorg/dashboards").Result)
{
string responseData = response.Content.ReadAsStringAsync().Result;
//Deserialize JSON string
pbiDashboards = JsonConvert.DeserializeObject<PBIDashboards>(responseData);
if (pbiDashboards != null)
{
var gridViewDashboards = pbiDashboards.value.Select(dashboard => new
{
Id = dashboard.id,
DisplayName = dashboard.displayName,
EmbedUrl = dashboard.embedUrl
});
}
}
}
return pbiDashboards;
}
Based on the error message(403), the issue is relative to the permission.
And AFAIK the is no such permission we can use when we acquire the access token using the client credentials flow for the Power BI REST. You can refer the permission for the figure below:
To get the token for the Power BI REST without user interaction, we can use the Resource owner password credentials flow. And you can use the 3rd party library PowerBI.Api.Client which already implement this.
After a lot of research, you can make a direct AJAX call to get the token:
private async Task<string> GetAccessToken()
{
string pBiUser = Properties.Settings.Default.PowerBIUser;
string pBiPwd = Properties.Settings.Default.PowerBIPwd;
string pBiClientId = Properties.Settings.Default.PowerBIClientId;
string pBiSecret = Properties.Settings.Default.PowerBIClientSecret;
string pBITenant = Properties.Settings.Default.PowerBITenantId;
string tokenEndpointUri = "https://login.microsoftonline.com/"+pBITenant+"/oauth2/token";
var content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "password"),
new KeyValuePair<string, string>("username", pBiUser),
new KeyValuePair<string, string>("password", pBiPwd),
new KeyValuePair<string, string>("client_id", pBiClientId),
new KeyValuePair<string, string>("client_secret", pBiSecret),
new KeyValuePair<string, string>("resource", "https://analysis.windows.net/powerbi/api")
});
using (var client = new HttpClient())
{
HttpResponseMessage res = client.PostAsync(tokenEndpointUri, content).Result;
string json = await res.Content.ReadAsStringAsync();
AzureAdTokenResponse tokenRes = JsonConvert.DeserializeObject<AzureAdTokenResponse>(json);
return tokenRes.AccessToken;
}
}
Once you have the string AccessToken, you can then call the Dashboards request.
protected PBIDashboards GetDashboards(string token)
{
PBIDashboards pbiDashboards = new PBIDashboards();
var baseAddress = new Uri("https://api.powerbi.com");
using (var httpClient = new System.Net.Http.HttpClient {BaseAddress = baseAddress})
{
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("authorization",
"Bearer " + token);
using (var response = httpClient.GetAsync("v1.0/myorg/dashboards").Result)
{
string responseData = response.Content.ReadAsStringAsync().Result;
//Deserialize JSON string
pbiDashboards = JsonConvert.DeserializeObject<PBIDashboards>(responseData);
if (pbiDashboards != null)
{
var gridViewDashboards = pbiDashboards.value.Select(dashboard => new
{
Id = dashboard.id,
DisplayName = dashboard.displayName,
EmbedUrl = dashboard.embedUrl
});
}
}
}
return pbiDashboards;
}
This will provide you the list of dashboards and the dashboard Id to call the PowerBI API to build the embeded page in Javascript. I used hidden input fields to store the access token and embed URL to pass over to the Javascript call.
// check if the embed url was selected
var embedUrl = document.getElementById('embed').value;
if (embedUrl === "")
return;
// get the access token.
accessToken = document.getElementById('token').value;
// Embed configuration used to describe the what and how to embed.
// This object is used when calling powerbi.embed.
// You can find more information at https://github.com/Microsoft/PowerBI-JavaScript/wiki/Embed-Configuration-Details.
var config = {
type: 'dashboard',
accessToken: accessToken,
embedUrl: embedUrl
};
// Grab the reference to the div HTML element that will host the dashboard.
var dashboardContainer = document.getElementById('dashboard');
// Embed the dashboard and display it within the div container.
var dashboard = powerbi.embed(dashboardContainer, config);