"unable to get local issuer certificate" when trying to access Microsoft Graph - php

I want to configure my Symfony4 application to read and send e-mails using the msgraph-sdk-php library.
My first experience was this piece of code:
$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',
],
'verify' => false
])->getBody()->getContents());
$accessToken = $token->access_token;
$graph = new Graph();
$graph->setAccessToken($accessToken);
try {
$user = $graph->createRequest("GET", "/me")
->setReturnType(User::class)
->execute();
} catch (GraphException $e) {
$user=(object) ['getGivenName'=>$e->getMessage()];
}
return "Hello, I am $user->getGivenName() ";
But then Symfony shows me an exception page with this message:
cURL error 60: SSL certificate problem: unable to get local issuer certificate
What should I do to overcome this?

On Windows systems cURL can't access the CA-Certs sometimes. So you have to download the CA-Files and add them to Curl. You can download the certificate here:
http://curl.haxx.se/docs/caextract.html
curl_setopt($ch, CURLOPT_CAINFO, __DIR__ . "/certs/cacert.pem");
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
So to fix the problem temporary you can disable the Peer verification but you should do that only for testing.
$client->setDefaultOption('verify', false);
Then it should be possible to connect. To add the certificate you can to the following but then you have to download the certificate first.
$client = new \GuzzleHttp\Client();
$client->setDefaultOption('verify', 'curl-ca-bundle.crt');
Or last solution ad the ca file to your php.ini (The file from curl.haxx.se):
curl.cainfo = "[pathtothisfile]\cacert.pem"

Related

PHP - Connect mailbox office 365 with OAuth

Please forgive my amateurism both in php and on stackoverflow.
I have a PHP script that reads emails from an office 365 mailbox, for this I use a standard connection:
$Inbox = imap_open('{Outlook.office365.com:993/imap/ssl}', 'mabal#mydomain.com', 'mypassword');
Then to read the new emails received, I used the command:
$UnRead = imap_search($Inbox, 'UNSEEN');
Starting 20221001 - October 1, 2022, Microsoft will remove this authentication and require OAuth2 authentication.
I registered my web application at Microsoft Azure and tried several settings. I have done a lot of research that allows me to connect to the mailbox to be able to read the mails without going through an interaction with a user. I've found nothing.
Does anyone have a step-by-step solution to retrieve a variable bound to an "imap_open" or do you have to use a completely different system.
Thanks for your help.
It's been a wild ride for me and my coworkers but we found a solution.
1 - Configure your mail box in Azure
(I didn't do this part so i can't help you more than that ! )
Edit : Thanks to parampal-pooni, this link explains how to configurate in azure.
You will need :
The client Id
The tenant Id
The secret client
The redirect Uri (Set it to http://localhost/test_imap)
2 - Grab a code to get a token
Construct this url :
$TENANT="5-48...";
$CLIENT_ID="c-9c-....";
$SCOPE="https://outlook.office365.com/IMAP.AccessAsUser.All";
$REDIRECT_URI="http://localhost/test_imap";
$authUri = 'https://login.microsoftonline.com/' . $TENANT
. '/oauth2/v2.0/authorize?client_id=' . $CLIENT_ID
. '&scope=' . $SCOPE
. '&redirect_uri=' . urlencode($REDIRECT_URI)
. '&response_type=code'
. '&approval_prompt=auto';
echo($authUri);
Go to the link, connect to the mail box with the passeword.
Once it done, you will be redirect to : http://localhost/test_imap?code=LmpxSnTw...&session_state=b5d713....
Save the code (remove the '&' at the end !) and the session state inside the url.
These codes expired after a few hours !
3 - Get an access token
$CLIENT_ID="c-9c-....";
$CLIENT_SECRET="Y~tN...";
$TENANT="5-48...";
$SCOPE="https://outlook.office365.com/IMAP.AccessAsUser.All offline_access";
$CODE="LmpxSnTw...";
$SESSION="b5d713...";
$REDIRECT_URI="http://localhost/test_imap";
echo "Trying to authenticate the session..";
$url= "https://login.microsoftonline.com/$TENANT/oauth2/v2.0/token";
$param_post_curl = [
'client_id'=>$CLIENT_ID,
'scope'=>$SCOPE,
'code'=>$CODE,
'session_state'=>$SESSION,
'client_secret'=>$CLIENT_SECRET,
'redirect_uri'=>$REDIRECT_URI,
'grant_type'=>'authorization_code' ];
$ch=curl_init();
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_POSTFIELDS, http_build_query($param_post_curl));
curl_setopt($ch,CURLOPT_POST, 1);
curl_setopt($ch,CURLOPT_RETURNTRANSFER, true);
$oResult=curl_exec($ch);
echo "result : \n";
var_dump($oResult);
The access_token given in response is going to work only for a few hours. ( If your script is going to be launch on a daily basic you need to recreate a token. I'm going to show you how in the part 5 ! Save the refresh_token inside $oResult. If you don't have the "refresh_token" you have forgot to put "offline_access" in the scope)
Edit : I forgot to add the redirect_uri in this step, thank jose ayram
4 - Connect to the mail box
Now choose your favorite library ;) ! We will use webklex/php-imap for this example (https://github.com/Webklex/php-imap)
include __DIR__.'/vendor/autoload.php';
use Webklex\PHPIMAP\ClientManager;
$access_token="EH.j8s5z8...";
//$cm = new ClientManager($options = ["options" => ["debug" => true]]);
$cm = new ClientManager();
$client = $cm->make([
'host' => 'outlook.office365.com',
'port' => 993,
'encryption' => 'ssl',
'validate_cert' => false,
'username' => 'mymailbox#domain.com',
'password' => $access_token,
'protocol' => 'imap',
'authentication' => "oauth"
]);
try {
//Connect to the IMAP Server
$client->connect();
$folder = $client->getFolder('INBOX');
$all_messages = $folder->query()->all()->get();
//DONE ! :D
} catch (Exception $e) {
echo 'Exception : ', $e->getMessage(), "\n";
}
5 - Connecting to the mail box everyday
include __DIR__.'/vendor/autoload.php';
use Webklex\PHPIMAP\ClientManager;
$CLIENT_ID="c-9c-....";
$CLIENT_SECRET="Y~tN...";
$TENANT="5-48...";
$REFRESH_TOKEN="EebH9H8S7...";
$url= "https://login.microsoftonline.com/$TENANT/oauth2/v2.0/token";
$param_post_curl = [
'client_id'=>$CLIENT_ID,
'client_secret'=>$CLIENT_SECRET,
'refresh_token'=>$REFRESH_TOKEN,
'grant_type'=>'refresh_token' ];
$ch=curl_init();
curl_setopt($ch,CURLOPT_URL,$url);
curl_setopt($ch,CURLOPT_POSTFIELDS, http_build_query($param_post_curl));
curl_setopt($ch,CURLOPT_POST, 1);
curl_setopt($ch,CURLOPT_RETURNTRANSFER, true);
//ONLY USE CURLOPT_SSL_VERIFYPEER AT FALSE IF YOU ARE IN LOCALHOST !!!
curl_setopt($ch,CURLOPT_SSL_VERIFYPEER, false);// NOT IN LOCALHOST ? ERASE IT !
$oResult=curl_exec($ch);
echo("Trying to get the token.... \n");
if(!empty($oResult)){
echo("Connecting to the mail box... \n");
//The token is a JSON object
$array_php_resul = json_decode($oResult,true);
if( isset($array_php_resul["access_token"]) ){
$access_token = $array_php_resul["access_token"];
//$cm = new ClientManager($options = ["options" => ["debug" => true]]);
$cm = new ClientManager();
$client = $cm->make([
'host' => 'outlook.office365.com',
'port' => 993,
'encryption' => 'ssl',
'validate_cert' => false,
'username' => 'mymailbox#domain.com',
'password' => $access_token,
'protocol' => 'imap',
'authentication' => "oauth"
]);
try {
//Connect to the IMAP Server
$client->connect();
}catch (Exception $e) {
echo 'Exception : ', $e->getMessage(), "\n";
}
}else{
echo('Error : '.$array_php_resul["error_description"]);
}
}
I hope it will help you.
Execute this curl
Replace client_id, client_secret and tenant_id
In scope you can try to use https://outlook.office365.com/.default, https://graph.microsoft.com/.default or https://outlook.office.com/.default
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d 'client_id={CLIENT_ID}&scope=https%3A%2F%2Foutlook.office365.com%2F.default&client_secret={CLIENT_SECRET}&grant_type=client_credentials' 'https://login.microsoftonline.com/{TENANT_ID}/oauth2/v2.0/token'
PHP-imap login
<?php
// ....
$instance = new ClientManager();
$this->client = $instance->make([
'host' => "outlook.office365.com",
'port' => "993",
'encryption' => 'ssl',
'validate_cert' => true,
'username' => "email#outlook.com",
'password' => $curlAcessToken, // Access token curl
'protocol' => 'imap',
'authentication' => "oauth",
]);

A google sheet api http request with "Service account key" auth and without google/apiclient

Google docs contains only examples with google/apiclient php library. But the library contains 14888 php files. This is too much for just one google sheet api request with the "Service account key" auth that I need.
Is there an example with a native http google sheet api request for the "Service account key" auth somewhere?
I believe your goal as follows.
You want to retrieve the access token from the service account without using googleapis for PHP.
You want to use Sheets API with the retrieved access token.
In order to achieve your goal, the sample script is as follows.
Sample script:
Before you use this script, please set the variables of $private_key and $client_email using your values of service account. And also, as the sample script for using Sheets API, please set $spreadsheetId and $range. In this case, for example, please share your Google Spreadsheet on your Google Drive with the email of the service account. By this, your Spreadsheet can be seen by the service account. Please be careful this.
<?php
$private_key = "-----BEGIN PRIVATE KEY-----\n###-----END PRIVATE KEY-----\n"; // private_key of JSON file retrieved by creating Service Account
$client_email = "###"; // client_email of JSON file retrieved by creating Service Account
$scopes = ["https://www.googleapis.com/auth/spreadsheets.readonly"]; // This is a sample scope.
$url = "https://www.googleapis.com/oauth2/v4/token";
$header = array("alg" => "RS256", "typ" => "JWT");
$now = floor(time());
$claim = array(
"iss" => $client_email,
"sub" => $client_email,
"scope" => implode(" ", $scopes),
"aud" => $url,
"exp" => (string)($now + 3600),
"iat" => (string)$now,
);
$signature = base64_encode(json_encode($header, JSON_UNESCAPED_SLASHES)) . "." . base64_encode(json_encode($claim, JSON_UNESCAPED_SLASHES));
$b = "";
openssl_sign($signature, $b, $private_key, "SHA256");
$jwt = $signature . "." . base64_encode($b);
$curl_handle = curl_init();
curl_setopt_array($curl_handle, [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => array(
"assertion" => $jwt,
"grant_type" => "urn:ietf:params:oauth:grant-type:jwt-bearer"
),
]);
$res = curl_exec($curl_handle);
curl_close($curl_handle);
$obj = json_decode($res);
$accessToken = $obj -> {'access_token'};
print($accessToken . "\n"); // You can see the retrieved access token.
// The following script is for a sample for using the access token.
$spreadsheetId = '###'; // Please set the Spreadsheet ID.
$range = 'Sheet1';
$curl_test = curl_init();
curl_setopt($curl_test, CURLOPT_URL, 'https://sheets.googleapis.com/v4/spreadsheets/' . $spreadsheetId .'/values/'. $range);
curl_setopt($curl_test, CURLOPT_HTTPHEADER, array('Authorization: Bearer ' . $accessToken));
curl_setopt($curl_test, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($curl_test);
print($response);
When you run this script, the access token is retrieved from the service account, and as a sample, the values of "Sheet1" of $spreadsheetId are retrieved using Sheets API.
References:
Using OAuth 2.0 for Server to Server Applications
jwt.io
Method: spreadsheets.values.get
Although this doesn't answer your question (this might be useful to others who might stumble on this 14888-file issue), please note that you can also reduce the number of classes by specifying which services to use in your composer.json file.
In your case you would need to use the following;
{
"require": {
"google/apiclient": "^2.7"
},
"scripts": {
"post-update-cmd": "Google\\Task\\Composer::cleanup"
},
"extra": {
"google/apiclient-services": [
"Sheets"
]
}
}
Refer to: https://packagist.org/packages/google/apiclient

Amazon S3 working in production but not from localhost

I cannot upload to my s3 bucket from localhost despite it working fine in production. No errors are caught during upload but the files never appear (neither pdf nor images). I have no blocking of public access (which appears to be the issue for most people).
I saw one person on here solve their problem by switching on allow_url_fopen in their php settings. Mine was already on.
EDIT: I should note that I have a python script which I run from Windows which uses the bucket without any problem. It makes me think it has something to do with my WAMP server settings but neither my php nor apache logs show anything and the settings look fine.
Any other ideas?
require_once '../../includes/aws/aws-autoloader.php';
$msg = "";
if( isset($_POST['fileUpload']) ) {
$fileUpload = $_POST['fileUpload'];
$dirPath = "drives/nota_simple/projects/";
$pdf_parts = explode(";base64,", $fileUpload["pdf"]);
$pdf_base64 = $pdf_parts[1];
$s3 = new Aws\S3\S3Client([
'region' => 'eu-west-1',
'version' => '2006-03-01',
'credentials' => [
'key' => "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
'secret' => "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
]
]);
$projectFolder = utf8_decode(filter_var($fileUpload['fileProject'], FILTER_SANITIZE_STRING));
$dirPath .= $projectFolder;
$dirPath .= '/' . $fileUpload['fileIdTask'];
$id_image = [taken from DB]
$key = $dirPath .'/'.$id_image.'.'.$fileUpload['fileExt'];
try { // Guardamos nota_simple
$result = $s3->putObject([
'Bucket' => 'BUCKET_NAME',
'Key' => $key,
'Body' => base64_decode($pdf_base64),
'ContentType' => 'application/' . $fileUpload['fileExt'],
'ACL' => 'public-read'
]);
}
catch(S3Exception $e) {
echo $e->getMessage() . "\n";
$msg = "Fallo al subir nota_simple: " . $e->getMessage();
$response = array("error" => $msg);
}
$response = array("success" => "Guardado con suceso.");
The issue was within my localhost server settings:
AWS HTTP error: cURL error 77: error setting certificate verify locations: CAfile: C:/wamp64/bin/php7.0.33/extras/ssl/cacert.pem
This was not the correct path.
I added the correct path to php.ini (as seen below) and restarted the server. Now it works!
curl.cainfo="c:/wamp64/bin/php/php7.0.33/extras/ssl/cacert.pem"
openssl.cafile="c:/wamp64/bin/php/php7.0.33/extras/ssl/cacert.pem"

PHP Office 365 Graph authentication

I am having trouble authenticating and accessing the Microsoft Graph API via PHP.
I have the Azure app all setup with clientID and clientSecret codes, as well as the correct redirectURI.
When I try to view this PHP code in web browser, I get a HTTP 500 error.
I have the below code:
<?php
use Microsoft\Graph\Graph;
use Microsoft\Graph\Model;
$clientSecret = 'SAMPLE';
$clientId = 'SAMPLE';
$redirectUri = 'SAMPLE';
$tenantId = 'SAMPLE';
$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;
$graph = new Graph();
$graph->setAccessToken($accessToken);
$user = $graph->createRequest("GET", "/me")
->setReturnType(Model\User::class)
->execute();
echo "<script type='text/javascript'>alert('$user->getGivenName()');</script>";
?>
I am hoping someone can provide anything useful, as I can't seem to find documentation anywhere on this.
I have also had to install composer.phar on web hosting via SSH, and am not sure if that is running correctly / in the correct directory. It does have all the required add-ins installed though such as guzzleHTTP, Microsoft Graph, and oauth2.

Curl GET Request to the spotify Authorization API

I need some help with my Curl GET Request to the Spotify API.
The API has three different ways/endpoints to get an authorization.
I read some articles, to find the correct syntax to send the request. But i always get an error. If i post the url into my brwoser it works perfectly, also with the redirect uri.
But it doesnt work with the Curl GET Request.
It sounds stupid, but i spend the last three days with this Problem.
My code:
<?php
$client_id = 'myClientID';
$redirect_url = 'http://mywebsite/first/page.php';
$scope = 'user-read-private%20user-read-email';
$data = array(
'client_id' => $client_id,
'response_type' => 'code',
'redirect_uri' => $redirect_url,
'state' => stateHash(), // Create a random hash
'scope' => $scope,
'show_dialog' => 'true'
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://accounts.spotify.com/authorize' . http_build_query($data));
curl_setopt($ch, CURLOPT_HTTPGET, TRUE);
$result=curl_exec($ch);
echo $result;
The error from the API Shows me this:
or i got an "1" as response.
I hope that i get some nice tips :)
There is a package for Spotify web API try using that
composer require jwilsson/spotify-web-api-php
Before using the Spotify Web API, you'll need to create an app at Spotify’s developer site.
Simple example displaying a user's profile:
require 'vendor/autoload.php';
$session = new SpotifyWebAPI\Session(
'CLIENT_ID',
'CLIENT_SECRET',
'REDIRECT_URI'
);
$api = new SpotifyWebAPI\SpotifyWebAPI();
if (isset($_GET['code'])) {
$session->requestAccessToken($_GET['code']);
$api->setAccessToken($session->getAccessToken());
print_r($api->me());
} else {
$options = [
'scope' => [
'user-read-email',
],
];
header('Location: ' . $session->getAuthorizeUrl($options));
die();
}
For more instructions and examples, check out the documentation.

Categories