I try to send a message to a specific channel in teams.
It works perfectly with 'https://developer.microsoft.com/de-de/graph/graph-explorer'.
For reciving the access token i do the following. This part works, because i can retrieve user information and other things with it.
$guzzle = new \GuzzleHttp\Client(['verify' => false]);
$tenantId = '<tenant_id>';
$clientId = '<client_id>';
$clientSecret = '<client_secret>';
$url = 'https://login.microsoftonline.com/' . $tenantId . '/oauth2/token?api-version=1.0';
$token = json_decode($guzzle->post($url, [
'form_params' => [
'client_id' => $clientId,
'client_secret' => $clientSecret,
'resource' => 'https://graph.microsoft.com/',
'grant_type' => 'client_credentials',
],
'verify' => false
])->getBody()->getContents());
$accessToken = $token->access_token;
This part now is the one that produces the error
$graph = new Graph();
$graph->setAccessToken($accessToken);
$url = '/teams/<team_id>/channels/<channel_id>/messages';
$queryParams = [
'body' => [
'content' => 'foo'
]
];
$info = $graph->setApiVersion("beta")->createRequest("POST", $url)
->addHeaders(array("Content-Type" => "application/json"))
->attachBody($queryParams)
->setReturnType(BetaModel\User::class)
->execute();
echo '<pre>';
print_r($info);
This produces the following error
{
"error": {
"code": "UnknownError",
"message": "",
"innerError": {
"date": "2020-11-11T10:14:03",
"request-id": "<request_id>",
"client-request-id": "<client_request_id>"
}
}
}
The teams id and the channel id are correct, i can retrieve them by using
/users/<user_id>/joinedTeams
and
/teams/<teams_id>/channels
and as mentioned earlier, it works in the graph explorer page.
I know that i somehow need to send the message in an user context, but i can't make it work, and i can't find useful informations, so i am pleased for any. Thanks in advance!
This api call currently does not support application permissions, so you cannot use the client credential flow to obtain a token. You need to grant delegated permissions to the application, and then use the auth code flow to obtain the token. see:here.
Related
I am calling one Microsoft graph API from my PHP application, API is https://graph.microsoft.com/beta/policies/identitySecurityDefaultsEnforcementPolicy
my code is like below
$graph = new Graph();
$graph->setAccessToken(session('my_token'));
try{
$response = $graph->createRequest("GET", "/policies/identitySecurityDefaultsEnforcementPolicy")->execute();
}
catch(Exception $e){
dd($e);
}
$arr = $response->getBody();
dd($arr);
but it always catches exception and displays the below error
Client error: `GET https://graph.microsoft.com/v1.0/policies/identitySecurityDefaultsEnforcementPolicy` resulted in a `403 Forbidden` response:
{"error":{"code":"AccessDenied","message":"You cannot perform the requested operation, required scopes are missing in the token.","innerError":{"date":"2022-11-23T06:47:39","request-id":"9a4573c7-fd72-44ae-8ac6-8e4589cf1497","client-request-id":"9a4573c7-fd72-44ae-8ac6-8e4589cf1497"}}}
all the other Microsoft graph APIs are working well
I have also given permission to Policy.Read.All and granted admin consent to the Microsoft app I am using here for auth.
Update: when I open Microsoft's online token parser https://jwt.ms/ and parsed my token, I see the roles like
"roles": [
"Mail.ReadWrite",
"User.ReadWrite.All",
"SecurityEvents.Read.All",
"Mail.ReadBasic.All",
"Group.Read.All",
"MailboxSettings.Read",
"Group.ReadWrite.All",
"SecurityEvents.ReadWrite.All",
"User.Invite.All",
"Directory.Read.All",
"User.Read.All",
"Domain.Read.All",
"GroupMember.Read.All",
"Mail.Read",
"User.Export.All",
"IdentityRiskyUser.Read.All",
"Mail.Send",
"User.ManageIdentities.All",
"MailboxSettings.ReadWrite",
"Organization.Read.All",
"GroupMember.ReadWrite.All",
"IdentityRiskEvent.Read.All",
"Mail.ReadBasic",
"Reports.Read.All"
]
but not the Policy.Read.All
Update: Getting auth token code is
$guzzle = new \GuzzleHttp\Client();
$url = 'https://login.microsoftonline.com/'.env("TANANT_ID").'/oauth2/token?api-version=beta';
$token = json_decode($guzzle->post($url, [
'form_params' => [
'client_id' => env("CLIENT_ID"),
'client_secret' => env("CLIENT_SECRET"),
'resource' => 'https://graph.microsoft.com/',
'grant_type' => 'client_credentials',
],
])->getBody()->getContents());
// echo $token->access_token;
Session::put('my_token', $token->access_token);
When you're requesting the token, you need to supply a scope URL,
https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow#get-a-token
So as a basic example (this might not give the permission you need) but shows what your missing.
$guzzle = new \GuzzleHttp\Client();
$url = 'https://login.microsoftonline.com/'.env("TANANT_ID").'/oauth2/token?api-version=beta';
$token = json_decode($guzzle->post($url, [
'form_params' => [
'client_id' => env("CLIENT_ID"),
'client_secret' => env("CLIENT_SECRET"),
'resource' => 'https://graph.microsoft.com/',
'scope' => 'https://graph.microsoft.com/.default',
'grant_type' => 'client_credentials',
],
])->getBody()->getContents());
// echo $token->access_token;
Session::put('my_token', $token->access_token);
specifically notice that i have added
'scope' => 'https://graph.microsoft.com/.default', to your form params
Looks like you don't have Policy.Read.All permission , could you please cross check permission through azure portal and provide the required permission and try again.
Thanks
I have a website where my clients can view their invoices and upload design assets related to the invoice. I want to create a PHP form which uploads the files to OneDrive, instead of storing them on our web server.
I have followed the instructions in this StackOverflow answer to get started.
I have created the app in Microsoft Azure, and entered the appropriate Application (client) ID, Object ID, and Directory (tenant) ID. I am using a client secret to authorize the application, and submitting a POST request using my account's email and password to get the access token.
The relevant code for my upload form looks like this:
if (isset($_FILES['uploads'])) {
$guzzle = new \GuzzleHttp\Client();
$tenantId = 'xxxxxx';
$clientId = 'xxxxxx';
$clientSecret = 'xxxxxx';
$url = 'https://login.microsoftonline.com/' . $tenantId . '/oauth2/token';
$user_token = json_decode($guzzle->post($url, [
'form_params' => [
'client_id' => $clientId,
'client_secret' => $clientSecret,
'resource' => 'https://graph.microsoft.com/',
'grant_type' => 'password',
'username' => 'xxxxxx#companydomain.com',
'password' => 'xxxxxx'
],
])->getBody()->getContents());
$user_accessToken = $user_token->access_token;
$graph = new Graph();
$graph->setAccessToken($user_accessToken);
foreach ($_FILES['uploads']['name'] as $key => $name) {
try {
$graph->createRequest(
"PUT", "/drive/root:/Documents/".$_FILES['uploads']['name'][$key].":/content"
)->upload(
$_FILES['uploads']['tmp_name'][$key]
);
} catch (Exception $e) {
var_dump($e);
}
}
}
This code throws an exception, showing the following error message when I try to upload a :
Client error: `PUT https://graph.microsoft.com/v1.0/drive/root:/Documents/Screen%20Shot%202022-10-30%20at%204.03.44%20PM.png:/content` resulted in a `403 Forbidden` response
In the Azure API Permissions, I have granted the Files.Read.All, Files.ReadWrite.All, and User.Read permissions. However, when I check the access token in https://jwt.ms/, I just see "scp": "User.Read".
So it looks like my access token might not have the correct permissions, but I can see that I do have permissions to read and write files when I look at the API Permissions page in Azure.
How can I further debug this issue and find a solution to upload files from my server to OneDrive?
Thanks to Nikolay's comment, I added a scope parameter to form_params and that fixed it.
I also changed the grant_type to client_credentials, and removed the username/password authorization.
$user_token = json_decode($guzzle->post($url, [
'form_params' => [
'client_id' => $clientId,
'client_secret' => $clientSecret,
'resource' => 'https://graph.microsoft.com/',
'grant_type' => 'client_credentials',
'scope' => 'https://graph.microsoft.com/.default'
],
])->getBody()->getContents());
$user_accessToken = $user_token->access_token;
I tried this code but not get access token, but not working
I want to sync my outlook 365 calendar events with my system. My system is a background service, not an application, therefore i can't provide a login screen for the user to approve authorization.
I'm following this link in order to get an access token
Get access without a user
$guzzle = new \GuzzleHttp\Client();
$url='https://login.microsoftonline.com/'.config('azure.tenantId').'/oauth2/v2.0/token';
$token = json_decode($guzzle->post($url, [
'form_params' => [
'grant_type' => 'client_credentials',
'client_id' => config('azure.appId'),
'client_secret' => config('azure.appSecret'),
'scope' => config('azure.scopes'),
'username' => "youremail#domaine.com",
'password' => "password",
],
])->getBody()->getContents());
$accessToken = $token->access_token;
//Code to get data user form Microsoft Graph API
$graph = new Graph();
$graph->setAccessToken($token->access_token);
$user = $graph->createRequest('GET', '/me?$select=displayName,mail,mailboxSettings,userPrincipalName')
->setReturnType(Model\User::class)
->execute();
$tokenCache = new TokenCache();
$tokenCache->storeTokens($accessToken, $user);
The solution to Login direct without redirection
The documentation is clear, but I did not understand it well, but I finally found the solution after looking at the documentation step by step well
https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth-ropc#authorization-request
try {
$guzzle = new \GuzzleHttp\Client();
$url = 'https://login.microsoftonline.com/'.config('azure.tenantId').'/oauth2/v2.0/token';
$token = json_decode($guzzle->post($url, [
'form_params' => [
'grant_type' => 'password',
'client_id' => config('azure.appId'),
'client_secret' => config('azure.appSecret'),
'scope' => config('azure.scopes'),
'username' => "youremail#domaine.com",
'password' => "password",
],
])->getBody()->getContents());
$graph = new Graph();
$graph->setAccessToken($token->access_token);
$user = $graph->createRequest('GET', '/me?$select=displayName,mail,mailboxSettings,userPrincipalName')
->setReturnType(Model\User::class)
->execute();
$token = new \League\OAuth2\Client\Token\AccessToken(json_decode(json_encode($token), true));
$tokenCache = new TokenCache();
$tokenCache->storeTokens($token, $user);
return redirect('/');
} catch (\League\OAuth2\Client\Provider\Exception\IdentityProviderException $e) {
return redirect('/')->with('error', 'Error requesting access token')->with('errorDetail', json_encode($e->getResponseBody()));
}
I have set the MS Azure API permissions of contacts.read.write but when executing the following code I get this message:
Uncaught GuzzleHttp\Exception\ClientException: Client error: POST https://graph.microsoft.com/beta/contacts resulted in a 400 Bad Request response: { "error": { "code": "Request_BadRequest", "message": "A value without a type name was found and no expecte (truncated...) in C:\Program Files\Ampps\www\weedo\weedo2\content\libraries\MicrosoftGraph\vendor\guzzlehttp\guzzle\src\Exception\RequestException.php:113
My post code:
$graph = new Graph();
$graph
->setBaseUrl("https://graph.microsoft.com/")
->setApiVersion("beta")
->setAccessToken($this->accessToken);
$data = array(
"GivenName" => "Test",
"Surname" => "account",
"EmailAddresses" => array(
array(
"Address" => "testaccount#mail.com",
"Name" => "Mail Name"
)
)
);
$user = $graph->createRequest("POST", "/contacts")
->addHeaders(array("Content-Type" => "application/json"))
->setReturnType(Model\User::class)
->attachBody($data)
->setTimeout("1000")
->execute();
I believe I followed these pages correctly, but the examples are not all given for PHP so I'm not sure where to go from here:
https://github.com/microsoftgraph/msgraph-sdk-php
https://learn.microsoft.com/en-us/graph/api/user-post-contacts?view=graph-rest-1.0&tabs=http
I also wrapped the $data parameter in a json_encode just for testing, but no luck.
Where to go? What to do?
EDIT:
So as seems i was confused with organizational contacts (which are obtained by /contacts) and personal contacts (which are obtained by /me/contacts). Now it is not (yet) supported to create organizational contacts, which i find rediculous but for some reason i'm not allowed to create contacts on application based either. any thoughts / ideas on this?
You are right, currently, there is no Microsoft Graph API for creating organizational contacts, only list and get method provided. When I tried to create a new one, I got this error:
This issue also has been confirmed by a Microsoft engineer here.
So the only 2 ways I know to add organizational contacts is on Microsoft 365 admin Center =>Users => Contacts blade to add manually:
Add my self as a org contat and I can get the new record from /contacts calling:
The second way is using the PowerShell command:New-MailContact to do this:
All of these 2 ways I have tested on my side and all works for me.
UPDATE:
If you are looking for some sample code about adding a user's personal contacts by an Azure AD application, just try code below:
use Microsoft\Graph\Graph;
use Microsoft\Graph\Http\GraphRequest;
$tenantId= '<your tenant name/ID>';
$clientId = '<azure ad app id>';
$clientSecret ='<azure ad app secret>';
$targetUser = '<target user UPN/ID>';
$guzzle = new \GuzzleHttp\Client();
$url = 'https://login.microsoftonline.com/' . $tenantId . '/oauth2/token?api-version=1.0';
$token = json_decode($guzzle->post($url, [
'form_params' => [
'client_id' => $clientId,
'client_secret' => $clientSecret,
'resource' => 'https://graph.microsoft.com/',
'grant_type' => 'client_credentials',
],
])->getBody()->getContents());
$accessToken = $token->access_token;
$data = array(
"GivenName" => "Test2",
"Surname" => "account2",
"EmailAddresses" => array(
array(
"Address" => "testaccount2#mail.com",
"Name" => "Mail Name2"
)
)
);
$graph = new Graph();
$graph->setAccessToken($accessToken);
$graph->createRequest("POST", "/users/$targetUser/contacts")
->addHeaders(array("Content-Type" => "application/json"))
->attachBody($data)
->execute();
Result:
I have to read a file in a OneDrive sheet using prestashop.
I can't use the microsoft/microsoft-graph packet with composer because it need guzzle v6 and prestashop is locked at v5 so i'm rigting the request myself but it seems i'm missing something.
I got the credential figured out, I have my access token (without it or a random string gives me an 401 error).
Now I'm trying to read a file, but even easier than that, I can't list the recent files in my drive.
I can do it on the graph explorer (beeing connected) and do whatever I want but whtin the code I always get an "400 error Bad request" with no other detail.
Here is my code, if you can point me to te right direction?
$url = 'https://graph.microsoft.com/v1.0/me/drive/recent';
$req = $guzzle->get($url, [
'headers' => [
'User-Agent' => 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.2 (KHTML, like Gecko) Chrome/5.0.342.3 Safari/533.2',
'Accept' => 'application/json, text/plain, */*',
'Authorization' => 'Bearer ' . $accessToken,
],
'body' => [
],
]);
EDIT :
The problem seems to be about authorisation, here is the code :
<?php
// 1/ retrieve the access token
$guzzle = new \GuzzleHttp\Client();
$url = 'https://login.microsoftonline.com/' . $tenantId . '/oauth2/token?api-version=1.0';
$token = json_decode($guzzle->post($url, [
'body' => [
'client_id' => $clientId,
'client_secret' => $clientSecret,
'resource' => 'https://graph.microsoft.com/',
'grant_type' => 'client_credentials',
//should I use this
'roles'=>[
"Sites.Read.All",
"Files.Read.All"
],
//or this?
'scp' => 'Sites.Read.All Files.Read.All'
],
])->getBody()->getContents());
$access_token = $token->access_token;
// 2/ read file content
$userId = 'email#domain.fr';
$url = 'https://graph.microsoft.com/v1.0/users/' . $userId . '/drive/items/##FILE_ID##/workbook/worksheets(\'Export\')/range(address=\'a2:d4\')?$select=values';
//$url = 'https://graph.microsoft.com/v1.0/users/' . $userId . '/drive/list';
$guzzle = new \GuzzleHttp\Client();
$req = $guzzle->get($url, [
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
]
]);
// this return a 403 forbidden
$ret = json_decode($req->getBody()->getContents());
Most likely you acquire access token (application permission token) via Client credentials grant flow, in that case there is no Me context since it represents signed-in user context (applicable for delegated permission token only).
For application permission token user needs to be explicitly specified, for example:
To call List recent files endpoint at least Files.Read.All and Sites.Read.All permission levels are required.
Once permissions are granted and consented as illustrated below
roles claim should contain the following list of permissions:
"roles": [
"Sites.Read.All",
"Files.Read.All"
]
$userId = "<username>#<tenant>.onmicrosoft.com";
$url = 'https://graph.microsoft.com/v1.0/users/' . $userId . '/drive/recent';
$guzzle = new Client();
$headers = ['Authorization'=>'Bearer ' . $accessToken];
$response = $guzzle->get($url,['headers' => $headers]);
$json = json_decode($response->getBody()->getContents(),true);
To determine whether access token is delegated or application permission, jwt.io service, for instance, could be utilized:
for application permission token, the permissions are provided in the roles claim:
For delegated permission token, the permissions are specified via scp claim instead
How to get access token
function getAccessToken($tenantId, $clientId, $clientSecret){
$guzzle = new Client();
$url = 'https://login.microsoftonline.com/' . $tenantId . '/oauth2/token?api-version=1.0';
$token = json_decode($guzzle->post($url, [
'form_params' => [
'client_id' => $clientId,
'client_secret' => $clientSecret,
'resource' => 'https://graph.microsoft.com/',
'grant_type' => 'client_credentials'
],
])->getBody()->getContents());
return $token->access_token;
}