microsoftgraph / msgraph-sdk-php

Microsoft Graph Library for PHP.
Other
552 stars 141 forks source link

msgraph sdk php + proxy not working #1483

Open 0x1234567890 opened 3 months ago

0x1234567890 commented 3 months ago

Hello, I only have the option of enabling Internet access via HTTP proxy on my environment. I always get the message Fatal error: Uncaught GuzzleHttp\Exception\ConnectException: cURL error 6: Could not resolve host: login.microsoftonline.com. The domain is resolved via the proxy. I just have the assumption that the proxy configuration is not applied.

Unfortunately, the specified proxy method does not work. Under a different environment where there is no proxy in the way, the code works.

I have already tested the following in the GuzzleConfig area:

'proxy' => [
        'http' => 'http://$proxyHost:$proxyPort', 
        'https' => 'http://$proxyHost:$proxyPort', 
    ]

Unfortunately without success.

This code is supposed to send an email via MSGraph SDK from a specific mailbox via a company application (also works on environments without proxy):

<?php

require("config.php");
require_once("assets/lib/vendor/autoload.php");

use Microsoft\Kiota\Authentication\Oauth\AuthorizationCodeContext;
use Microsoft\Graph\Core\Authentication\GraphPhpLeagueAuthenticationProvider;
use Microsoft\Graph\GraphServiceClient;
use Microsoft\Kiota\Abstractions\ApiException;
use Microsoft\Kiota\Authentication\Oauth\ClientCredentialContext;
use Microsoft\Graph\Core\GraphClientFactory;
use Microsoft\Graph\GraphRequestAdapter;
use Microsoft\Graph\Graph;

use Microsoft\Graph\Generated\Users\Item\SendMail\SendMailPostRequestBody;
use Microsoft\Graph\Generated\Models\BodyType;
use Microsoft\Graph\Generated\Models\EmailAddress;
use Microsoft\Graph\Generated\Models\ItemBody;
use Microsoft\Graph\Generated\Models\Message;
use Microsoft\Graph\Generated\Models\Recipient;
use Microsoft\Graph\Generated\Models\InternetMessageHeader;

$scopes = ['https://graph.microsoft.com/.default'];

// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
$tenantId = AZURE_TENANTID;

// Values from app registration
$clientId = AZURE_CLIENTID;
$clientSecret = AZURE_CLIENTSECRET;

// Microsoft\Kiota\Authentication\Oauth\AuthorizationCodeContext
$tokenRequestContext = new ClientCredentialContext(
    $tenantId,
    $clientId,
    $clientSecret
);
$authProvider = new GraphPhpLeagueAuthenticationProvider($tokenRequestContext);

// Proxy-Einstellungen
$proxyHost = PROXY_HOST;
$proxyPort = PROXY_PORT;

// Create HTTP client with a Guzzle config
// to specify proxy
$guzzleConfig = [
    "proxy" => "http://$proxyHost:$proxyPort"
];
$httpClient = GraphClientFactory::createWithConfig($guzzleConfig);
//$httpClient = GraphClientFactory::setClientConfig($guzzleConfig)::create();
$requestAdapter = new GraphRequestAdapter($authProvider, $httpClient);
$graphServiceClient = GraphServiceClient::createWithRequestAdapter($requestAdapter);

$graphServiceClient = new GraphServiceClient($tokenRequestContext, $scopes);

$requestBody = new SendMailPostRequestBody();
$message = new Message();
$message->setSubject('9/9/2018: concert');
$messageBody = new ItemBody();
$messageBody->setContentType(new BodyType('html'));
$messageBody->setContent('The group represents Nevada.');
$message->setBody($messageBody);
$toRecipientsRecipient1 = new Recipient();
$toRecipientsRecipient1EmailAddress = new EmailAddress();
$toRecipientsRecipient1EmailAddress->setAddress('my@email.com');
$toRecipientsRecipient1->setEmailAddress($toRecipientsRecipient1EmailAddress);
$toRecipientsArray []= $toRecipientsRecipient1;
$message->setToRecipients($toRecipientsArray);

$internetMessageHeadersInternetMessageHeader1 = new InternetMessageHeader();
$internetMessageHeadersInternetMessageHeader1->setName('x-custom-header-group-name');
$internetMessageHeadersInternetMessageHeader1->setValue('Nevada');
$internetMessageHeadersArray []= $internetMessageHeadersInternetMessageHeader1;
$internetMessageHeadersInternetMessageHeader2 = new InternetMessageHeader();
$internetMessageHeadersInternetMessageHeader2->setName('x-custom-header-group-id');
$internetMessageHeadersInternetMessageHeader2->setValue('NV001');
$internetMessageHeadersArray []= $internetMessageHeadersInternetMessageHeader2;
$message->setInternetMessageHeaders($internetMessageHeadersArray);

$requestBody->setMessage($message);

$result = $graphServiceClient->users()->byUserId('sender@email.com')->sendMail()->post($requestBody)->wait();
print_r($result)
?>

A simple PHP CURL proxy test works without any problems:


$url = 'https://login.microsoftonline.com/c06xxxxxxxxxxbc7d/oauth2/v2.0/token';
// cURL-Initialisierung
$ch = curl_init();

// cURL-Optionen setzen
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_PROXY, $proxyHost);
curl_setopt($ch, CURLOPT_PROXYPORT, $proxyPort);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

// Anfrage durchführen
$response = curl_exec($ch);

// Überprüfen auf Fehler
if(curl_errno($ch)) {
    echo 'cURL-Fehler: ' . curl_error($ch);
} else {
    // Erfolgreiche Anfrage
    echo $response;
}

// cURL schließen
curl_close($ch);

How can i solve this Problem?

nikolaiessel commented 1 month ago

The problem is, that this only setups the proxy for graph requests. But graph needs authentication tokens which are provided by GraphPhpLeagueAuthenticationProvider - and there no proxy is given.

There exists no parameter for it and Microsoft\Kiota\Authentication\Oauth\ProviderFactory creates all options for the League\OAuth2\Client\Provider\GenericProvider independend, so the GuzzleClient can never get a proxy setting.

Microsoft\Kiota\Authentication\Oauth\ProviderFactory does acceppt a $collaborators argument - where we could inject a ['httpClient' => new GuzzleClient(...)], but the GraphPhpLeagueAccessTokenProvider does deliver an empty array.

So I needed clone the two wrapper to add the $collaborators parameter:

class AuthenticationProvider extends \Microsoft\Kiota\Abstractions\Authentication\BaseBearerTokenAuthenticationProvider
{   
    /**
     * @param array<string, object> $collaborators
     */
    public function __construct(
        \Microsoft\Kiota\Authentication\Oauth\TokenRequestContext $tokenRequestContext,
        array $scopes = [],
        string $nationalCloud = \Microsoft\Graph\Core\NationalCloud::GLOBAL,
        array $collaborators = []
    ) {
        parent::__construct(new AccessTokenProvider($tokenRequestContext, $scopes, $nationalCloud, $collaborators));
    }
}

class AccessTokenProvider extends \Microsoft\Kiota\Authentication\PhpLeagueAccessTokenProvider
{
    public const NATIONAL_CLOUD_TO_AZURE_AD_ENDPOINT = [
        \Microsoft\Graph\Core\NationalCloud::GLOBAL => 'https://login.microsoftonline.com',
        \Microsoft\Graph\Core\NationalCloud::US_GOV => 'https://login.microsoftonline.us',
        \Microsoft\Graph\Core\NationalCloud::CHINA => 'https://login.chinacloudapi.cn'
    ];

    /**
     * @param array<string, object> $collaborators
     */
    public function __construct(
        \Microsoft\Kiota\Authentication\Oauth\TokenRequestContext $tokenRequestContext,
        array $scopes = [],
        string $nationalCloud = \Microsoft\Graph\Core\NationalCloud::GLOBAL,
        array $collaborators = []
    )
    {
        $allowedHosts = [
            "graph.microsoft.com",
            "graph.microsoft.us",
            "dod-graph.microsoft.us",
            "microsoftgraph.chinacloudapi.cn",
            "canary.graph.microsoft.com",
            "graph.microsoft-ppe.com"
        ];
        $tokenBaseServiceUrl = self::NATIONAL_CLOUD_TO_AZURE_AD_ENDPOINT[$nationalCloud] ??
            self::NATIONAL_CLOUD_TO_AZURE_AD_ENDPOINT[\Microsoft\Graph\Core\NationalCloud::GLOBAL];
        $oauthProvider = \Microsoft\Kiota\Authentication\Oauth\ProviderFactory::create(
            $tokenRequestContext,
            $collaborators,
            $tokenBaseServiceUrl,
            $nationalCloud
        );
        parent::__construct($tokenRequestContext, $scopes, $allowedHosts, $oauthProvider);
    }
}

now the proxy works when used in my class like that:

        $httpClient = GraphClientFactory::createWithConfig(["proxy" => Configuration::getProxy()]);
        $this->_graph = GraphServiceClient::createWithRequestAdapter(
            new GraphRequestAdapter(
                new AuthenticationProvider(
                    $tokenRequestContext,
                    [".default"],
                    \Microsoft\Graph\Core\NationalCloud::GLOBAL,
                    ["httpClient" => $httpClient]
                ),
                $httpClient
            )
        );
0x1234567890 commented 1 month ago

Thank you, So will an update fix the whole thing?

I also tried to use a Guzzeconfig such as verify = false but that didn't work.

How should I use this in my example?

this instruction does not work: https://learn.microsoft.com/en-us/graph/sdks/customize-client?tabs=PHP

nikolaiessel commented 1 month ago

Depends if someone/some team does work on it. I had the same problem yesterday and debugged the libraries to identify the problem and find a workaround.

Maybe your version should be somelike this, but you have to check/modify yourself

<?php

require("config.php");
require_once("assets/lib/vendor/autoload.php");

use Microsoft\Kiota\Authentication\Oauth\AuthorizationCodeContext;
use Microsoft\Graph\Core\Authentication\GraphPhpLeagueAuthenticationProvider;
use Microsoft\Graph\GraphServiceClient;
use Microsoft\Kiota\Abstractions\ApiException;
use Microsoft\Kiota\Authentication\Oauth\ClientCredentialContext;
use Microsoft\Graph\Core\GraphClientFactory;
use Microsoft\Graph\GraphRequestAdapter;
use Microsoft\Graph\Graph;

use Microsoft\Graph\Generated\Users\Item\SendMail\SendMailPostRequestBody;
use Microsoft\Graph\Generated\Models\BodyType;
use Microsoft\Graph\Generated\Models\EmailAddress;
use Microsoft\Graph\Generated\Models\ItemBody;
use Microsoft\Graph\Generated\Models\Message;
use Microsoft\Graph\Generated\Models\Recipient;
use Microsoft\Graph\Generated\Models\InternetMessageHeader;

$scopes = ['https://graph.microsoft.com/.default'];

// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
$tenantId = AZURE_TENANTID;

// Values from app registration
$clientId = AZURE_CLIENTID;
$clientSecret = AZURE_CLIENTSECRET;

// Create HTTP client with a Guzzle config to specify proxy
$guzzleConfig = [
    "proxy" => "http://$proxyHost:$proxyPort"
];
$httpClient = GraphClientFactory::createWithConfig($guzzleConfig);

// Microsoft\Kiota\Authentication\Oauth\AuthorizationCodeContext
$tokenRequestContext = new ClientCredentialContext(
    $tenantId,
    $clientId,
    $clientSecret
);

$authProvider = new AuthenticationProvider(
                    $tokenRequestContext ,
                    $scopes,
                    \Microsoft\Graph\Core\NationalCloud::GLOBAL,
                    ["httpClient" => $httpClient]
                );

// Proxy-Einstellungen
$proxyHost = PROXY_HOST;
$proxyPort = PROXY_PORT;

$requestAdapter = new GraphRequestAdapter($authProvider, $httpClient);
$graphServiceClient = GraphServiceClient::createWithRequestAdapter($requestAdapter);

$graphServiceClient = new GraphServiceClient($tokenRequestContext, $scopes);

$requestBody = new SendMailPostRequestBody();
$message = new Message();
$message->setSubject('9/9/2018: concert');
$messageBody = new ItemBody();
$messageBody->setContentType(new BodyType('html'));
$messageBody->setContent('The group represents Nevada.');
$message->setBody($messageBody);
$toRecipientsRecipient1 = new Recipient();
$toRecipientsRecipient1EmailAddress = new EmailAddress();
$toRecipientsRecipient1EmailAddress->setAddress('my@email.com');
$toRecipientsRecipient1->setEmailAddress($toRecipientsRecipient1EmailAddress);
$toRecipientsArray []= $toRecipientsRecipient1;
$message->setToRecipients($toRecipientsArray);

$internetMessageHeadersInternetMessageHeader1 = new InternetMessageHeader();
$internetMessageHeadersInternetMessageHeader1->setName('x-custom-header-group-name');
$internetMessageHeadersInternetMessageHeader1->setValue('Nevada');
$internetMessageHeadersArray []= $internetMessageHeadersInternetMessageHeader1;
$internetMessageHeadersInternetMessageHeader2 = new InternetMessageHeader();
$internetMessageHeadersInternetMessageHeader2->setName('x-custom-header-group-id');
$internetMessageHeadersInternetMessageHeader2->setValue('NV001');
$internetMessageHeadersArray []= $internetMessageHeadersInternetMessageHeader2;
$message->setInternetMessageHeaders($internetMessageHeadersArray);

$requestBody->setMessage($message);

$result = $graphServiceClient->users()->byUserId('sender@email.com')->sendMail()->post($requestBody)->wait();
print_r($result);

class AuthenticationProvider extends \Microsoft\Kiota\Abstractions\Authentication\BaseBearerTokenAuthenticationProvider
{   
    /**
     * @param array<string, object> $collaborators
     */
    public function __construct(
        \Microsoft\Kiota\Authentication\Oauth\TokenRequestContext $tokenRequestContext,
        array $scopes = [],
        string $nationalCloud = \Microsoft\Graph\Core\NationalCloud::GLOBAL,
        array $collaborators = []
    ) {
        parent::__construct(new AccessTokenProvider($tokenRequestContext, $scopes, $nationalCloud, $collaborators));
    }
}

class AccessTokenProvider extends \Microsoft\Kiota\Authentication\PhpLeagueAccessTokenProvider
{
    public const NATIONAL_CLOUD_TO_AZURE_AD_ENDPOINT = [
        \Microsoft\Graph\Core\NationalCloud::GLOBAL => 'https://login.microsoftonline.com',
        \Microsoft\Graph\Core\NationalCloud::US_GOV => 'https://login.microsoftonline.us',
        \Microsoft\Graph\Core\NationalCloud::CHINA => 'https://login.chinacloudapi.cn'
    ];

    /**
     * @param array<string, object> $collaborators
     */
    public function __construct(
        \Microsoft\Kiota\Authentication\Oauth\TokenRequestContext $tokenRequestContext,
        array $scopes = [],
        string $nationalCloud = \Microsoft\Graph\Core\NationalCloud::GLOBAL,
        array $collaborators = []
    )
    {
        $allowedHosts = [
            "graph.microsoft.com",
            "graph.microsoft.us",
            "dod-graph.microsoft.us",
            "microsoftgraph.chinacloudapi.cn",
            "canary.graph.microsoft.com",
            "graph.microsoft-ppe.com"
        ];
        $tokenBaseServiceUrl = self::NATIONAL_CLOUD_TO_AZURE_AD_ENDPOINT[$nationalCloud] ??
            self::NATIONAL_CLOUD_TO_AZURE_AD_ENDPOINT[\Microsoft\Graph\Core\NationalCloud::GLOBAL];
        $oauthProvider = \Microsoft\Kiota\Authentication\Oauth\ProviderFactory::create(
            $tokenRequestContext,
            $collaborators,
            $tokenBaseServiceUrl,
            $nationalCloud
        );
        parent::__construct($tokenRequestContext, $scopes, $allowedHosts, $oauthProvider);
    }
}
?>

Edit: fixed code quotes

0x1234567890 commented 1 month ago

This workaround worked for me:

Very dirty Workaround: edit "vendor/guzzlehttp/guzzle/src/Handler/CurlFactory.php"

add line: $options['proxy'] = "http://host:port"; image

or for SSL Verify: image

nikolaiessel commented 1 month ago

I wouldn´t do this. Setting a hard verify=false means you disable every certifcate checks in nearly every request your code do, as most requests are executed by guzzle these days. So if your code calls for "Bill Gates", someone answers "thats me, I am Bill Gates" and with this setting your code says "hi, I never have seen you, but I trust you that you are Bill Gates"

0x1234567890 commented 1 month ago

I wouldn´t do this. Setting a hard verify=false means you disable every certifcate checks in nearly every request your code do, as most requests are executed by guzzle these days. So if your code calls for "Bill Gates", someone answers "thats me, I am Bill Gates" and with this setting your code says "hi, I never have seen you, but I trust you that you are Bill Gates"

I am aware of this, I use the option for testing on a test environment. (not on the Internet, only internal communication)