Skip to content

Commit 25eaa5e

Browse files
committed
Authentication: trusted publishing setup for artifact publishing
1 parent e389d73 commit 25eaa5e

File tree

3 files changed

+83
-2
lines changed

3 files changed

+83
-2
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
"php-http/discovery": "^1.0",
1919
"psr/http-client-implementation": "^1.0",
2020
"php-http/message-factory": "^1.0",
21-
"psr/http-message-implementation": "^1.0"
21+
"psr/http-message-implementation": "^1.0",
22+
"private-packagist/oidc-identities": "dev-main"
2223
},
2324
"require-dev": {
2425
"friendsofphp/php-cs-fixer": "^3.0",

src/Client.php

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,26 @@
1616
use PrivatePackagist\ApiClient\HttpClient\Plugin\ExceptionThrower;
1717
use PrivatePackagist\ApiClient\HttpClient\Plugin\PathPrepend;
1818
use PrivatePackagist\ApiClient\HttpClient\Plugin\RequestSignature;
19+
use PrivatePackagist\ApiClient\HttpClient\Plugin\TrustedPublishingTokenExchange;
20+
use Psr\Log\LoggerInterface;
21+
use Psr\Log\NullLogger;
1922

2023
class Client
2124
{
2225
/** @var HttpPluginClientBuilder */
2326
private $httpClientBuilder;
2427
/** @var ResponseMediator */
2528
private $responseMediator;
29+
/** @var LoggerInterface */
30+
private $logger;
2631

2732
/** @param string $privatePackagistUrl */
28-
public function __construct(?HttpPluginClientBuilder $httpClientBuilder = null, $privatePackagistUrl = null, ?ResponseMediator $responseMediator = null)
33+
public function __construct(?HttpPluginClientBuilder $httpClientBuilder = null, $privatePackagistUrl = null, ?ResponseMediator $responseMediator = null, ?LoggerInterface $logger = null)
2934
{
3035
$this->httpClientBuilder = $builder = $httpClientBuilder ?: new HttpPluginClientBuilder();
3136
$privatePackagistUrl = $privatePackagistUrl ? : 'https://packagist.com';
3237
$this->responseMediator = $responseMediator ? : new ResponseMediator();
38+
$this->logger = $logger ? : new NullLogger();
3339

3440
$builder->addPlugin(new Plugin\AddHostPlugin(Psr17FactoryDiscovery::findUriFactory()->createUri($privatePackagistUrl)));
3541
$builder->addPlugin(new PathPrepend('/api'));
@@ -58,6 +64,15 @@ public function authenticate(
5864
$this->httpClientBuilder->addPlugin(new RequestSignature($key, $secret));
5965
}
6066

67+
/**
68+
* @param string $organizationUrlName
69+
* @param string $packageName
70+
*/
71+
public function authenticateWithTrustedPublishing($organizationUrlName, $packageName) {
72+
$this->httpClientBuilder->removePlugin(TrustedPublishingTokenExchange::class);
73+
$this->httpClientBuilder->addPlugin(new TrustedPublishingTokenExchange($organizationUrlName, $packageName, $this->getHttpClientBuilder(), $this->logger));
74+
}
75+
6176
public function credentials()
6277
{
6378
return new Api\Credentials($this, $this->responseMediator);
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<?php declare(strict_types=1);
2+
3+
namespace PrivatePackagist\ApiClient\HttpClient\Plugin;
4+
5+
use Http\Client\Common\HttpMethodsClient;
6+
use Http\Client\Common\Plugin;
7+
use Http\Discovery\Psr17FactoryDiscovery;
8+
use Http\Discovery\Psr18ClientDiscovery;
9+
use PrivatePackagist\ApiClient\HttpClient\HttpPluginClientBuilder;
10+
use PrivatePackagist\OIDC\Identities\TokenGenerator;
11+
use Psr\Http\Message\RequestInterface;
12+
use Psr\Log\LoggerInterface;
13+
14+
class TrustedPublishingTokenExchange implements Plugin
15+
{
16+
use Plugin\VersionBridgePlugin;
17+
18+
/** @var string */
19+
private $organizationUrlName;
20+
/** @var string */
21+
private $packageName;
22+
/** @var HttpPluginClientBuilder $httpPluginClientBuilder */
23+
private $httpPluginClientBuilder;
24+
/** @var LoggerInterface */
25+
private $logger;
26+
27+
/**
28+
* @param string $organizationUrlName
29+
* @param string $packageName
30+
*/
31+
public function __construct($organizationUrlName, $packageName, HttpPluginClientBuilder $httpPluginClientBuilder, LoggerInterface $logger)
32+
{
33+
$this->organizationUrlName = $organizationUrlName;
34+
$this->packageName = $packageName;
35+
$this->httpPluginClientBuilder = $httpPluginClientBuilder;
36+
$this->logger = $logger;
37+
}
38+
39+
protected function doHandleRequest(RequestInterface $request, callable $next, callable $first)
40+
{
41+
$this->httpPluginClientBuilder->removePlugin(self::class);
42+
43+
$privatePackagistHttpclient = $this->httpPluginClientBuilder->getHttpClient();
44+
$audience = json_decode((string) $privatePackagistHttpclient->get('/oidc/audience')->getBody(), true);
45+
$this->logger->debug('Audience', $audience);
46+
47+
$oidcHttpClient = new HttpMethodsClient(
48+
Psr18ClientDiscovery::find(),
49+
Psr17FactoryDiscovery::findRequestFactory(),
50+
Psr17FactoryDiscovery::findStreamFactory()
51+
);
52+
53+
$tokenGenerator = new TokenGenerator($this->logger, $oidcHttpClient);
54+
$token = $tokenGenerator->generate($audience['audience']);
55+
if (!$token) {
56+
return $next($request);
57+
}
58+
59+
$apiCredentials = json_decode((string) $privatePackagistHttpclient->post('/oidc/token-exchange/' . $this->organizationUrlName . '/' . $this->packageName, ['Authorization' => 'Bearer ' . $token->token])->getBody(), true);
60+
61+
$this->httpPluginClientBuilder->addPlugin($requestSignature = new RequestSignature($apiCredentials['key'], $apiCredentials['secret']));
62+
63+
return $requestSignature->handleRequest($request, $next, $first);
64+
}
65+
}

0 commit comments

Comments
 (0)