Getting all messages from Microsoft Teams - api

I'm trying to get all the messages from Microsoft Teams in my tenant, I have registered the application to Azure, set the correct permissions and grated admin privileges.
What I am getting confused about is creating a GraphServiceClient.
My app is more of an Daemon Application.
I would really appreciate if someone could give me an example of how to create the client correctly.
this is my code so far:
string[] graphScopes = { "https://graph.microsoft.com/.default" };
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder
.Create("x")
.WithTenantId("x")
.WithClientSecret("x")
.Build();
ClientCredentialProvider authProvider = new ClientCredentialProvider(app);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
try
{
var messages = await graphClient.Teams["x"].Channels["x#thread.skype"].Messages.Request().GetAsync();
Console.ReadLine();
foreach(var item in messages)
{
Console.WriteLine(item.Body);
}
}
catch(Exception e)
{
Console.WriteLine(e.Message);
Console.Read();
}
I'm getting the following error no matter what I'm trying to get
Code: UnknownError Inner error: AdditionalData: request-id: x date: 2020-05-27T14:22:37 ClientRequestId:x
update: I was able to get something from the API, I had wrong permissions.
still can't get the messages though,
I have all these permission:
ChannelMessage.Read.All, Group.Read.All, Group.ReadWrite.All
I'm probably missing the "ChannelMessage.Read.Group (RSC)" permission but I can't find it in the permissions page.

May this is the solution or the problem ;-)
Microsoft Teams APIs in Microsoft Graph that access sensitive data are considered protected APIs. These APIs require that you have additional validation, beyond permissions and consent, before you can use them.
https://learn.microsoft.com/en-us/graph/teams-protected-apis

Your problem is you are accessing a "beta" api but using the production base url path.
The permission you need is ANY of the following (i.e. or not and):
ChannelMessage.Read.Group (RSC) OR
ChannelMessage.Read.All OR
Group.Read.All OR
Group.ReadWrite.All
Since you have Group.Read.All, that is ALL you need for permissions.
What you need to do is change the base URL to the beta api:
graphClient.BaseUrl = "https://graph.microsoft.com/beta";
UPDATED:
Since now you are saying that you are getting a "Forbidden" error, I think you also have a consent problem.
My guess is that you created & consented you app on one tenant but you are trying to access the data in another tenant. This will give you a forbidden errors. i.e. you created and consented on a dev azure account tenant and are trying to access your work tenant.
If this is the situation you need to:
* Make sure that the setup you azure app to be multi-tenanted
* You have to get your app consented by the target tenant
If you do that and use the beta endpoint I would expect that your example code will start working.
Update2:
Finally got around to trying to do the message list with a application context like you above and I get the same Forbidden error as well from the beta api. From a user context it works fine. So your answer will be to use a user context and not an application context to access this API.
It looks like what you are hitting is a Protected API. So if you want to use this API from an application context, you will have to submit a request to be allowed access to it.

Related

Why do my queries work in Microsoft Graph Explorer but not in my client app (MVC C#)?

I am attempting to learn more about Microsoft Graph and have made extensive use of the Microsoft Graph Explorer site. I am now attempting to transfer a pair of queries from Microsoft Graph Explorer to an MVC C# app but I am receiving the following error message:
"AADSTS65001: The user or administrator has not consented to use the application with ID '' named 'My App'."
The following query works just fine in Microsoft Graph Explorer:
https://graph.microsoft.com/v1.0/servicePrincipals/[resourceID]/appRoleAssignedTo?$select=appRoleId,principalId,principalDisplayName,principalType,resourceId,resourceDisplayName
The following C# equivalent throws the error:
var appRoleAssignedTo = await graphClient.ServicePrincipals[resourceId].AppRoleAssignedTo
.Request()
.Select("appRoleId,principalId,principalDisplayName,principalType,resourceId,resourceDisplayName")
.GetAsync()
.ConfigureAwait(false);
The Microsoft Graph Explorer site indicates the following four permissions must be consented by the app administrator:
"Application.Read.All", "Application.ReadWrite.All", "Directory.Read.All", "Directory.ReadWrite.All".
These permissions have been consented as required.
I am trying to automate these queries so that I can incorporate them into my app. Any assistance is greatly appreciated.
I think you may forget to grant api permission, or setting delegate api permission but using client credential flow to call the api.
when we use Microsoft Graph Explorer to call graph api, it require us to sign in with the microsoft account first, then we can call the api, this means we are using delegate api permission to call the api.
So I want to double confirm with you that if you has a sign in progress before you call the api, if not, then the failure should be the expected behavior. If you don't want to sign in before calling graph api, you need to grant application api permission and using client credential flow like code below to call this api:
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "tenant_name.onmicrosoft.com";
var clientId = "azure_ad_appid";
var clientSecret = "client_secret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
If you has a sign in progress before calling the api, then I suggest you to check if you've grant the api permission, if you want to give application api permission, you also need to click this button.

How can I delete Azure B2C users directly from my app?

I'm building an ASP .Net Core web App. I use Azure ADB2C for user authentication and I would like to have an admin user, which could delete other users. I can delete users from Azure Active Directory via Azure portal, but I would like to do it directly from the app. I have created an admin account in my Active Directory tenant, and gave it global administrator permissions.
I tried to use Graph API, but I can't get it to work. I created an IAuthenticationProved according to instructions on this website:
https://learn.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=CS.
Then I created GraphServiceClient and tried to delete user (https://learn.microsoft.com/en-us/graph/api/user-delete?view=graph-rest-1.0&tabs=csharp), but I got error:
System.AggregateException: 'Returning 0 accountsts and 0 broker accountsdata provider for login.microsoftonline.com. Success? True.)'
AuthenticationException: Code: authenticationChallengeRequired
Message: Authentication challange is required.
My code looks like this:
IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create("<My Client ID>")
.WithRedirectUri("<My Redirect Uri>")
.WithClientSecret("<My Client secret>")
.Build();
List<string> scopes = new List<string>();
scopes.Add("https://graph.microsoft.com/User.ReadWrite.All");
scopes.Add("https://graph.microsoft.com/Directory.ReadWrite.All");
AuthorizationCodeProvider authProvider = new AuthorizationCodeProvider(confidentialClientApplication,scopes);
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
graphClient.Users[<UserId>]
.Request()
.DeleteAsync()
.Wait();
Could you tell me what I'm doing wrong? Or maybe there is some other way to do it? Thank you in advance!
Your approach to use Graph API is the way to go. But the problem you are facing there is because your selected auth provider (AuthorizationCodeProvider) is not matching with the scope needed here to delete user. Looking at your code, you want 'Authorization Code' flow which means it would need a delegated scope. As mentioned in permission requirement for delete user, the 'Delegated' scope needed to delete user is Directory.AccessAsUser.All which I don't see in the code snippet you shared.
Additionally, suggest you a quick read to Choose a Microsoft Graph authentication provider based on scenario.
NOTE: To add the required delegated permission, the application registration must be either of the first two types types shown below. The third option won't give you that.
For the above you will be able to add Directory.AccessAsUser.All delegated permission.

Authenticate Google access token with ASP.NET Core backend server

I have Angular2 on client and ASP.NET Core on server side. I use JavaScriptServices (aspnetcore-spa template).
For authentication I use OpenIddict and I follow example here.
Now I am on the server side in Controller class method and I would like to validate id_token because this is suggested on this side:
Important: Do not use the Google IDs returned by getId() or the user's
profile information to communicate the currently signed in user to
your backend server. Instead, send ID tokens, which can be securely
validated on the server.
And I would also like to register user (save email, profile ...) in my database through ASP.NET Core identity.
I would like to use Google API client Library for .NET to get user information and store refresh_token. Years ago I manage to do it with PHP, but I can't figure it out with .NET.
I download nuget packages: Google.Apis, Google.Apis.OAuth2.v2, Google.Apis.Plus.v1.
I am not sure which nuget package I need for this, which class should I use, how to set Google ServerKey and how to get user information from information which I get from gapi.signin2 button.
In simple:
How can I validate id_token from .NET with Google .NET Client library?
I found solution here. It is old, but it works.
var googleInitializer = new BaseClientService.Initializer();
googleInitializer.ApiKey = this.config["Authentication:Google:ServerKey"];
Oauth2Service ser = new Oauth2Service(googleInitializer);
Oauth2Service.TokeninfoRequest req = ser.Tokeninfo();
req.AccessToken = request.AccessToken; //access token received from Google SignIn button
Tokeninfo userinfo = await req.ExecuteAsync();
I didn't figure it out how to get Display name and picture on server. But it can be done on client:
onGoogleLoginSuccess(user: gapi.auth2.GoogleUser)
{
console.log("basic profile", user.getBasicProfile());
}
If someone knows more updated solution or how to retrieve basic user profile on server, please share it.
In addition I can use Google+, but careful because Google Account is not Google+ Account. I didn't have + account and get error:
Google.Apis.Requests.RequestError Not Found [404] Errors [
Message[Not Found] Location[ - ] Reason[notFound] Domain[global] ]
in code:
var plusService = new PlusService(googleInitializer);
Person me = await plusService.People.Get(userinfo.UserId).ExecuteAsync();
but it is possible to get all user information (picture, display name, first name, last name, birthday ...)

Save information immediately after Google login in Azure Mobile Services (.NET Back-end)

What I basically want to be able to do is authenticate to azure mobile services (using google or some other provider), and immediately save some of the user information (i.e. email address) on the server.
I know I could call a custom method from the app after authentication, but I was hoping to have some hook to do this straight after the google login on the server side.
Is this possible? How do I do it?!
This is currently only possible in the .NET runtime. If using the Node runtime, you will not be able to do this.
For the .NET runtime, you would want to create a class which inherits from GoogleLoginProvider (I'll call mine CustomGoogleLoginProvider), and then you'll need to override the CreateCredentials method:
public override ProviderCredentials CreateCredentials(ClaimsIdentity claimsIdentity)
{
// grab any information from claimsIdentity which you would like to store
// If you need the access token for use with the graph APIs, you can use the following
string providerAccessToken = claimsIdentity.GetClaimValueOrNull(ServiceClaimTypes.ProviderAccessToken);
// use the access token with HttpClient to get graph information to store
return base.CreateCredentials(claimsIdentity);
}
Then in your WebApiConfig.cs, add the following to the Register() method, immediately after the options object is created:
options.LoginProviders.Remove(typeof(GoogleLoginProvider));
options.LoginProviders.Add(typeof(CustomGoogleLoginProvider));
The CreateCredentials() method gets called immediately before a Mobile Services token is created. At this point, the Google token has been validated, and the claimsIdentity has been populated with whatever Google sent back.
Some information will be available in the claimsIdentity by default, but you may also have information which requires you to call through to Google. You can only do this if you set the proper scopes configured.
If you did want to go the custom API route, you would just need to make a call from your controller:
ServiceUser user = (ServiceUser)this.User;
GoogleCredentials creds = (await user.GetIdentitiesAsync()).OfType<GoogleCredentials>().FirstOrDefault();
string accessToken = creds.AccessToken;
The Node version of getIdentities() is documented here.

Using oauth2_access_token to get connections in linkedIn

I'm trying to get the connections in linkedIn using their API, but when I try to retrieve the connections I get a 401 unauthorized error.
in the official documentation says
You must use an access token to make an authenticated call on behalf
of a user
Make the API calls You can now use this access_token to make API calls on behalf of this user by appending
"oauth2_access_token=access_token" at the end of the API call that you
wish to make.
The API call that I'm trying to do is the following
Error -->
http://api.linkedin.com/v1/people/~/connections:(id,headline,first-name,last-name)?format=json&oauth2_access_token=access_token
I have tried to do it with the following endpoint without any problems.
OK --> https://api.linkedin.com/v1/people/~:(id,first-name,last-name,formatted-name,date-of-birth,industry,email-address,location,headline,picture-urls::(original))?format=json&oauth2_access_token=access_token
this list of endpoints for the connections API are described here
http://developer.linkedin.com/documents/connections-api
I just copied and pasted one endpoint from there, so the question is what's the problem with the endpoint for getting the connections? what am I missing?
EDIT:
for the preAuth Url I'm using
https://www.linkedin.com/uas/oauth2/authorization?response_type=code&client_id=ConsumerKey&scope=r_fullprofile%20r_emailaddress%20r_network&state&state=NewGuid&redirect_uri=Encoded_Url
https://www.linkedin.com/uas/oauth2/accessToken?grant_type=authorization_code&code=QueryString_Code&redirect_uri=EncodedCallback&client_id=ConsummerKey&client_secret=ConsumerSecret
please find attached the login screen requesting the permissions
EDIT2:
Switched to https and worked like a charm!
Access tokens are issued for a specific scope that describes the extent of the permission you are requesting. When you start the authentication transaction, you add a specific parameter (called scope) that requests the user to consent access to what you want (in this case their connections). If I remember correctly, in LinkedIn that is r_network.
Check their documentation here: http://developer.linkedin.com/documents/authentication#granting
So, your call is perfectly ok, but very likely your access_token doesn't have enough privileges.
apiHelper.getRequest(getActivity(),"https://api.linkedin.com/v1/people/~/connections?modified=new", new ApiListener() {
#Override
public void onApiSuccess(ApiResponse response) {
}
#Override
public void onApiError(LIApiError error) {
}
});
If you are trying to get user connections using the LinkedIn SDK for android like in the snippet above,
Check for permissions in the SDK in this class com.linkedin.platform.utils.Scope.
Make sure r_network is available when building your scope. Example
public static final LIPermission R_NETWORK = new LIPermission("r_network", "Your network");
Can now be used like this to build scope
Scope.build(Scope.R_BASICPROFILE, Scope.R_EMAILADDRESS, Scope.W_SHARE, Scope.R_FULLPROFILE, Scope.R_CONTACTINFO, Scope.R_NETWORK)