Skip to content

Commit 9535268

Browse files
committed
Add AppServiceCredential.php
1 parent 6160252 commit 9535268

14 files changed

+131
-27
lines changed

composer.json

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,5 @@
2828
"name": "Viet Pham",
2929
"email": "viet@webonyx.com"
3030
}
31-
],
32-
"config": {
33-
"allow-plugins": {
34-
"php-http/discovery": true
35-
}
36-
}
31+
]
3732
}

src/Client/AadClient.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class AadClient
2525

2626
private int $timeout;
2727

28-
public function __construct(private string $tenantId, private string $clientId, ?HttpClientInterface $httpClient = null, ?LoggerInterface $logger = null, int $timeout = 5)
28+
public function __construct(private string $tenantId, private string $clientId, ?HttpClientInterface $httpClient = null, ?LoggerInterface $logger = null, int $timeout = 20)
2929
{
3030
$this->logger = $logger ?? new NullLogger();
3131
$this->httpClient = $httpClient ?? HttpClient::create();
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
<?php
2+
3+
namespace Azure\Identity\Credential;
4+
5+
use Azure\Identity\EnvVar;
6+
use Azure\Identity\Exception\InvalidScopesException;
7+
use Azure\Identity\Token;
8+
use Azure\Identity\TokenInterface;
9+
use Psr\Log\LoggerInterface;
10+
use Symfony\Component\HttpClient\HttpClient;
11+
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
12+
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
13+
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
14+
use Symfony\Contracts\HttpClient\HttpClientInterface;
15+
16+
class AppServiceCredential implements AzureCredentialInterface
17+
{
18+
private HttpClientInterface $httpClient;
19+
20+
public function __construct(public ?string $clientId = null, ?HttpClientInterface $httpClient = null, private ?LoggerInterface $logger = null)
21+
{
22+
$this->httpClient = $httpClient ?? HttpClient::create();
23+
}
24+
25+
public function getToken(array $scopes, array $options = []): ?TokenInterface
26+
{
27+
$url = EnvVar::get('IDENTITY_ENDPOINT');
28+
$secret = EnvVar::get('IDENTITY_HEADER');
29+
30+
if (!$url || !$secret) {
31+
$this->logger->warning('App Service managed identity configuration not found in environment');
32+
33+
return null;
34+
}
35+
36+
$clientId = $this->clientId ?? EnvVar::get('AZURE_CLIENT_ID');
37+
$params = [
38+
'api-version' => '2019-08-01',
39+
'resource' => $this->scopesToResource($scopes),
40+
];
41+
42+
if ($clientId) {
43+
$params['client_id'] = $clientId;
44+
}
45+
46+
try {
47+
$response = $this->httpClient->request('GET', $url, [
48+
'query' => array_merge($params, $options),
49+
'headers' => [
50+
'X-IDENTITY-HEADER' => $secret
51+
]
52+
]);
53+
54+
$result = $response->toArray();
55+
} catch (DecodingExceptionInterface $e) {
56+
$this->logger->info('Failed to decode token response.', ['exception' => $e]);
57+
58+
return null;
59+
} catch (TransportExceptionInterface|HttpExceptionInterface $e) {
60+
$this->logger->info('Failed to fetch token.', ['exception' => $e]);
61+
62+
return null;
63+
}
64+
65+
// App service return expires_on instead expires_in
66+
$result['expires_in'] = floor($result['expires_on'] - time());
67+
68+
return Token::fromArray($result);
69+
}
70+
71+
/**
72+
* Convert an AADv2 scope to an AADv1 resource
73+
* @param array $scopes
74+
* @return string
75+
*/
76+
private function scopesToResource(array $scopes): string
77+
{
78+
if (count($scopes) !== 1) {
79+
throw new InvalidScopesException('AppServiceCredential requires exactly one scope per token request.');
80+
}
81+
82+
$resource = $scopes[0];
83+
if (str_ends_with($resource, '/.default')) {
84+
$resource = substr($resource, 0, strlen('/.default') * -1);
85+
}
86+
87+
return $resource;
88+
}
89+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?php
2+
3+
namespace Azure\Identity\Credential;
4+
5+
use Azure\Identity\TokenInterface;
6+
7+
interface AzureCredentialInterface
8+
{
9+
public function getToken(array $scopes, array $options = []): ?TokenInterface;
10+
}

src/Credential/CacheCredential.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
use Azure\Identity\TokenInterface;
66
use Symfony\Contracts\Service\ResetInterface;
77

8-
class CacheCredential implements TokenCredentialInterface, ResetInterface
8+
class CacheCredential implements AzureCredentialInterface, ResetInterface
99
{
1010
/**
1111
* @var (null|TokenInterface)[]
1212
*/
1313
private array $cache = [];
1414

15-
public function __construct(private TokenCredentialInterface $decorated)
15+
public function __construct(private AzureCredentialInterface $decorated)
1616
{
1717

1818
}

src/Credential/ChainTokenCredential.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@
77
use Symfony\Contracts\HttpClient\HttpClientInterface;
88
use Symfony\Contracts\Service\ResetInterface;
99

10-
class ChainTokenCredential implements TokenCredentialInterface, ResetInterface
10+
class ChainTokenCredential implements AzureCredentialInterface, ResetInterface
1111
{
1212
/**
13-
* @var TokenCredentialInterface[]
13+
* @var AzureCredentialInterface[]
1414
*/
1515
private array $credentials;
1616

1717
/**
18-
* @var TokenCredentialInterface[]
18+
* @var AzureCredentialInterface[]
1919
*/
2020
private array $lastSuccessfulCredentials = [];
2121

@@ -24,7 +24,7 @@ public function __construct(array $credentials)
2424
$this->credentials = $credentials;
2525
}
2626

27-
public static function createDefaultChain(array $config, ?HttpClientInterface $httpClient = null, ?LoggerInterface $logger = null): TokenCredentialInterface
27+
public static function createDefaultChain(array $config, ?HttpClientInterface $httpClient = null, ?LoggerInterface $logger = null): AzureCredentialInterface
2828
{
2929
return new ChainTokenCredential([
3030
new EnvironmentCredential($httpClient, $logger),

src/Credential/ClientAssertionCredential.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use Psr\Log\LoggerInterface;
88
use Symfony\Contracts\HttpClient\HttpClientInterface;
99

10-
class ClientAssertionCredential implements TokenCredentialInterface
10+
class ClientAssertionCredential implements AzureCredentialInterface
1111
{
1212
private AadClient $client;
1313

src/Credential/ClientSecretCredential.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use Psr\Log\LoggerInterface;
88
use Symfony\Contracts\HttpClient\HttpClientInterface;
99

10-
class ClientSecretCredential implements TokenCredentialInterface
10+
class ClientSecretCredential implements AzureCredentialInterface
1111
{
1212
private AadClient $client;
1313

src/Credential/DefaultAzureCredential.php

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,11 @@
66
use Azure\Identity\TokenInterface;
77
use Psr\Log\LoggerInterface;
88
use Psr\Log\NullLogger;
9-
use Symfony\Component\HttpClient\HttpClient;
109
use Symfony\Contracts\HttpClient\HttpClientInterface;
1110

12-
class DefaultAzureCredential implements TokenCredentialInterface
11+
class DefaultAzureCredential implements AzureCredentialInterface
1312
{
14-
private TokenCredentialInterface $cacheable;
13+
private AzureCredentialInterface $cacheable;
1514

1615
public function __construct(array $config = [], ?HttpClientInterface $httpClient = null, ?LoggerInterface $logger = null)
1716
{

src/Credential/EnvironmentCredential.php

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* - `AZURE_TENANT_ID`: The Azure Active Directory tenant (directory) ID.
1313
* - `AZURE_CLIENT_ID`: The client (application) ID of an App Registration in the tenant.
1414
*/
15-
class EnvironmentCredential implements TokenCredentialInterface
15+
class EnvironmentCredential implements AzureCredentialInterface
1616
{
1717
private $logger;
1818

@@ -26,8 +26,14 @@ public function __construct(?HttpClientInterface $httpClient = null, ?LoggerInte
2626

2727
public function getToken(array $scopes, array $options = []): ?TokenInterface
2828
{
29-
$tenantId = EnvVar::get('AZURE_TENANT_ID');
3029
$clientId = EnvVar::get('AZURE_CLIENT_ID');
30+
31+
if (EnvVar::get('IDENTITY_ENDPOINT') && EnvVar::get('IDENTITY_HEADER')) {
32+
$client = new AppServiceCredential($clientId, $this->httpClient, $this->logger);
33+
return $client->getToken($scopes, $options);
34+
}
35+
36+
$tenantId = EnvVar::get('AZURE_TENANT_ID');
3137
if (!$tenantId || !$clientId) {
3238
return null;
3339
}

0 commit comments

Comments
 (0)