How to create a event in MS Teams Calendar using MS Graph API from a backend service - api

I am a newbie in using Microsoft Graph API, I really interested to implement the graph API possibilities in my .Net Core application. I decided to create a sample application, that create meetings in MS Teams app. I have already done the steps listed below.
I register a new app in Azure Active Directory.
Assign 'Calendars.Read' and 'Calendars.ReadWrite' (Permission type - Application) permissions.
I know there are two types of authentication for permission. Delegated and Application.
Permissions
Code
try
{
var config = this.LoadAppSettings();
GraphServiceClient graphClient = GetAuthenticatedGraphClient(config);
var #event = new Event
{
Subject = "My event by ragesh",
Start = new DateTimeTimeZone
{
DateTime = "2020-06-11T07:44:21.358Z",
TimeZone = "UTC"
},
End = new DateTimeTimeZone
{
DateTime = "2020-06-18T07:44:21.358Z",
TimeZone = "UTC"
}
};
await graphClient.Me.Events
.Request()
.AddAsync(#event);
}
catch (Exception ex)
{
throw ex;
}
But when I execute my code to create events in graph API it shows an authentication error.
Error

When you use application permissions, you cannot use the /me API url segment, since there is no authenticated user. You must instead use /users/<user-id> in it's place.
You're using the .NET SDK, so that translates to you cannot use graphClient.Me, you must use graphClient.Users[userId].

Related

Adding a field to claim for Azure AD in ASP.NET Core 6

My MVC web application previously used cookie based authentication handled by identity framework and usernames and passwords stored in my db. However, I have since moved to Azure AD for authentication and it is working great however I use a field in my database for each user called InternalUserNumber and this is used in many stored procedures and table valued functions called from my web application's dapper queries. So on authenticating to Azure AD the user is redirected to the homepage which on load I was planning to run a sql query using the preferred_username (email address) against our database to get the UserID and I was going to store it as a session variable which I can use later in my code. However, this got me thinking I use to store the UserID as a claim in the cookie as follows:
new Claim(ClaimTypes.Name, user.InternalUserNumber.ToString())
Is this still doable using the Azure AD cookie which is generated after logging in? I do not have access to the admin panel in Azure AD so this has to be all code based. Or is my only option storing this in the session as a variable and if so whats the best route for that? I would note i do plan to implement SignalR which I read you cannot use session if you are going to use SignalR so theres that potential issue for that route.
Thanks
Edit :
I have actually added some code which uses an event passed in program.cs. I was trying to implement a service to do the following code but I failed to be able to figure it out but instead put directly inside the event code as shown below. My issue is I cannot access the preferred_username in HttpContext object as it returns null? I am able to use HttpContext in my controllers so why am I not able to use it here in Program.CS during this event being triggered?
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>
{
builder.Configuration.Bind("AzureAd", options);
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = context =>
{
//var service = context.HttpContext.RequestServices.GetRequiredService<ISomeService>();
//var result = await service.DoSomethingAsync();
string query = "select dbo.A2F_0013_ReturnUserIDForEmail(#Email) as UserID";
string connectionString = builder.Configuration.GetValue<string>("ConnectionStrings:DBContext");
string signInEmailAddress = context.HttpContext.User.FindFirstValue("preferred_username");
using (var connection = new SqlConnection(connectionString))
{
var queryResult = connection.QueryFirst(query, new { Email = signInEmailAddress });
// add claims
var claims = new List<Claim>
{
new Claim("UserID", queryResult.UserID.ToString())
};
var appIdentity = new ClaimsIdentity(claims);
context.Principal.AddIdentity(appIdentity);
}
return Task.CompletedTask;
},
};
})
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();

Graph API Call Issues - POST Event

I'm running into issues when trying to create an event in a specific user calendar.
This call works fine: POST https://graph.microsoft.com/v1.0/me/events
But when I change the API Call to include the other user details, it throws this error: "The specified object was not found in the store."
I have created an app on Azure and assigned all necessary permissions.
App Permissions
Error:
Can someone please assist if I'm missing something?
Please note when you use /me, it means you are calling the ms graph api with a delegate api permission which is authentiated by entering user name/password, you can only do operations on your own account with this kind of authentication. While you want to do operations for other users like /users/user_id/xxx, you required the application api permission. That's why api document showed api permission in Delegated and Application. One for personal and another for all users.
When we need to get access token contain application permission, we need to use client credential flow. This flow is used for daemon application since this kind of application doesn't have user interactive operation, so we can only use application permission for this kind of scenario. And as you can see it will offer "very big ability" to the application(allow application to create/change/delete items for any user in your tenant), so we need to use appliation permission with caution.
Come back to the case, you can follow this section to generate access token and call the api. You can also using graph SDK in your code to call that api.
using Azure.Identity;
using Microsoft.Graph;
public async Task<string> testAsync() {
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "tenant_name.onmicrosoft.com";
var clientId = "azure_ad_clientid";
var clientSecret = "client_secret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var a = await graphClient.Users["user_id"].Request().GetAsync();
return a.DisplayName;
}

How to use YouTube Data API

I tried using YouTube Data API.
I really took a good look at everything I found on the internet. The code itself isn't the problem, but I did not find out, where to use this code. Do I simply create a python file (in Visual Studio Code for example) and run it there? Because it didn't work when I tried this...
I also saw many people using the API with the commander only, others used something in chrome (localhost:8888...). So I don`t really know what's the way to go or what I should do.
Thanks for any help :)
Best regards!
I'm not a python developer but as a guess you could start here:
https://developers.google.com/youtube/v3/quickstart/python
using pip to install the dependencies you need.
You should be able to create a simple python file that authenticates with the API and then calls a method on the on the google api client and then output it. There are some examples here:
https://github.com/youtube/api-samples/blob/master/python/
using System;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Services;
using Google.Apis.Upload;
using Google.Apis.Util.Store;
using Google.Apis.YouTube.v3;
using Google.Apis.YouTube.v3.Data;
namespace Google.Apis.YouTube.Samples
{
/// <summary>
/// YouTube Data API v3 sample: upload a video.
/// Relies on the Google APIs Client Library for .NET, v1.7.0 or higher.
/// See https://code.google.com/p/google-api-dotnet-client/wiki/GettingStarted
/// </summary>
internal class UploadVideo
{
[STAThread]
static void Main(string[] args)
{
Console.WriteLine("YouTube Data API: Upload Video");
Console.WriteLine("==============================");
try
{
new UploadVideo().Run().Wait();
}
catch (AggregateException ex)
{
foreach (var e in ex.InnerExceptions)
{
Console.WriteLine("Error: " + e.Message);
}
}
Console.WriteLine("Press any key to continue...");
Console.ReadKey();
}
private async Task Run()
{
UserCredential credential;
using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
{
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
// This OAuth 2.0 access scope allows an application to upload files to the
// authenticated user's YouTube channel, but doesn't allow other types of access.
new[] { YouTubeService.Scope.YoutubeUpload },
"user",
CancellationToken.None
);
}
var youtubeService = new YouTubeService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = Assembly.GetExecutingAssembly().GetName().Name
});
var video = new Video();
video.Snippet = new VideoSnippet();
video.Snippet.Title = "Default Video Title";
video.Snippet.Description = "Default Video Description";
video.Snippet.Tags = new string[] { "tag1", "tag2" };
video.Snippet.CategoryId = "22"; // See https://developers.google.com/youtube/v3/docs/videoCategories/list
video.Status = new VideoStatus();
video.Status.PrivacyStatus = "unlisted"; // or "private" or "public"
var filePath = #"REPLACE_ME.mp4"; // Replace with path to actual movie file.
using (var fileStream = new FileStream(filePath, FileMode.Open))
{
var videosInsertRequest = youtubeService.Videos.Insert(video, "snippet,status", fileStream, "video/*");
videosInsertRequest.ProgressChanged += videosInsertRequest_ProgressChanged;
videosInsertRequest.ResponseReceived += videosInsertRequest_ResponseReceived;
await videosInsertRequest.UploadAsync();
}
}
void videosInsertRequest_ProgressChanged(Google.Apis.Upload.IUploadProgress progress)
{
switch (progress.Status)
{
case UploadStatus.Uploading:
Console.WriteLine("{0} bytes sent.", progress.BytesSent);
break;
case UploadStatus.Failed:
Console.WriteLine("An error prevented the upload from completing.\n{0}", progress.Exception);
break;
}
}
void videosInsertRequest_ResponseReceived(Video video)
{
Console.WriteLine("Video id '{0}' was successfully uploaded.", video.Id);
}
}
}
Make sure you have python installed on your PC
Create a project: Google’s APIs and Services dashboard
Enable the Youtube v3 API: API Library
Create credentials: Credentials wizard
Now you need to get an access token and a refresh token using the credentials you created
Find an authentication example in one of the following libraries:
https://github.com/googleapis/google-api-python-client
https://github.com/omarryhan/aiogoogle (for the async version)
Copy and paste the client ID and client secret you got from step 4 and paste them in the authentication example you found in step 6 (Should search for an OAuth2 example), this step should provide with an access token and a refresh token
Copy and paste a Youtube example from either:
https://github.com/googleapis/google-api-python-client
https://github.com/omarryhan/aiogoogle (for the async version)
Replace the access token and refresh token fields with the ones you got.
Now you should be able to run the file from any terminal by typing:
python3 yourfile.py
[EDIT]
The API key is not the same as the access token. There are 2 main ways to authenticate with Google APIs:
Access and refresh token
API_KEY.
API key won't work with personal info. You need to get an access and refresh token for that (method 1).
Once you get an access token, it acts in a similar fashion to the API_KEY you got. Getting an access token is a bit more complicated than only working with an API_KEY.
A refresh token is a token you get with the access token upon authentication. Access tokens expire after 3600 seconds. When they expire, your authentication library asks Google's servers for a new access token with the refresh token. The refresh token has a very long lifetime (often indefinite), so make sure you store it securely.
To get an access token and a refresh token (user credentials), you must first create client credentials. Which should consists of 1. a client ID and 2. a client secret. These are just normal strings.
You should also, set a redirect URL in your Google app console in order to properly perform the OAuth2 flow. The OAuth2 flow is the authentication protocol that many APIs rely on to allow them to act on a user's account with the consent of the user. (e.g. when an app asks you to post on your behalf or control your account on your behalf, it typically will use this protocol.)
Aiogoogle's docs does a decent job in explaining the authentication flow(s) available by Google.
https://aiogoogle.readthedocs.io/en/latest/
But this is an async Python library. If you're not familiar with the async syntax, you can read the docs just to get a general idea of how the authentication system works and then apply it to Google's sync Python library.
About point no.6. The links I posted with Aiogoogle being one of them, are just client libraries that help you access Google's API quicker and with less boilerplate. Both libraries have documentation, where they have links to examples on how to use them. So, open the documentation, read it, search for the examples posted, try to understand how the code in the example(s) work. Then maybe download it and run it on your own machine.
I recommend that your read the docs. Hope that helps.

Sense/net using content query in web application

I try to use content query in web application but it throw an exception " Lucene.Net.Store.AlreadyClosedException: this IndexReader is closed". Please give help me resolve that problem.
var startSettings = new RepositoryStartSettings
{
Console = Console.Out,
StartLuceneManager = true, // <-- this is necessary
IsWebContext = false,
PluginsPath = AppDomain.CurrentDomain.BaseDirectory,
};
using (Repository.Start(startSettings))
{
var resultQuery = ContentQuery.Query("+InTree:#0 + DisplayName:*#1*", null, folderPath, q);
}
The recommended way to connect to Sense/Net from a different application (app domain) is through the REST API. It is much easier to maintain and involves less configuration (the only exception is where you are working inside the Sense/Net application itself, or you only have a single application and you do not want to access Sense/Net from anywhere else, and you are willing to deal with a local index of Sense/Net and all the config values it needs, etc).
Connecting through the REST API does not mean you have to send HTTP requests manually (although that is also not complicated at all): there is a .Net client library which does that for you. You can access all content metadata or binaries through the client, you can upload files, query content, manage permissions, etc.
// loading a content
dynamic content = await Content.LoadAsync(id);
DateTime date = content.BirthDate;
// querying
var results = await Content.QueryAsync(queryText);
Install: https://www.nuget.org/packages/SenseNet.Client
Source and examples: https://github.com/SenseNet/sn-client-dotnet
To use it in a web application, you have to do the following:
initialize the client context once, at the beginning of the application life cycle (e.g. app start)
if you need to make requests to Sense/Net in the name of the currently logged in user (e.g. because you want to query for documents accessible by her), than you have to create a new ServerContext object for every user with the username/password of that user, and provide this object to any client call (e.g. load or save content methods).
var sc = new ServerContext
{
Url = "http://example.com",
Username = "user1",
Password = "asdf"
};
var content = await Content.LoadAsync(id, sc);

Google .Net Service Account access to Calendar

I am updating a web service application that calls Google's calendar API's to list calendar events for a particular calendar and insert new calendar events. I am trying to upgrade it to version 3 of the api's. For authentication I am using a Service Account Credential that I created in the Google Developers Console (https://console.developers.google.com). I am able to create the CalendarService using the following code :
using System;
using Google.Apis.Auth.OAuth2;
using System.Security.Cryptography.X509Certificates;
using Google.Apis.Services;
using Google.Apis.Calendar.v3;
using Google.Apis.Calendar.v3.Data;
...
string SERVICE_ACCOUNT_EMAIL =
"....googleusercontent.com";
string SERVICE_ACCOUNT_PKCS12_FILE_PATH = #"C:\temp\API Project-123456789.p12";
// Create the service.
X509Certificate2 certificate = new X509Certificate2(SERVICE_ACCOUNT_PKCS12_FILE_PATH, "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(SERVICE_ACCOUNT_EMAIL)
{
Scopes = new[] { CalendarService.Scope.Calendar }
, User = "something#mycompany.com"
}.FromCertificate(certificate));
// Create the service.
var cs = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Calendar API Sample",
});
But when I call the list method to query a public calendar:
Events events = service.Events.List("something#mycompany.com").Execute();
A TokenResponseException is thrown with the following error message:
Error:"invalid_grant", Description:"", Uri:""
FYI : I have gone into the AdminHome for my company and under security Manage clients API Access and registered the SERVICE_ACCOUNT_EMAIL above to http://www.google.com/calendar/feeds/.
Any help with this would be greatly appreciated.
I believe you need to register the Client ID of the service account of your App, I have successfully got it working using this difference.
Here is a similar issue I resolved with inserting events.
Google API Calender v3 Event Insert via Service Account using Asp.Net MVC
Here is the google documention on domain-wide-authorization.
https://developers.google.com/+/domains/authentication/delegation