Skip to content

Commit 4f3084c

Browse files
committed
Support authenticating with client credentials
1 parent 809aa55 commit 4f3084c

File tree

3 files changed

+48
-6
lines changed

3 files changed

+48
-6
lines changed

src/Selector/Selector.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,10 @@ public function selectOrganization(InputInterface $input, string $filterByLink =
951951
}
952952
}
953953

954+
if ($this->api->isUsingClientCredentials()) {
955+
throw new \InvalidArgumentException('An organization name or ID (--org) is required when client credentials are in use.');
956+
}
957+
954958
$userId = $this->api->getMyUserId();
955959
$organizations = $this->api->getClient()->listOrganizationsWithMember($userId);
956960

src/Service/Api.php

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ private function getConnectorOptions(): array
280280
$connectorOptions['verify'] = $this->config->getBool('api.skip_ssl') ? false : $this->caBundlePath();
281281

282282
$connectorOptions['debug'] = false;
283-
$connectorOptions['client_id'] = $this->config->get('api.oauth2_client_id');
283+
$connectorOptions['client_id'] = $this->config->getStr('api.oauth2_client_id');
284284
$connectorOptions['user_agent'] = $this->config->getUserAgent();
285285
$connectorOptions['timeout'] = $this->config->getInt('api.default_timeout');
286286

@@ -292,6 +292,13 @@ private function getConnectorOptions(): array
292292
$connectorOptions['api_token_type'] = 'access';
293293
}
294294

295+
if ($this->config->has('api.oauth2_client_secret')) {
296+
$connectorOptions['client_secret'] = $this->config->getStr('api.oauth2_client_secret');
297+
}
298+
if ($this->config->has('api.oauth2_scopes')) {
299+
$connectorOptions['scopes'] = (array) $this->config->get('api.oauth2_scopes');
300+
}
301+
295302
$connectorOptions['proxy'] = $this->guzzleProxyConfig();
296303

297304
$connectorOptions['token_url'] = $this->config->get('api.oauth2_token_url');
@@ -510,11 +517,17 @@ public function getClient(bool $autoLogin = true, bool $reset = false): Platform
510517

511518
$sessionId = $this->config->getSessionId();
512519

513-
// Override the session ID if an API token is set.
514-
// This ensures file storage from other credentials will not be
515-
// reused.
516-
if (!empty($options['api_token'])) {
517-
$sessionId = 'api-token-' . \substr(\hash('sha256', (string) $options['api_token']), 0, 32);
520+
// Override the session ID if an API token or client credentials
521+
// are set. This ensures file storage from other credentials will
522+
// not be reused.
523+
if (!empty($options['api_token']) || !empty($options['client_secret'])) {
524+
$credsKeys = [];
525+
foreach (['api_token', 'client_id', 'client_secret', 'scopes'] as $key) {
526+
if (array_key_exists($key, $options)) {
527+
$credsKeys[] = is_array($options[$key]) ? implode(' ', $options[$key]) : $options[$key];
528+
}
529+
}
530+
$sessionId = 'c-' . \hash('sha256', implode(':', $credsKeys));
518531
}
519532

520533
// Set up a session to store OAuth2 tokens.
@@ -628,6 +641,11 @@ private function matchesVendorFilter(string|array|null $filters, BasicProjectInf
628641
return empty($filters) || in_array($project->vendor, (array) $filters);
629642
}
630643

644+
public function isUsingClientCredentials(): bool
645+
{
646+
return $this->config->has('api.oauth2_client_secret') && $this->getClient()->getMyUserId() === false;
647+
}
648+
631649
/**
632650
* Returns the project list for the current user.
633651
*
@@ -637,6 +655,10 @@ private function matchesVendorFilter(string|array|null $filters, BasicProjectInf
637655
*/
638656
public function getMyProjects(?bool $refresh = null): array
639657
{
658+
if ($this->isUsingClientCredentials()) {
659+
return [];
660+
}
661+
640662
$new = $this->config->getBool('api.centralized_permissions') && $this->config->getBool('api.organizations');
641663
/** @var string[]|string|null $vendorFilter */
642664
$vendorFilter = $this->config->getWithDefault('api.vendor_filter', null);
@@ -1672,6 +1694,17 @@ public function getCodeSourceIntegration(Project $project): ?Integration
16721694
*/
16731695
public function showSessionInfo(bool $logout = false, bool $newline = true): void
16741696
{
1697+
if ($this->isUsingClientCredentials()) {
1698+
if ($newline) {
1699+
$this->stdErr->writeln('');
1700+
}
1701+
$this->stdErr->writeln(\sprintf(
1702+
'Client credentials are configured (client ID: <info>%s</info>)',
1703+
$this->config->getStr('api.oauth2_client_id'),
1704+
));
1705+
return;
1706+
}
1707+
16751708
$sessionId = $this->config->getSessionId();
16761709
if ($sessionId !== 'default' || count($this->listSessionIds()) > 1) {
16771710
if ($newline) {

src/Service/Config.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,8 @@ private function applyEnvironmentOverrides(): void
416416
'AUTH_URL' => 'api.auth_url',
417417
'OAUTH2_AUTH_URL' => 'api.oauth2_auth_url',
418418
'OAUTH2_CLIENT_ID' => 'api.oauth2_client_id',
419+
'OAUTH2_CLIENT_SECRET' => 'api.oauth2_client_secret',
420+
'OAUTH2_SCOPE' => 'api.oauth2_scopes',
419421
'OAUTH2_TOKEN_URL' => 'api.oauth2_token_url',
420422
'OAUTH2_REVOKE_URL' => 'api.oauth2_revoke_url',
421423
'CERTIFIER_URL' => 'api.certifier_url',
@@ -672,6 +674,9 @@ private function applyDynamicDefaults(): void
672674
if (!isset($this->config['api']['oauth2_client_id'])) {
673675
$this->config['api']['oauth2_client_id'] = $this->getStr('application.slug');
674676
}
677+
if (isset($this->config['api']['oauth2_scopes']) && is_string($this->config['api']['oauth2_scopes'])) {
678+
$this->config['api']['oauth2_scopes'] = explode(' ', $this->config['api']['oauth2_scopes']);
679+
}
675680
if (!isset($this->config['detection']['console_domain']) && isset($this->config['service']['console_url'])) {
676681
$consoleDomain = parse_url((string) $this->config['service']['console_url'], PHP_URL_HOST);
677682
if ($consoleDomain !== false) {

0 commit comments

Comments
 (0)