Skip to content

Commit 9a01566

Browse files
authored
feat(SDK-4543): Support Organizations with Client Grants (#736)
### Changes This pull request adds functionality associated with Organizations and Client Grants. It introduces support for: - Retrieving Organizations associated with a Client Grant. - Retrieving Client Grants associated with an Organization. - Associating or disassociating client grants from organizations. ### References Please review internal ticket SDK-4543. ### Testing Tests have been added and updated to cover these changes. Coverage remains at 100%. ### Contributor Checklist - [x] I agree to adhere to the [Auth0 General Contribution Guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md). - [x] I agree to uphold the [Auth0 Code of Conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md).
1 parent 6764f47 commit 9a01566

File tree

7 files changed

+327
-19
lines changed

7 files changed

+327
-19
lines changed

src/API/Management/ClientGrants.php

+54-12
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ public function create(
2121
string $audience,
2222
?array $scope = null,
2323
?RequestOptions $options = null,
24+
?string $organizationUsage = null,
25+
?bool $allowAnyOrganization = null,
2426
): ResponseInterface {
2527
[$clientId, $audience] = Toolkit::filter([$clientId, $audience])->string()->trim();
2628
[$scope] = Toolkit::filter([$scope])->array()->trim();
@@ -30,16 +32,24 @@ public function create(
3032
[$audience, \Auth0\SDK\Exception\ArgumentException::missing('audience')],
3133
])->isString();
3234

35+
$body = [
36+
'client_id' => $clientId,
37+
'audience' => $audience,
38+
'scope' => $scope,
39+
];
40+
41+
if (null !== $organizationUsage) {
42+
$body['organization_usage'] = $organizationUsage;
43+
}
44+
45+
if (null !== $allowAnyOrganization) {
46+
$body['allow_any_organization'] = $allowAnyOrganization;
47+
}
48+
3349
return $this->getHttpClient()
3450
->method('post')
3551
->addPath(['client-grants'])
36-
->withBody(
37-
(object) [
38-
'client_id' => $clientId,
39-
'audience' => $audience,
40-
'scope' => $scope,
41-
],
42-
)
52+
->withBody((object) $body)
4353
->withOptions($options)
4454
->call();
4555
}
@@ -120,10 +130,34 @@ public function getAllByClientId(
120130
return $this->getAll($params, $options);
121131
}
122132

133+
public function getOrganizations(
134+
string $grantId,
135+
?array $parameters = null,
136+
?RequestOptions $options = null,
137+
): ResponseInterface {
138+
[$grantId] = Toolkit::filter([$grantId])->string()->trim();
139+
[$parameters] = Toolkit::filter([$parameters])->array()->trim();
140+
141+
Toolkit::assert([
142+
[$grantId, \Auth0\SDK\Exception\ArgumentException::missing('grantId')],
143+
])->isString();
144+
145+
/** @var array<null|int|string> $parameters */
146+
147+
return $this->getHttpClient()
148+
->method('get')
149+
->addPath(['client-grants', $grantId, 'organizations'])
150+
->withParams($parameters)
151+
->withOptions($options)
152+
->call();
153+
}
154+
123155
public function update(
124156
string $grantId,
125157
?array $scope = null,
126158
?RequestOptions $options = null,
159+
?string $organizationUsage = null,
160+
?bool $allowAnyOrganization = null,
127161
): ResponseInterface {
128162
[$grantId] = Toolkit::filter([$grantId])->string()->trim();
129163
[$scope] = Toolkit::filter([$scope])->array()->trim();
@@ -132,13 +166,21 @@ public function update(
132166
[$grantId, \Auth0\SDK\Exception\ArgumentException::missing('grantId')],
133167
])->isString();
134168

169+
$body = [
170+
'scope' => $scope,
171+
];
172+
173+
if (null !== $organizationUsage) {
174+
$body['organization_usage'] = $organizationUsage;
175+
}
176+
177+
if (null !== $allowAnyOrganization) {
178+
$body['allow_any_organization'] = $allowAnyOrganization;
179+
}
180+
135181
return $this->getHttpClient()
136182
->method('patch')->addPath(['client-grants', $grantId])
137-
->withBody(
138-
(object) [
139-
'scope' => $scope,
140-
],
141-
)
183+
->withBody((object) $body)
142184
->withOptions($options)
143185
->call();
144186
}

src/API/Management/Organizations.php

+67
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,34 @@
1616
*/
1717
final class Organizations extends ManagementEndpoint implements OrganizationsInterface
1818
{
19+
public function addClientGrant(
20+
string $id,
21+
string $grantId,
22+
?array $parameters = null,
23+
?RequestOptions $options = null,
24+
): ResponseInterface {
25+
[$id, $grantId] = Toolkit::filter([$id, $grantId])->string()->trim();
26+
[$parameters] = Toolkit::filter([$parameters])->array()->trim();
27+
28+
Toolkit::assert([
29+
[$id, \Auth0\SDK\Exception\ArgumentException::missing('id')],
30+
[$grantId, \Auth0\SDK\Exception\ArgumentException::missing('grantId')],
31+
])->isString();
32+
33+
$body = [
34+
'grant_id' => $grantId,
35+
];
36+
37+
/** @var array<null|int|string> $parameters */
38+
39+
return $this->getHttpClient()
40+
->method('post')->addPath(['organizations', $id, 'client-grants'])
41+
->withBody((object) $body)
42+
->withParams($parameters)
43+
->withOptions($options)
44+
->call();
45+
}
46+
1947
public function addEnabledConnection(
2048
string $id,
2149
string $connectionId,
@@ -259,6 +287,22 @@ public function getByName(
259287
->call();
260288
}
261289

290+
public function getClientGrants(
291+
string $id,
292+
?RequestOptions $options = null,
293+
): ResponseInterface {
294+
[$id] = Toolkit::filter([$id])->string()->trim();
295+
296+
Toolkit::assert([
297+
[$id, \Auth0\SDK\Exception\ArgumentException::missing('id')],
298+
])->isString();
299+
300+
return $this->getHttpClient()
301+
->method('get')->addPath(['organizations', $id, 'client-grants'])
302+
->withOptions($options)
303+
->call();
304+
}
305+
262306
public function getEnabledConnection(
263307
string $id,
264308
string $connectionId,
@@ -361,6 +405,29 @@ public function getMembers(
361405
->call();
362406
}
363407

408+
public function removeClientGrant(
409+
string $id,
410+
string $grantId,
411+
?array $parameters = null,
412+
?RequestOptions $options = null,
413+
): ResponseInterface {
414+
[$id, $grantId] = Toolkit::filter([$id, $grantId])->string()->trim();
415+
[$parameters] = Toolkit::filter([$parameters])->array()->trim();
416+
417+
Toolkit::assert([
418+
[$id, \Auth0\SDK\Exception\ArgumentException::missing('id')],
419+
[$grantId, \Auth0\SDK\Exception\ArgumentException::missing('grantId')],
420+
])->isString();
421+
422+
/** @var array<null|int|string> $parameters */
423+
424+
return $this->getHttpClient()
425+
->method('delete')->addPath(['organizations', $id, 'client-grants', $grantId])
426+
->withParams($parameters)
427+
->withOptions($options)
428+
->call();
429+
}
430+
364431
public function removeEnabledConnection(
365432
string $id,
366433
string $connectionId,

src/Contract/API/Management/ClientGrantsInterface.php

+31-7
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ interface ClientGrantsInterface
1313
* Create a new Client Grant.
1414
* Required scope: `create:client_grants`.
1515
*
16-
* @param string $clientId client ID to receive the grant
17-
* @param string $audience audience identifier for the API being granted
18-
* @param null|array<string> $scope Optional. Scopes allowed for this client grant.
19-
* @param null|RequestOptions $options Optional. Additional request options to use, such as a field filtering or pagination. (Not all endpoints support these. See @see for supported options.)
16+
* @param string $clientId client ID to receive the grant
17+
* @param string $audience audience identifier for the API being granted
18+
* @param null|array<string> $scope Optional. Scopes allowed for this client grant.
19+
* @param null|RequestOptions $options Optional. Additional request options to use, such as a field filtering or pagination. (Not all endpoints support these. See @see for supported options.)
20+
* @param null|string $organizationUsage Optional. Defines whether organizations can be used with client credentials exchanges for this grant. Possible values are `deny`, `allow` or `require`.
21+
* @param null|bool $allowAnyOrganization Optional. If enabled, any organization can be used with this grant. If disabled (default), the grant must be explicitly assigned to the desired organizations.
2022
*
2123
* @throws \Auth0\SDK\Exception\ArgumentException when an invalid `clientId` or `audience` are provided
2224
* @throws \Auth0\SDK\Exception\NetworkException when the API request fails due to a network error
@@ -28,6 +30,8 @@ public function create(
2830
string $audience,
2931
?array $scope = null,
3032
?RequestOptions $options = null,
33+
?string $organizationUsage = null,
34+
?bool $allowAnyOrganization = null,
3135
): ResponseInterface;
3236

3337
/**
@@ -101,13 +105,31 @@ public function getAllByClientId(
101105
?RequestOptions $options = null,
102106
): ResponseInterface;
103107

108+
/**
109+
* Retrieve a client grant's organizations.
110+
* Required scope: `read:organization_client_grants`.
111+
*
112+
* @param string $grantId Grant (by it's ID) to update
113+
* @param null|int[]|null[]|string[] $parameters Optional. Additional query parameters to pass with the API request. See @see for supported options.
114+
* @param null|RequestOptions $options Optional. Additional request options to use, such as a field filtering or pagination. (Not all endpoints support these. See @see for supported options.)
115+
*
116+
* @throws \Auth0\SDK\Exception\NetworkException when the API request fails due to a network error
117+
*/
118+
public function getOrganizations(
119+
string $grantId,
120+
?array $parameters = null,
121+
?RequestOptions $options = null,
122+
): ResponseInterface;
123+
104124
/**
105125
* Update an existing Client Grant.
106126
* Required scope: `update:client_grants`.
107127
*
108-
* @param string $grantId grant (by it's ID) to update
109-
* @param null|array<string> $scope Optional. Array of scopes to update; will replace existing scopes, not merge.
110-
* @param null|RequestOptions $options Optional. Additional request options to use, such as a field filtering or pagination. (Not all endpoints support these. See @see for supported options.)
128+
* @param string $grantId Grant (by it's ID) to update
129+
* @param null|array<string> $scope Optional. Array of scopes to update; will replace existing scopes, not merge.
130+
* @param null|RequestOptions $options Optional. Additional request options to use, such as a field filtering or pagination. (Not all endpoints support these. See @see for supported options.)
131+
* @param null|string $organizationUsage Optional. Defines whether organizations can be used with client credentials exchanges for this grant. Possible values are `deny`, `allow` or `require`.
132+
* @param null|bool $allowAnyOrganization Optional. If enabled, any organization can be used with this grant. If disabled (default), the grant must be explicitly assigned to the desired organizations.
111133
*
112134
* @throws \Auth0\SDK\Exception\ArgumentException when an invalid `grantId` is provided
113135
* @throws \Auth0\SDK\Exception\NetworkException when the API request fails due to a network error
@@ -118,5 +140,7 @@ public function update(
118140
string $grantId,
119141
?array $scope = null,
120142
?RequestOptions $options = null,
143+
?string $organizationUsage = null,
144+
?bool $allowAnyOrganization = null,
121145
): ResponseInterface;
122146
}

src/Contract/API/Management/OrganizationsInterface.php

+50
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,25 @@
99

1010
interface OrganizationsInterface
1111
{
12+
/**
13+
* Associate a client grant to an organization.
14+
* Required scope: `create:organization_client_grants`.
15+
*
16+
* @param string $id Organization (by ID) to associate the client grant with.
17+
* @param string $grantId Client Grant (by ID) to associate with the organization.
18+
* @param null|array<mixed> $parameters Optional. Additional body content to send with the API request.
19+
* @param null|RequestOptions $options Optional. Additional request options to use, such as a field filtering or pagination. (Not all endpoints support these.
20+
*
21+
* @throws \Auth0\SDK\Exception\ArgumentException When an invalid `id` or `connectionId` are provided
22+
* @throws \Auth0\SDK\Exception\NetworkException When the API request fails due to a network error
23+
*/
24+
public function addClientGrant(
25+
string $id,
26+
string $grantId,
27+
?array $parameters = null,
28+
?RequestOptions $options = null,
29+
): ResponseInterface;
30+
1231
/**
1332
* Add a connection to an organization.
1433
* Required scope: `create:organization_connections`.
@@ -206,6 +225,21 @@ public function getByName(
206225
?RequestOptions $options = null,
207226
): ResponseInterface;
208227

228+
/**
229+
* Get client grants associated to an organization.
230+
* Required scope: `read:organization_client_grants`.
231+
*
232+
* @param string $id Organization (by ID) that the connection is associated with
233+
* @param null|RequestOptions $options Optional. Additional request options to use, such as a field filtering or pagination. (Not all endpoints support these.)
234+
*
235+
* @throws \Auth0\SDK\Exception\ArgumentException when an invalid `id` or `connectionId` are provided
236+
* @throws \Auth0\SDK\Exception\NetworkException when the API request fails due to a network error
237+
*/
238+
public function getClientGrants(
239+
string $id,
240+
?RequestOptions $options = null,
241+
): ResponseInterface;
242+
209243
/**
210244
* Get a connection (by ID) associated with an organization.
211245
* Required scope: `read:organization_connections`.
@@ -314,6 +348,22 @@ public function getMembers(
314348
?RequestOptions $options = null,
315349
): ResponseInterface;
316350

351+
/**
352+
* Remove a client grant from an organization.
353+
* Required scope: `delete:organization_client_grants`.
354+
*
355+
* @param string $id Organization (by ID) to remove client grant from.
356+
* @param string $grantId Client Grant (by ID) to remove from the organization.
357+
* @param null|array<mixed> $parameters Optional. Additional body content to send with the API request.
358+
* @param null|RequestOptions $options Optional. Additional request options to use, such as a field filtering or pagination. (Not all endpoints support these.)
359+
*/
360+
public function removeClientGrant(
361+
string $id,
362+
string $grantId,
363+
?array $parameters = null,
364+
?RequestOptions $options = null,
365+
): ResponseInterface;
366+
317367
/**
318368
* Remove a connection from an organization.
319369
* Required scope: `delete:organization_connections`.

tests/Unit/API/AuthenticationTest.php

+26
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,32 @@
465465
expect($requestHeaders['header_testing'][0])->toEqual(123);
466466
});
467467

468+
test('clientCredentials() includes organization in request when configured', function(): void {
469+
$clientSecret = uniqid();
470+
471+
$this->configuration->setClientSecret($clientSecret);
472+
$authentication = $this->sdk->authentication();
473+
$authentication->getHttpClient()->mockResponses([HttpResponseGenerator::create()]);
474+
$authentication->clientCredentials(['organization' => 'org_xyz'], ['header_testing' => 123]);
475+
476+
$request = $authentication->getHttpClient()->getLastRequest()->getLastRequest();
477+
$requestUri = $request->getUri();
478+
$requestBody = explode('&', $request->getBody()->__toString());
479+
$requestHeaders = $request->getHeaders();
480+
481+
expect($requestUri->getHost())->toEqual($this->configuration->getDomain());
482+
expect($requestUri->getPath())->toEqual('/oauth/token');
483+
484+
expect($requestBody)->toContain('grant_type=client_credentials');
485+
expect($requestBody)->toContain('client_id=__test_client_id__');
486+
expect($requestBody)->toContain('client_secret=' . $clientSecret);
487+
expect($requestBody)->toContain('audience=aud1');
488+
expect($requestBody)->toContain('organization=org_xyz');
489+
490+
$this->assertArrayHasKey('header_testing', $requestHeaders);
491+
expect($requestHeaders['header_testing'][0])->toEqual(123);
492+
});
493+
468494
test('refreshToken() is properly formatted', function(): void {
469495
$clientSecret = uniqid();
470496
$refreshToken = uniqid();

0 commit comments

Comments
 (0)