From 03c49a639c78047318ad48d9ee0e72bfbf83367c Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 16 Mar 2021 01:04:00 -0400 Subject: [PATCH 01/38] feat: Support passing `organization_id` configuration parameter --- src/API/Authentication.php | 24 +++++++++++++++++------- src/Auth0.php | 13 ++++++++++++- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/API/Authentication.php b/src/API/Authentication.php index 51b4b61a..a77fefd7 100644 --- a/src/API/Authentication.php +++ b/src/API/Authentication.php @@ -52,6 +52,14 @@ class Authentication */ private $audience; + + /** + * Organization ID, if applicable. + * + * @var null|string + */ + private $organization_id; + /** * Scopes to request during login. * @@ -92,15 +100,17 @@ public function __construct( ?string $client_secret = null, ?string $audience = null, ?string $scope = null, - array $guzzleOptions = [] + array $guzzleOptions = [], + ?string $organization_id = null ) { - $this->domain = $domain; - $this->client_id = $client_id; - $this->client_secret = $client_secret; - $this->audience = $audience; - $this->scope = $scope; - $this->guzzleOptions = $guzzleOptions; + $this->domain = $domain; + $this->client_id = $client_id; + $this->client_secret = $client_secret; + $this->audience = $audience; + $this->scope = $scope; + $this->guzzleOptions = $guzzleOptions; + $this->organization_id = $organization_id; $this->apiClient = new ApiClient( [ 'domain' => 'https://'.$this->domain, diff --git a/src/Auth0.php b/src/Auth0.php index 2210cb33..c3c8c31f 100644 --- a/src/Auth0.php +++ b/src/Auth0.php @@ -92,6 +92,13 @@ class Auth0 */ protected $audience; + /** + * Auth0 Organization ID, found in your Organization settings. + * + * @var string + */ + protected $organizationId; + /** * Scope for ID tokens and /userinfo endpoint * @@ -278,6 +285,8 @@ public function __construct(array $config) $this->clientSecret = self::urlSafeBase64Decode($this->clientSecret); } + $this->organizationId = $config['organization_id'] ?? $_ENV['AUTH0_ORGANIZATION_ID'] ?? null; + $this->audience = $config['audience'] ?? null; $this->responseMode = $config['response_mode'] ?? 'query'; $this->responseType = $config['response_type'] ?? 'code'; @@ -345,7 +354,8 @@ public function __construct(array $config) $this->clientSecret, $this->audience, $this->scope, - $this->guzzleOptions + $this->guzzleOptions, + $this->organizationId ); $this->user = $this->store->get('user'); @@ -692,6 +702,7 @@ public function decodeIdToken(string $idToken, array $verifierOptions = []) : ar } $verifierOptions = $verifierOptions + [ + 'org_id' => $this->organizationId, 'leeway' => $this->idTokenLeeway, 'max_age' => $this->transientHandler->getOnce('max_age') ?? $this->maxAge, self::TRANSIENT_NONCE_KEY => $this->transientHandler->getOnce(self::TRANSIENT_NONCE_KEY) From 00295696ef89dc020bbe993417e672bff361ff82 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 16 Mar 2021 01:04:14 -0400 Subject: [PATCH 02/38] feat: Pass `organization` parameter to /authorize endpoint --- src/API/Authentication.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/API/Authentication.php b/src/API/Authentication.php index a77fefd7..3c3ff20f 100644 --- a/src/API/Authentication.php +++ b/src/API/Authentication.php @@ -148,6 +148,7 @@ public function get_authorize_link( $params['state'] = $state ?? $params['state'] ?? null; $params['audience'] = $params['audience'] ?? $this->audience ?? null; $params['scope'] = $params['scope'] ?? $this->scope ?? null; + $params['organization'] = $params['organization_id'] ?? $this->organization_id ?? null; $params = array_filter($params); From daea5a950ba1f61c3dcd7baa5ffe3f6500e009af Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 16 Mar 2021 01:05:04 -0400 Subject: [PATCH 03/38] feat: Add new API request helpers for normalizing additional query parameters --- src/API/Management/GenericResource.php | 53 ++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/src/API/Management/GenericResource.php b/src/API/Management/GenericResource.php index 85414278..259be31a 100644 --- a/src/API/Management/GenericResource.php +++ b/src/API/Management/GenericResource.php @@ -40,6 +40,22 @@ public function getApiClient() return $this->apiClient; } + /** + * Convenience function for normalizePagination, normalizeIncludeTotals and normalizeIncludeFields. + * + * @param array $params Original parameters to normalize. + * + * @return array + */ + protected function normalizeRequest(array $params) + { + $params = $this->normalizeIncludeFields($params); + $params = $this->normalizePagination($params); + $params = $this->normalizeIncludeTotals($params); + + return $params; + } + /** * Normalize pagination parameters. * @@ -88,6 +104,43 @@ protected function normalizeIncludeTotals(array $params, $include_totals = null) return $params; } + /** + * Normalize fields and include_fields parameters. + * + * @param array $params Original parameters to normalize. + * @param null|array $fields Optional. Array of fields names to include in the response. + * @param null|bool $includeFields Optional. Whether to include (true) or exclude (false) the fields specified. + * + * @return array + */ + protected function normalizeIncludeFields(array $params, ?array $fields = [], ?bool $includeFields = null) + { + $fields = (null === $fields ? [] : $fields); + $params['fields'] = (isset($params['fields']) ? $params['fields'] : $fields); + + if (is_array($params['fields'])) { + $params['fields'] = implode(',', $params['fields']); + } + + if (null !== $params['fields']) { + $params['include_fields'] = (isset($params['include_fields']) ? $params['include_fields'] : $includeFields); + + if (null !== $params['include_fields']) { + $params['include_fields'] = filter_var( $params['include_fields'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); + } + } + + if (null === $params['fields']) { + unset($params['fields']); + } + + if (null === $params['include_fields']) { + unset($params['include_fields']); + } + + return $params; + } + /** * Check for invalid permissions with an array of permissions. * From 2f3d57de3a2953f9eedb77894b6d4859cb2f5e7d Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 16 Mar 2021 01:05:38 -0400 Subject: [PATCH 04/38] feat: Validate ID Token `org_id` claim when an organization is configured. --- src/Helpers/Tokens/IdTokenVerifier.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/Helpers/Tokens/IdTokenVerifier.php b/src/Helpers/Tokens/IdTokenVerifier.php index df348fdd..fbcdaeac 100644 --- a/src/Helpers/Tokens/IdTokenVerifier.php +++ b/src/Helpers/Tokens/IdTokenVerifier.php @@ -97,6 +97,20 @@ public function verify(string $token, array $options = []) : array } } + /* + * Organization check + */ + $tokenOrgId = $verifiedToken['org_id'] ?? null; + $expectedOrgId = $options['org_id'] ?? null; + + if ($tokenOrgId !== $expectedOrgId) { + throw new InvalidTokenException( sprintf( + 'Organization (org_id) claim mismatch in the ID token; expected "%s", found "%s"', + $expectedOrgId, + $tokenOrgId + ) ); + } + /* * Authentication time check */ From 9a854cc36045f3b3a064d9a0126c753122754e04 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 16 Mar 2021 01:06:18 -0400 Subject: [PATCH 05/38] feat: Support querying /users/:user_id/organizations endpoint for listing user organization memberships. --- src/API/Management/Users.php | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/API/Management/Users.php b/src/API/Management/Users.php index 0609f357..b7c1b04f 100644 --- a/src/API/Management/Users.php +++ b/src/API/Management/Users.php @@ -460,6 +460,33 @@ public function getLogs($user_id, array $params = []) ->call(); } + /** + * Get organizations a specific user belongs to. + * Required scope: "read:organizations" + * + * @param string $user_id User ID to get organization entries for. + * @param array $params Additional listing params like page, per_page, sort, and include_totals. + * + * @throws EmptyOrInvalidParameterException Thrown if the user_id parameter is empty or is not a string. + * @throws \Exception Thrown by the HTTP client when there is a problem with the API call. + * + * @return mixed + * + * @link https://auth0.com/docs/api/management/v2#!/Users/get_organizations_by_user #TODO + */ + public function getOrganizations($user_id, array $params = []) + { + $this->checkEmptyOrInvalidString($user_id, 'user_id'); + + $params = $this->normalizePagination( $params ); + $params = $this->normalizeIncludeTotals( $params ); + + return $this->apiClient->method('get') + ->addPath('users', $user_id, 'organizations') + ->withDictParams($params) + ->call(); + } + /** * Removes the current Guardian recovery code and generates and returns a new one. * Required scope: "update:users" From 47587bb602541305ff69f7b2ca14b413bfadedf7 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 16 Mar 2021 01:07:21 -0400 Subject: [PATCH 06/38] feat: Add Organizations endpoint support for Management API --- src/API/Management/Organizations.php | 477 +++++++++++++++++++++++++++ 1 file changed, 477 insertions(+) create mode 100644 src/API/Management/Organizations.php diff --git a/src/API/Management/Organizations.php b/src/API/Management/Organizations.php new file mode 100644 index 00000000..b1d429bb --- /dev/null +++ b/src/API/Management/Organizations.php @@ -0,0 +1,477 @@ + $params Optional. Options to include with the request, such as pagination or filtering parameters. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + */ + public function getAll( + array $params = [] + ) { + return $this->apiClient->method('get') + ->addPath('organizations') + ->withDictParams($this->normalizeRequest($params)) + ->call(); + } + + /** + * Get details about an organization, queried by it's ID. + * Required scope: "read:organizations" + * + * @param string $organizationId Organization (by ID) to retrieve details for. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + * + * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO + */ + public function get( + string $organizationId + ) { + return $this->apiClient->method('get') + ->addPath('organizations', $organizationId) + ->call(); + } + + /** + * Get details about an organization, queried by it's `name`. + * Required scope: "read:organizations" + * + * @param string $organizationName Organization (by name parameter provided during creation) to retrieve details for. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + * + * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO + */ + public function getByName( + string $organizationName + ) { + return $this->apiClient->method('get') + ->addPath('organizations', 'name', $organizationName) + ->call(); + } + + /** + * Create an organization. + * Required scope: "create:organizations" + * + * @param string $name The name of the Organization. Cannot be changed later. + * @param string $displayName The displayed name of the Organization. + * @param array $branding An array containing branding customizations for the organization. + * @param array $metadata Optional. Additional metadata to store about the organization. + * @param array $additionalParameters Optional. Additional parameters to send with the API request. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + * + * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO + */ + public function create( + string $name = '', + string $displayName = '', + array $branding, + array $metadata = [], + array $additionalParameters = [] + ) { + $payload = [ + 'name' => $name, + 'display_name' => $displayName, + 'branding' => $branding, + 'metadata' => $metadata, + ] + $additionalParameters; + + return $this->apiClient->method('post') + ->addPath('organizations') + ->withBody(json_encode($payload)) + ->call(); + } + + /** + * Update an organization. + * Required scope: "update:organizations" + * + * @param string $organizationId Organization (by ID) to update. + * @param string $displayName The displayed name of the Organization. + * @param array $branding An array containing branding customizations for the organization. + * @param array $metadata Optional. Additional metadata to store about the organization. + * @param array $additionalParameters Optional. Additional parameters to send with the API request. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + * + * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO + */ + public function patch( + string $organizationId, + string $displayName = '', + array $branding, + array $metadata = [], + array $additionalParameters = [] + ) + { + $payload = [ + 'display_name' => $displayName, + 'branding' => $branding, + 'metadata' => $metadata, + ] + $additionalParameters; + + return $this->apiClient->method('patch') + ->addPath('organizations', $organizationId) + ->withBody(json_encode($payload)) + ->call(); + } + + /** + * Delete an organization. + * Required scope: "delete:organizations" + * + * @param string $organizationId Organization (by ID) to delete. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + * + * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO + */ + public function delete( + string $organizationId + ) + { + return $this->apiClient->method('delete') + ->addPath('organizations', $organizationId) + ->call(); + } + + /** + * List the connections associated with an organization. + * Required scope: "read:organization_connections" + * + * @param string $organizationId Organization (by ID) to list connections of. + * @param array $params Optional. Additional options to include with the request, such as pagination or filtering parameters. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + * + * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO + */ + public function getConnections( + string $organizationId, + array $params = [] + ) { + return $this->apiClient->method('get') + ->addPath('organizations', $organizationId, 'connections') + ->withDictParams($this->normalizeRequest($params)) + ->call(); + } + + /** + * Add a connection to an organization. + * Required scope: "create:organization_connections" + * + * @param string $organizationId Organization (by ID) to add a connection to. + * @param string $connectionId Connection (by ID) to add to organization. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + * + * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO + */ + public function addConnection( + string $organizationId, + string $connectionId + ) { + return $this->apiClient->method('post') + ->addPath('organizations', $organizationId, 'connections', $connectionId) + ->call(); + } + + /** + * Remove a connection from an organization. + * Required scope: "delete:organization_connections" + * + * @param string $organizationId Organization (by ID) to remove connection from. + * @param string $connectionId Connection (by ID) to remove from organization. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + * + * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO + */ + public function removeConnection( + string $organizationId, + string $connectionId + ) { + return $this->apiClient->method('delete') + ->addPath('organizations', $organizationId, 'connections', $connectionId) + ->call(); + } + + /** + * List the members (users) belonging to an organization + * Required scope: "read:organization_members" + * + * @param string $organizationId Organization (by ID) to list members of. + * @param array $params Optional. Additional options to include with the request, such as pagination or filtering parameters. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + * + * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO + */ + public function getMembers( + string $organizationId, + array $params = [] + ) { + return $this->apiClient->method('get') + ->addPath('organizations', $organizationId, 'members') + ->withDictParams($this->normalizeRequest($params)) + ->call(); + } + + /** + * Add a user to an organization as a member. + * Required scope: "update:organization_members" + * + * @param string $organizationId Organization (by ID) to add new members to. + * @param string $userId User (by ID) to add from the organization. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + * + * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO + */ + public function addMember( + string $organizationId, + string $userId + ) { + return $this->addMembers($organizationId, [ $userId ]); + } + + /** + * Add one or more users to an organization as members. + * Required scope: "update:organization_members" + * + * @param string $organizationId Organization (by ID) to add new members to. + * @param array $userIds One or more users (by ID) to add from the organization. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + * + * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO + */ + public function addMembers( + string $organizationId, + array $userIds + ) { + $payload = [ + 'members' => $userIds + ]; + + return $this->apiClient->method('post') + ->addPath('organizations', $organizationId, 'members') + ->withBody(json_encode($payload)) + ->call(); + } + + /** + * Remove a member (user) from an organization. + * Required scope: "delete:organization_members" + * + * @param string $organizationId Organization (by ID) user belongs to. + * @param string $userId User (by ID) to remove from the organization. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + * + * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO + */ + public function removeMember( + string $organizationId, + string $userId + ) { + return $this->removeMembers($organizationId, [ $userId ]); + } + + /** + * Remove one or more members (users) from an organization. + * Required scope: "delete:organization_members" + * + * @param string $organizationId Organization (by ID) users belong to. + * @param array $userIds One or more users (by ID) to remove from the organization. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + * + * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO + */ + public function removeMembers( + string $organizationId, + array $userIds + ) { + $payload = [ + 'members' => $userIds + ]; + + return $this->apiClient->method('delete') + ->addPath('organizations', $organizationId, 'members') + ->withBody(json_encode($payload)) + ->call(); + } + + /** + * List the roles a member (user) in an organization currently has. + * Required scope: "read:organization_member_roles" + * + * @param string $organizationId Organization (by ID) user belongs to. + * @param string $userId User (by ID) to add role to. + * @param array $params Optional. Additional options to include with the request, such as pagination or filtering parameters. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + * + * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO + */ + public function getMemberRoles( + string $organizationId, + string $userId, + array $params = [] + ) { + return $this->apiClient->method('get') + ->addPath('organizations', $organizationId, 'members', $userId) + ->withDictParams($this->normalizeRequest($params)) + ->call(); + } + + /** + * Add a role to a member (user) in an organization. + * Required scope: "create:organization_member_roles" + * + * @param string $organizationId Organization (by ID) user belongs to. + * @param string $userId User (by ID) to add role to. + * @param string $roleId Role (by ID) to add to the user. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + * + * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO + */ + public function addMemberRole( + string $organizationId, + string $userId, + string $roleId + ) { + return $this->addMemberRoles($organizationId, $userId, [ $roleId ]); + } + + /** + * Add one or more roles to a member (user) in an organization. + * Required scope: "create:organization_member_roles" + * + * @param string $organizationId Organization (by ID) user belongs to. + * @param string $userId User (by ID) to add roles to. + * @param array $roleIds One or more roles (by ID) to add to the user. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + * + * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO + */ + public function addMemberRoles( + string $organizationId, + string $userId, + array $roleIds + ) { + $payload = [ + 'roles' => $roleIds + ]; + + return $this->apiClient->method('post') + ->addPath('organizations', $organizationId, 'members', $userId) + ->withBody(json_encode($payload)) + ->call(); + } + + /** + * Remove a role from a member (user) in an organization. + * Required scope: "delete:organization_member_roles" + * + * @param string $organizationId Organization (by ID) user belongs to. + * @param string $userId User (by ID) to remove roles from. + * @param string $roleId Role (by ID) to remove from the user. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + * + * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO + */ + public function removeMemberRole( + string $organizationId, + string $userId, + string $roleId + ) { + return $this->removeMemberRoles($organizationId, $userId, [ $roleId ]); + } + + /** + * Remove one or more roles from a member (user) in an organization. + * Required scope: "delete:organization_member_roles" + * + * @param string $organizationId Organization (by ID) user belongs to. + * @param string $userId User (by ID) to remove roles from. + * @param array $roleIds One or more roles (by ID) to remove from the user. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + * + * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO + */ + public function removeMemberRoles( + string $organizationId, + string $userId, + array $roleIds + ) { + $payload = [ + 'roles' => $roleIds + ]; + + return $this->apiClient->method('delete') + ->addPath('organizations', $organizationId, 'members', $userId) + ->withBody(json_encode($payload)) + ->call(); + } +} From bc79a0b9f185d40998852850bbac42aa2657e0ec Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 16 Mar 2021 01:13:08 -0400 Subject: [PATCH 07/38] fix: Specify required parameters --- src/API/Management/Organizations.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/API/Management/Organizations.php b/src/API/Management/Organizations.php index b1d429bb..5d8cb3c2 100644 --- a/src/API/Management/Organizations.php +++ b/src/API/Management/Organizations.php @@ -88,8 +88,8 @@ public function getByName( * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function create( - string $name = '', - string $displayName = '', + string $name, + string $displayName, array $branding, array $metadata = [], array $additionalParameters = [] @@ -125,7 +125,7 @@ public function create( */ public function patch( string $organizationId, - string $displayName = '', + string $displayName, array $branding, array $metadata = [], array $additionalParameters = [] From f1903dd3196da44e1bd642c5b59b0f0ba9ca9c8b Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 16 Mar 2021 01:20:42 -0400 Subject: [PATCH 08/38] feat: Prep for basic branding customizations validation --- src/API/Management/Organizations.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/API/Management/Organizations.php b/src/API/Management/Organizations.php index 5d8cb3c2..90937660 100644 --- a/src/API/Management/Organizations.php +++ b/src/API/Management/Organizations.php @@ -3,6 +3,7 @@ namespace Auth0\SDK\API\Management; use GuzzleHttp\Exception\RequestException; +use Auth0\SDK\Exception\EmptyOrInvalidParameterException; /** * Organizations @@ -94,6 +95,8 @@ public function create( array $metadata = [], array $additionalParameters = [] ) { + $this->validateBranding($branding); + $payload = [ 'name' => $name, 'display_name' => $displayName, @@ -131,6 +134,8 @@ public function patch( array $additionalParameters = [] ) { + $this->validateBranding($branding); + $payload = [ 'display_name' => $displayName, 'branding' => $branding, @@ -474,4 +479,20 @@ public function removeMemberRoles( ->withBody(json_encode($payload)) ->call(); } + + /** + * Validate an array containing branding customizations for use during the creation or updating of an organization. + * + * @param array $branding An array containing branding customizations for the organization. + * + * @return void + * + * @throws EmptyOrInvalidParameterException When an improperly formatted branding customization is provided. + */ + protected function validateBranding( + array $branding + ) { + // TODO + return true; + } } From 879f7a3586bbadde77f40ff65ca55d7e8b9c8f89 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 16 Mar 2021 01:35:50 -0400 Subject: [PATCH 09/38] feat: Throw unique error if org_id claim is expected but not present. --- src/Helpers/Tokens/IdTokenVerifier.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Helpers/Tokens/IdTokenVerifier.php b/src/Helpers/Tokens/IdTokenVerifier.php index fbcdaeac..80a5a5d3 100644 --- a/src/Helpers/Tokens/IdTokenVerifier.php +++ b/src/Helpers/Tokens/IdTokenVerifier.php @@ -103,9 +103,13 @@ public function verify(string $token, array $options = []) : array $tokenOrgId = $verifiedToken['org_id'] ?? null; $expectedOrgId = $options['org_id'] ?? null; - if ($tokenOrgId !== $expectedOrgId) { + if (null !== $expectedOrgId && (null === $tokenOrgId || !is_string($tokenOrgId))) { + throw new InvalidTokenException('Organization Id (org_id) claim must be a string present in the ID token'); + } + + if (null !== $expectedOrgId && ($tokenOrgId !== $expectedOrgId)) { throw new InvalidTokenException( sprintf( - 'Organization (org_id) claim mismatch in the ID token; expected "%s", found "%s"', + 'Organization Id (org_id) claim value mismatch in the ID token; expected "%s", found "%s"', $expectedOrgId, $tokenOrgId ) ); From b2e22bec9c263bceb17c44fa7e35e5141aac3be8 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 16 Mar 2021 12:26:14 -0400 Subject: [PATCH 10/38] fix: Expose $organization params, rather than $organizationId --- src/API/Authentication.php | 21 +- src/API/Management/GenericResource.php | 4 +- src/API/Management/Organizations.php | 344 +++++++++++++------------ src/Auth0.php | 8 +- src/Helpers/Tokens/IdTokenVerifier.php | 4 +- 5 files changed, 199 insertions(+), 182 deletions(-) diff --git a/src/API/Authentication.php b/src/API/Authentication.php index 3c3ff20f..b897743c 100644 --- a/src/API/Authentication.php +++ b/src/API/Authentication.php @@ -52,13 +52,12 @@ class Authentication */ private $audience; - /** * Organization ID, if applicable. * * @var null|string */ - private $organization_id; + private $organization; /** * Scopes to request during login. @@ -101,16 +100,16 @@ public function __construct( ?string $audience = null, ?string $scope = null, array $guzzleOptions = [], - ?string $organization_id = null + ?string $organization = null ) { - $this->domain = $domain; - $this->client_id = $client_id; - $this->client_secret = $client_secret; - $this->audience = $audience; - $this->scope = $scope; - $this->guzzleOptions = $guzzleOptions; - $this->organization_id = $organization_id; + $this->domain = $domain; + $this->client_id = $client_id; + $this->client_secret = $client_secret; + $this->audience = $audience; + $this->scope = $scope; + $this->guzzleOptions = $guzzleOptions; + $this->organization = $organization; $this->apiClient = new ApiClient( [ 'domain' => 'https://'.$this->domain, @@ -148,7 +147,7 @@ public function get_authorize_link( $params['state'] = $state ?? $params['state'] ?? null; $params['audience'] = $params['audience'] ?? $this->audience ?? null; $params['scope'] = $params['scope'] ?? $this->scope ?? null; - $params['organization'] = $params['organization_id'] ?? $this->organization_id ?? null; + $params['organization'] = $params['organization'] ?? $this->organization ?? null; $params = array_filter($params); diff --git a/src/API/Management/GenericResource.php b/src/API/Management/GenericResource.php index 259be31a..1a915455 100644 --- a/src/API/Management/GenericResource.php +++ b/src/API/Management/GenericResource.php @@ -109,13 +109,13 @@ protected function normalizeIncludeTotals(array $params, $include_totals = null) * * @param array $params Original parameters to normalize. * @param null|array $fields Optional. Array of fields names to include in the response. - * @param null|bool $includeFields Optional. Whether to include (true) or exclude (false) the fields specified. + * @param null|boolean $includeFields Optional. Whether to include (true) or exclude (false) the fields specified. * * @return array */ protected function normalizeIncludeFields(array $params, ?array $fields = [], ?bool $includeFields = null) { - $fields = (null === $fields ? [] : $fields); + $fields = (null === $fields ? [] : $fields); $params['fields'] = (isset($params['fields']) ? $params['fields'] : $fields); if (is_array($params['fields'])) { diff --git a/src/API/Management/Organizations.php b/src/API/Management/Organizations.php index 90937660..f95fd77a 100644 --- a/src/API/Management/Organizations.php +++ b/src/API/Management/Organizations.php @@ -24,9 +24,10 @@ class Organizations extends GenericResource * @throws RequestException When API request fails. Reason for failure provided in exception message. */ public function getAll( - array $params = [] - ) { - return $this->apiClient->method('get') + array $params = [] + ) + { + return $this->apiClient->method('get') ->addPath('organizations') ->withDictParams($this->normalizeRequest($params)) ->call(); @@ -36,7 +37,7 @@ public function getAll( * Get details about an organization, queried by it's ID. * Required scope: "read:organizations" * - * @param string $organizationId Organization (by ID) to retrieve details for. + * @param string $organization Organization (by ID) to retrieve details for. * * @return mixed * @@ -45,10 +46,11 @@ public function getAll( * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function get( - string $organizationId - ) { - return $this->apiClient->method('get') - ->addPath('organizations', $organizationId) + string $organization + ) + { + return $this->apiClient->method('get') + ->addPath('organizations', $organization) ->call(); } @@ -65,9 +67,10 @@ public function get( * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function getByName( - string $organizationName - ) { - return $this->apiClient->method('get') + string $organizationName + ) + { + return $this->apiClient->method('get') ->addPath('organizations', 'name', $organizationName) ->call(); } @@ -76,10 +79,10 @@ public function getByName( * Create an organization. * Required scope: "create:organizations" * - * @param string $name The name of the Organization. Cannot be changed later. - * @param string $displayName The displayed name of the Organization. - * @param array $branding An array containing branding customizations for the organization. - * @param array $metadata Optional. Additional metadata to store about the organization. + * @param string $name The name of the Organization. Cannot be changed later. + * @param string $displayName The displayed name of the Organization. + * @param array $branding An array containing branding customizations for the organization. + * @param array $metadata Optional. Additional metadata to store about the organization. * @param array $additionalParameters Optional. Additional parameters to send with the API request. * * @return mixed @@ -89,22 +92,23 @@ public function getByName( * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function create( - string $name, - string $displayName, - array $branding, - array $metadata = [], - array $additionalParameters = [] - ) { - $this->validateBranding($branding); + string $name, + string $displayName, + array $branding, + array $metadata = [], + array $additionalParameters = [] + ) + { + $this->validateBranding($branding); - $payload = [ - 'name' => $name, - 'display_name' => $displayName, - 'branding' => $branding, - 'metadata' => $metadata, - ] + $additionalParameters; + $payload = [ + 'name' => $name, + 'display_name' => $displayName, + 'branding' => $branding, + 'metadata' => $metadata, + ] + $additionalParameters; - return $this->apiClient->method('post') + return $this->apiClient->method('post') ->addPath('organizations') ->withBody(json_encode($payload)) ->call(); @@ -114,10 +118,10 @@ public function create( * Update an organization. * Required scope: "update:organizations" * - * @param string $organizationId Organization (by ID) to update. - * @param string $displayName The displayed name of the Organization. - * @param array $branding An array containing branding customizations for the organization. - * @param array $metadata Optional. Additional metadata to store about the organization. + * @param string $organization Organization (by ID) to update. + * @param string $displayName The displayed name of the Organization. + * @param array $branding An array containing branding customizations for the organization. + * @param array $metadata Optional. Additional metadata to store about the organization. * @param array $additionalParameters Optional. Additional parameters to send with the API request. * * @return mixed @@ -127,23 +131,23 @@ public function create( * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function patch( - string $organizationId, - string $displayName, - array $branding, - array $metadata = [], - array $additionalParameters = [] + string $organization, + string $displayName, + array $branding, + array $metadata = [], + array $additionalParameters = [] ) { - $this->validateBranding($branding); + $this->validateBranding($branding); - $payload = [ - 'display_name' => $displayName, - 'branding' => $branding, - 'metadata' => $metadata, - ] + $additionalParameters; + $payload = [ + 'display_name' => $displayName, + 'branding' => $branding, + 'metadata' => $metadata, + ] + $additionalParameters; - return $this->apiClient->method('patch') - ->addPath('organizations', $organizationId) + return $this->apiClient->method('patch') + ->addPath('organizations', $organization) ->withBody(json_encode($payload)) ->call(); } @@ -152,7 +156,7 @@ public function patch( * Delete an organization. * Required scope: "delete:organizations" * - * @param string $organizationId Organization (by ID) to delete. + * @param string $organization Organization (by ID) to delete. * * @return mixed * @@ -161,11 +165,11 @@ public function patch( * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function delete( - string $organizationId + string $organization ) { - return $this->apiClient->method('delete') - ->addPath('organizations', $organizationId) + return $this->apiClient->method('delete') + ->addPath('organizations', $organization) ->call(); } @@ -173,8 +177,8 @@ public function delete( * List the connections associated with an organization. * Required scope: "read:organization_connections" * - * @param string $organizationId Organization (by ID) to list connections of. - * @param array $params Optional. Additional options to include with the request, such as pagination or filtering parameters. + * @param string $organization Organization (by ID) to list connections of. + * @param array $params Optional. Additional options to include with the request, such as pagination or filtering parameters. * * @return mixed * @@ -183,11 +187,12 @@ public function delete( * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function getConnections( - string $organizationId, - array $params = [] - ) { - return $this->apiClient->method('get') - ->addPath('organizations', $organizationId, 'connections') + string $organization, + array $params = [] + ) + { + return $this->apiClient->method('get') + ->addPath('organizations', $organization, 'connections') ->withDictParams($this->normalizeRequest($params)) ->call(); } @@ -196,8 +201,8 @@ public function getConnections( * Add a connection to an organization. * Required scope: "create:organization_connections" * - * @param string $organizationId Organization (by ID) to add a connection to. - * @param string $connectionId Connection (by ID) to add to organization. + * @param string $organization Organization (by ID) to add a connection to. + * @param string $connection Connection (by ID) to add to organization. * * @return mixed * @@ -206,11 +211,12 @@ public function getConnections( * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function addConnection( - string $organizationId, - string $connectionId - ) { - return $this->apiClient->method('post') - ->addPath('organizations', $organizationId, 'connections', $connectionId) + string $organization, + string $connection + ) + { + return $this->apiClient->method('post') + ->addPath('organizations', $organization, 'connections', $connection) ->call(); } @@ -218,8 +224,8 @@ public function addConnection( * Remove a connection from an organization. * Required scope: "delete:organization_connections" * - * @param string $organizationId Organization (by ID) to remove connection from. - * @param string $connectionId Connection (by ID) to remove from organization. + * @param string $organization Organization (by ID) to remove connection from. + * @param string $connection Connection (by ID) to remove from organization. * * @return mixed * @@ -228,11 +234,12 @@ public function addConnection( * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function removeConnection( - string $organizationId, - string $connectionId - ) { - return $this->apiClient->method('delete') - ->addPath('organizations', $organizationId, 'connections', $connectionId) + string $organization, + string $connection + ) + { + return $this->apiClient->method('delete') + ->addPath('organizations', $organization, 'connections', $connection) ->call(); } @@ -240,8 +247,8 @@ public function removeConnection( * List the members (users) belonging to an organization * Required scope: "read:organization_members" * - * @param string $organizationId Organization (by ID) to list members of. - * @param array $params Optional. Additional options to include with the request, such as pagination or filtering parameters. + * @param string $organization Organization (by ID) to list members of. + * @param array $params Optional. Additional options to include with the request, such as pagination or filtering parameters. * * @return mixed * @@ -250,11 +257,12 @@ public function removeConnection( * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function getMembers( - string $organizationId, - array $params = [] - ) { - return $this->apiClient->method('get') - ->addPath('organizations', $organizationId, 'members') + string $organization, + array $params = [] + ) + { + return $this->apiClient->method('get') + ->addPath('organizations', $organization, 'members') ->withDictParams($this->normalizeRequest($params)) ->call(); } @@ -263,8 +271,8 @@ public function getMembers( * Add a user to an organization as a member. * Required scope: "update:organization_members" * - * @param string $organizationId Organization (by ID) to add new members to. - * @param string $userId User (by ID) to add from the organization. + * @param string $organization Organization (by ID) to add new members to. + * @param string $user User (by ID) to add from the organization. * * @return mixed * @@ -273,18 +281,19 @@ public function getMembers( * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function addMember( - string $organizationId, - string $userId - ) { - return $this->addMembers($organizationId, [ $userId ]); + string $organization, + string $user + ) + { + return $this->addMembers($organization, [ $user ]); } /** * Add one or more users to an organization as members. * Required scope: "update:organization_members" * - * @param string $organizationId Organization (by ID) to add new members to. - * @param array $userIds One or more users (by ID) to add from the organization. + * @param string $organization Organization (by ID) to add new members to. + * @param array $users One or more users (by ID) to add from the organization. * * @return mixed * @@ -293,15 +302,16 @@ public function addMember( * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function addMembers( - string $organizationId, - array $userIds - ) { - $payload = [ - 'members' => $userIds - ]; + string $organization, + array $users + ) + { + $payload = [ + 'members' => $users + ]; - return $this->apiClient->method('post') - ->addPath('organizations', $organizationId, 'members') + return $this->apiClient->method('post') + ->addPath('organizations', $organization, 'members') ->withBody(json_encode($payload)) ->call(); } @@ -310,8 +320,8 @@ public function addMembers( * Remove a member (user) from an organization. * Required scope: "delete:organization_members" * - * @param string $organizationId Organization (by ID) user belongs to. - * @param string $userId User (by ID) to remove from the organization. + * @param string $organization Organization (by ID) user belongs to. + * @param string $user User (by ID) to remove from the organization. * * @return mixed * @@ -320,18 +330,19 @@ public function addMembers( * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function removeMember( - string $organizationId, - string $userId - ) { - return $this->removeMembers($organizationId, [ $userId ]); + string $organization, + string $user + ) + { + return $this->removeMembers($organization, [ $user ]); } /** * Remove one or more members (users) from an organization. * Required scope: "delete:organization_members" * - * @param string $organizationId Organization (by ID) users belong to. - * @param array $userIds One or more users (by ID) to remove from the organization. + * @param string $organization Organization (by ID) users belong to. + * @param array $users One or more users (by ID) to remove from the organization. * * @return mixed * @@ -340,15 +351,16 @@ public function removeMember( * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function removeMembers( - string $organizationId, - array $userIds - ) { - $payload = [ - 'members' => $userIds - ]; + string $organization, + array $users + ) + { + $payload = [ + 'members' => $users + ]; - return $this->apiClient->method('delete') - ->addPath('organizations', $organizationId, 'members') + return $this->apiClient->method('delete') + ->addPath('organizations', $organization, 'members') ->withBody(json_encode($payload)) ->call(); } @@ -357,9 +369,9 @@ public function removeMembers( * List the roles a member (user) in an organization currently has. * Required scope: "read:organization_member_roles" * - * @param string $organizationId Organization (by ID) user belongs to. - * @param string $userId User (by ID) to add role to. - * @param array $params Optional. Additional options to include with the request, such as pagination or filtering parameters. + * @param string $organization Organization (by ID) user belongs to. + * @param string $user User (by ID) to add role to. + * @param array $params Optional. Additional options to include with the request, such as pagination or filtering parameters. * * @return mixed * @@ -368,12 +380,13 @@ public function removeMembers( * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function getMemberRoles( - string $organizationId, - string $userId, - array $params = [] - ) { - return $this->apiClient->method('get') - ->addPath('organizations', $organizationId, 'members', $userId) + string $organization, + string $user, + array $params = [] + ) + { + return $this->apiClient->method('get') + ->addPath('organizations', $organization, 'members', $user) ->withDictParams($this->normalizeRequest($params)) ->call(); } @@ -382,9 +395,9 @@ public function getMemberRoles( * Add a role to a member (user) in an organization. * Required scope: "create:organization_member_roles" * - * @param string $organizationId Organization (by ID) user belongs to. - * @param string $userId User (by ID) to add role to. - * @param string $roleId Role (by ID) to add to the user. + * @param string $organization Organization (by ID) user belongs to. + * @param string $user User (by ID) to add role to. + * @param string $role Role (by ID) to add to the user. * * @return mixed * @@ -393,20 +406,21 @@ public function getMemberRoles( * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function addMemberRole( - string $organizationId, - string $userId, - string $roleId - ) { - return $this->addMemberRoles($organizationId, $userId, [ $roleId ]); + string $organization, + string $user, + string $role + ) + { + return $this->addMemberRoles($organization, $user, [ $role ]); } /** * Add one or more roles to a member (user) in an organization. * Required scope: "create:organization_member_roles" * - * @param string $organizationId Organization (by ID) user belongs to. - * @param string $userId User (by ID) to add roles to. - * @param array $roleIds One or more roles (by ID) to add to the user. + * @param string $organization Organization (by ID) user belongs to. + * @param string $user User (by ID) to add roles to. + * @param array $roles One or more roles (by ID) to add to the user. * * @return mixed * @@ -415,16 +429,17 @@ public function addMemberRole( * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function addMemberRoles( - string $organizationId, - string $userId, - array $roleIds - ) { - $payload = [ - 'roles' => $roleIds - ]; + string $organization, + string $user, + array $roles + ) + { + $payload = [ + 'roles' => $roles + ]; - return $this->apiClient->method('post') - ->addPath('organizations', $organizationId, 'members', $userId) + return $this->apiClient->method('post') + ->addPath('organizations', $organization, 'members', $user) ->withBody(json_encode($payload)) ->call(); } @@ -433,9 +448,9 @@ public function addMemberRoles( * Remove a role from a member (user) in an organization. * Required scope: "delete:organization_member_roles" * - * @param string $organizationId Organization (by ID) user belongs to. - * @param string $userId User (by ID) to remove roles from. - * @param string $roleId Role (by ID) to remove from the user. + * @param string $organization Organization (by ID) user belongs to. + * @param string $user User (by ID) to remove roles from. + * @param string $role Role (by ID) to remove from the user. * * @return mixed * @@ -444,20 +459,21 @@ public function addMemberRoles( * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function removeMemberRole( - string $organizationId, - string $userId, - string $roleId - ) { - return $this->removeMemberRoles($organizationId, $userId, [ $roleId ]); + string $organization, + string $user, + string $role + ) + { + return $this->removeMemberRoles($organization, $user, [ $role ]); } /** * Remove one or more roles from a member (user) in an organization. * Required scope: "delete:organization_member_roles" * - * @param string $organizationId Organization (by ID) user belongs to. - * @param string $userId User (by ID) to remove roles from. - * @param array $roleIds One or more roles (by ID) to remove from the user. + * @param string $organization Organization (by ID) user belongs to. + * @param string $user User (by ID) to remove roles from. + * @param array $roles One or more roles (by ID) to remove from the user. * * @return mixed * @@ -466,16 +482,17 @@ public function removeMemberRole( * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function removeMemberRoles( - string $organizationId, - string $userId, - array $roleIds - ) { - $payload = [ - 'roles' => $roleIds - ]; + string $organization, + string $user, + array $roles + ) + { + $payload = [ + 'roles' => $roles + ]; - return $this->apiClient->method('delete') - ->addPath('organizations', $organizationId, 'members', $userId) + return $this->apiClient->method('delete') + ->addPath('organizations', $organization, 'members', $user) ->withBody(json_encode($payload)) ->call(); } @@ -490,9 +507,10 @@ public function removeMemberRoles( * @throws EmptyOrInvalidParameterException When an improperly formatted branding customization is provided. */ protected function validateBranding( - array $branding - ) { - // TODO - return true; + array $branding + ) + { + // #TODO + return true; } } diff --git a/src/Auth0.php b/src/Auth0.php index c3c8c31f..2bc16399 100644 --- a/src/Auth0.php +++ b/src/Auth0.php @@ -97,7 +97,7 @@ class Auth0 * * @var string */ - protected $organizationId; + protected $organization; /** * Scope for ID tokens and /userinfo endpoint @@ -285,7 +285,7 @@ public function __construct(array $config) $this->clientSecret = self::urlSafeBase64Decode($this->clientSecret); } - $this->organizationId = $config['organization_id'] ?? $_ENV['AUTH0_ORGANIZATION_ID'] ?? null; + $this->organization = $config['organization'] ?? $_ENV['AUTH0_ORGANIZATION'] ?? null; $this->audience = $config['audience'] ?? null; $this->responseMode = $config['response_mode'] ?? 'query'; @@ -355,7 +355,7 @@ public function __construct(array $config) $this->audience, $this->scope, $this->guzzleOptions, - $this->organizationId + $this->organization ); $this->user = $this->store->get('user'); @@ -702,7 +702,7 @@ public function decodeIdToken(string $idToken, array $verifierOptions = []) : ar } $verifierOptions = $verifierOptions + [ - 'org_id' => $this->organizationId, + 'org_id' => $this->organization, 'leeway' => $this->idTokenLeeway, 'max_age' => $this->transientHandler->getOnce('max_age') ?? $this->maxAge, self::TRANSIENT_NONCE_KEY => $this->transientHandler->getOnce(self::TRANSIENT_NONCE_KEY) diff --git a/src/Helpers/Tokens/IdTokenVerifier.php b/src/Helpers/Tokens/IdTokenVerifier.php index 80a5a5d3..4798c9b7 100644 --- a/src/Helpers/Tokens/IdTokenVerifier.php +++ b/src/Helpers/Tokens/IdTokenVerifier.php @@ -100,10 +100,10 @@ public function verify(string $token, array $options = []) : array /* * Organization check */ - $tokenOrgId = $verifiedToken['org_id'] ?? null; + $tokenOrgId = $verifiedToken['org_id'] ?? null; $expectedOrgId = $options['org_id'] ?? null; - if (null !== $expectedOrgId && (null === $tokenOrgId || !is_string($tokenOrgId))) { + if (null !== $expectedOrgId && (null === $tokenOrgId || ! is_string($tokenOrgId))) { throw new InvalidTokenException('Organization Id (org_id) claim must be a string present in the ID token'); } From 946bc0509252fbbd29e51aa1802ca60e1f23b31f Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 16 Mar 2021 13:46:55 -0400 Subject: [PATCH 11/38] feat: Add helpers functions for parsing invitation query parameters from request --- src/Auth0.php | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/Auth0.php b/src/Auth0.php index 2bc16399..eff093d6 100644 --- a/src/Auth0.php +++ b/src/Auth0.php @@ -767,6 +767,49 @@ protected function getState() return $state; } + /** + * If invitation parameters are present in the request, handle extraction and automatically redirect to Universal Login. + * + * @return void + */ + protected function handleInvitation() + { + if ($invite = $this->getInvitationParameters()) { + $this->login(null, null, [ + 'invitation' => $invite->invitation, + 'organization' => $invite->organization + ]); + } + } + + /** + * Get the invitation details GET request + * + * @return object|null + */ + protected function getInvitationParameters() + { + $invite = null; + $orgId = null; + $orgName = null; + + if ($this->responseMode === 'query') { + $invite = (isset($_GET['invitation']) ? filter_var($_GET['invitation'], FILTER_SANITIZE_STRING, FILTER_NULL_ON_FAILURE) : null); + $orgId = (isset($_GET['organization']) ? filter_var($_GET['organization'], FILTER_SANITIZE_STRING, FILTER_NULL_ON_FAILURE) : null); + $orgName = (isset($_GET['organization_name']) ? filter_var($_GET['organization_name'], FILTER_SANITIZE_STRING, FILTER_NULL_ON_FAILURE) : null); + } + + if ($invite && $orgId && $orgName) { + return (object)[ + 'invitation' => $invite, + 'organization' => $orgId, + 'organizationName' => $orgName + ]; + } + + return null; + } + /** * Delete any persistent data and clear out all stored properties * From 26c24f861c0ffdc6b5b6c1c68da2f24748e752dd Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 16 Mar 2021 13:47:03 -0400 Subject: [PATCH 12/38] docs: Update README with orgs examples --- README.md | 132 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 115 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 66ed0753..5ea731b2 100644 --- a/README.md +++ b/README.md @@ -16,18 +16,29 @@ Our PHP SDK provides a straight-forward and rigorously tested interface for acce This is [one of many libraries we offer](https://auth0.com/docs/libraries) supporting numerous platforms. -## Documentation - -- [Documentation](https://auth0.com/docs/libraries/auth0-php) - - [Installation](https://auth0.com/docs/libraries/auth0-php#installation) - - [Getting Started](https://auth0.com/docs/libraries/auth0-php#getting-started) - - [Basic Usage](https://auth0.com/docs/libraries/auth0-php/auth0-php-basic-use) - - [Authentication API](https://auth0.com/docs/libraries/auth0-php/using-the-authentication-api-with-auth0-php) - - [Management API](https://auth0.com/docs/libraries/auth0-php/using-the-management-api-with-auth0-php) - - [Troubleshooting](https://auth0.com/docs/libraries/auth0-php/troubleshoot-auth0-php-library) -- Quickstarts - - [Basic authentication example](https://auth0.com/docs/quickstart/webapp/php/) ([GitHub repo](https://github.com/auth0-samples/auth0-php-web-app/tree/master/00-Starter-Seed)) - - [Authenticated backend API example](https://auth0.com/docs/quickstart/backend/php/) ([GitHub repo](https://github.com/auth0-samples/auth0-php-api-samples/tree/master/01-Authenticate)) +## Table of Contents + +- [Table of Contents](#table-of-contents) +- [Requirements](#requirements) +- [Installation](#installation) +- [Getting Started](#getting-started) + - [Authentication API](#authentication-api) + - [Management API](#management-api) +- [Examples](#examples) + - [Organizations (Closed Beta)](#organizations-closed-beta) + - [Logging in with an Organization](#logging-in-with-an-organization) + - [Accepting user invitations](#accepting-user-invitations) +- [Documentation](#documentation) +- [Contributing](#contributing) +- [Support + Feedback](#support--feedback) +- [Vulnerability Reporting](#vulnerability-reporting) +- [What is Auth0?](#what-is-auth0) +- [License](#license) + +## Requirements + +- PHP 7.3+ / 8.0+ +- [Composer](https://getcomposer.org/) ## Installation @@ -43,7 +54,7 @@ Guidance on setting up Composer and alternative installation methods can be foun To get started, you'll need to create a [free Auth0 account](https://auth0.com/signup) and register an [Application](https://auth0.com/docs/applications). -### Authentication API Client +### Authentication API Begin by instantiating the SDK and passing the relevant details from your Application's settings page: @@ -68,9 +79,9 @@ Using the SDK, making requests to Auth0's endpoints couldn't be simpler. For exa ```php // Do we have an authenticated session available? -if ($userinfo = $auth0->getUser()) { +if ($user = $auth0->getUser()) { // Output the authenticated user - var_dump($userinfo); + print_r($user); exit; } @@ -80,7 +91,7 @@ $auth0->login(); Further examples of how you can use the Authentication API Client can be found on [our documentation site](https://auth0.com/docs/libraries/auth0-php/). -### Management API Client +### Management API This SDK also offers an interface for Auth0's Management API which, in order to access, requires an Access Token that is issued specifically for your tenant's Management API by specifying the corresponding Audience. @@ -89,7 +100,7 @@ The process for retrieving such an Access Token is described in our [documentati ```php use Auth0\SDK\API\Management; -$mgmt_api = new Management('{{YOUR_ACCESS_TOKEN}}', 'https://{{YOUR_TENANT}}.auth0.com'); +$mgmt_api = new Management('{YOUR_ACCESS_TOKEN}', 'https://{YOUR_TENANT}.auth0.com'); ``` The SDK provides convenient interfaces to the Management API's endpoints. For example, to search for users: @@ -115,6 +126,93 @@ if (! empty($results)) { At the moment the best way to see what endpoints are covered is to read through the `\Auth0\SDK\API\Management` class, [available here](https://github.com/auth0/auth0-PHP/blob/master/src/API/Management.php). +## Examples + +### Organizations (Closed Beta) + +Organizations is a set of features that provide better support for developers who build and maintain SaaS and Business-to-Business (B2B) applications. + +Using Organizations, you can: + +- Represent teams, business customers, partner companies, or any logical grouping of users that should have different ways of accessing your applications, as organizations. +- Manage their membership in a variety of ways, including user invitation. +- Configure branded, federated login flows for each organization. +- Implement role-based access control, such that users can have different roles when authenticating in the context of different organizations. +- Build administration capabilities into your products, using Organizations APIs, so that those businesses can manage their own organizations. + +Note that Organizations is currently only available to customers on our Enterprise and Startup subscription plans. + +#### Logging in with an Organization + +Configure the Authentication API client with your Organization ID: + +```php +use Auth0\SDK\Auth0; + +$auth0 = new Auth0([ + // Found in your Auth0 dashboard, under Organization settings: + 'organization' => '{YOUR_ORGANIZATION_ID}', + + // Found in your Auth0 dashboard, under Application settings: + 'domain' => '{YOUR_TENANT}.auth0.com', + 'client_id' => '{YOUR_APPLICATION_CLIENT_ID}', + 'redirect_uri' => 'https://{YOUR_APPLICATION_CALLBACK_URL}', +]); +``` + +Redirect to the Universal Login page using the organization: + +```php +$auth0->login(); +``` + +#### Accepting user invitations + +Auth0 Organizations allow users to be invited using emailed links, which will direct a user back to your application. The URL the user will arrive at is based on your configured `Application Login URI`, which you can change from your Application's settings inside the Auth0 dashboard. + +When the user arrives at your application using an invite link, you can expect three query parameters to be provided: `invitation`, `organization`, and `organization_name`. These will always be delivered using a GET request. + +A helper function is provided to handle extracting these query parameters and automatically redirecting to the Universal Login page: + +```php +// Expects the Auth0 SDK to be configured first, as demonstrated above. +$auth0->handleInvitation(); +``` + +If you prefer to have more control over this process, a separate helper function is provided for extracting the query parameters, `getInvitationParameters()`, which you can use to initiate the Universal Login redirect yourself: + +```php +// Expects the Auth0 SDK to be configured first, as demonstrated above. + +// Returns an object containing the invitation query parameters, or null if they aren't present +if ($invite = $auth0->getInvitationParameters()) { + // Does the invite organization match your application's intended organization? + if ($invite->organization !== $auth0->organization) { + throw new Exception("These aren't the droids you're looking for."); + } + + // Redirect to Universal Login using the emailed invitation + $auth0->login(null, null, [ + 'invitation' => $invite->invitation + ]); +} +``` + +After successful authentication via the Universal Login page, the user will arrive back at your application using your configured `redirect_uri`, their token will be automatically validated, and the user will have an authenticated session. Use `getUser()` to retrieve details about the authenticated user. + +## Documentation + +- [Documentation](https://auth0.com/docs/libraries/auth0-php) + - [Installation](https://auth0.com/docs/libraries/auth0-php#installation) + - [Getting Started](https://auth0.com/docs/libraries/auth0-php#getting-started) + - [Basic Usage](https://auth0.com/docs/libraries/auth0-php/auth0-php-basic-use) + - [Authentication API](https://auth0.com/docs/libraries/auth0-php/using-the-authentication-api-with-auth0-php) + - [Management API](https://auth0.com/docs/libraries/auth0-php/using-the-management-api-with-auth0-php) + - [Troubleshooting](https://auth0.com/docs/libraries/auth0-php/troubleshoot-auth0-php-library) +- Quickstarts + - [Basic authentication example](https://auth0.com/docs/quickstart/webapp/php/) ([GitHub repo](https://github.com/auth0-samples/auth0-php-web-app/tree/master/00-Starter-Seed)) + - [Authenticated backend API example](https://auth0.com/docs/quickstart/backend/php/) ([GitHub repo](https://github.com/auth0-samples/auth0-php-api-samples/tree/master/01-Authenticate)) + ## Contributing We appreciate your feedback and contributions to the project! Before you get started, please review the following: From 84f6f1300437df3e920cd4575aeb46f32cb5d7d2 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 16 Mar 2021 13:58:58 -0400 Subject: [PATCH 13/38] docs: Update README --- README.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/README.md b/README.md index 5ea731b2..88ea8d52 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,6 @@ Our PHP SDK provides a straight-forward and rigorously tested interface for acce This is [one of many libraries we offer](https://auth0.com/docs/libraries) supporting numerous platforms. -## Table of Contents - -- [Table of Contents](#table-of-contents) - [Requirements](#requirements) - [Installation](#installation) - [Getting Started](#getting-started) @@ -160,7 +157,7 @@ $auth0 = new Auth0([ ]); ``` -Redirect to the Universal Login page using the organization: +Redirect to the Universal Login page using the configured organization: ```php $auth0->login(); From 5855e56eee62cf2b325b91dbb55c16666b6cf779 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 16 Mar 2021 14:32:04 -0400 Subject: [PATCH 14/38] docs: Update README --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 88ea8d52..d40ab960 100644 --- a/README.md +++ b/README.md @@ -183,14 +183,15 @@ If you prefer to have more control over this process, a separate helper function // Returns an object containing the invitation query parameters, or null if they aren't present if ($invite = $auth0->getInvitationParameters()) { - // Does the invite organization match your application's intended organization? - if ($invite->organization !== $auth0->organization) { + // Does the invite organization match your intended organization? + if ($invite->organization !== '{MY_ORGANIZATION_ID}') { throw new Exception("These aren't the droids you're looking for."); } // Redirect to Universal Login using the emailed invitation $auth0->login(null, null, [ - 'invitation' => $invite->invitation + 'invitation' => $invite->invitation, + 'organization' => $invite->organization ]); } ``` From 85391574ec99b837d5157235b84f59c8c854cd88 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 16 Mar 2021 14:36:14 -0400 Subject: [PATCH 15/38] docs: Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d40ab960..3212f571 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,7 @@ If you prefer to have more control over this process, a separate helper function // Returns an object containing the invitation query parameters, or null if they aren't present if ($invite = $auth0->getInvitationParameters()) { // Does the invite organization match your intended organization? - if ($invite->organization !== '{MY_ORGANIZATION_ID}') { + if ($invite->organization !== '{YOUR_ORGANIZATION_ID}') { throw new Exception("These aren't the droids you're looking for."); } From 94d4b0fc8d5f98d6328a69e4a0008b33d3998337 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 16 Mar 2021 15:12:34 -0400 Subject: [PATCH 16/38] feat: Allow instantiating singleton of Organizations class from Management class. --- src/API/Management.php | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/API/Management.php b/src/API/Management.php index 3d6b9a2c..49e9b512 100644 --- a/src/API/Management.php +++ b/src/API/Management.php @@ -18,6 +18,7 @@ use Auth0\SDK\API\Management\Logs; use Auth0\SDK\API\Management\LogStreams; use Auth0\SDK\API\Management\ResourceServers; +use Auth0\SDK\API\Management\Organizations; use Auth0\SDK\API\Management\Roles; use Auth0\SDK\API\Management\Rules; use Auth0\SDK\API\Management\Stats; @@ -126,6 +127,13 @@ class Management */ private $logStreams; + /** + * Instance of Auth0\SDK\API\Management\Organizations + * + * @var Organizations + */ + private $organizations; + /** * Instance of Auth0\SDK\API\Management\Roles * @@ -381,6 +389,20 @@ public function logStreams() : LogStreams return $this->logStreams; } + /** + * Return an instance of the Organizations class. + * + * @return Organizations + */ + public function organizations() : LogStreams + { + if (! $this->organizations instanceof Organizations) { + $this->organizations = new Organizations($this->apiClient); + } + + return $this->organizations(); + } + /** * Return an instance of the Roles class. * From ecdcad26dbaf355d68362e85df71c30bf1799774 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 30 Mar 2021 00:59:22 -0400 Subject: [PATCH 17/38] chore: Update phpunit.xml.dist to newer configuration format --- phpunit.xml.dist | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 9bbb4fd1..3a6321ba 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,10 @@ - + + + + + ./src/ + + ./tests/unit @@ -10,10 +13,4 @@ ./tests/integration - - - - ./src/ - - From c1342c910c7d90ac8821c828cdd66c892d9db21e Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 30 Mar 2021 01:07:30 -0400 Subject: [PATCH 18/38] chore: Coding standards --- src/Auth0.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Auth0.php b/src/Auth0.php index 9990a8b2..2ce1c144 100644 --- a/src/Auth0.php +++ b/src/Auth0.php @@ -778,8 +778,8 @@ public function handleInvitation() { if ($invite = $this->getInvitationParameters()) { $this->login(null, null, [ - 'invitation' => $invite->invitation, - 'organization' => $invite->organization + 'invitation' => $invite->invitation, + 'organization' => $invite->organization ]); } } @@ -791,18 +791,18 @@ public function handleInvitation() */ public function getInvitationParameters() { - $invite = null; - $orgId = null; + $invite = null; + $orgId = null; $orgName = null; if ($this->responseMode === 'query') { - $invite = (isset($_GET['invitation']) ? filter_var($_GET['invitation'], FILTER_SANITIZE_STRING, FILTER_NULL_ON_FAILURE) : null); - $orgId = (isset($_GET['organization']) ? filter_var($_GET['organization'], FILTER_SANITIZE_STRING, FILTER_NULL_ON_FAILURE) : null); + $invite = (isset($_GET['invitation']) ? filter_var($_GET['invitation'], FILTER_SANITIZE_STRING, FILTER_NULL_ON_FAILURE) : null); + $orgId = (isset($_GET['organization']) ? filter_var($_GET['organization'], FILTER_SANITIZE_STRING, FILTER_NULL_ON_FAILURE) : null); $orgName = (isset($_GET['organization_name']) ? filter_var($_GET['organization_name'], FILTER_SANITIZE_STRING, FILTER_NULL_ON_FAILURE) : null); } if ($invite && $orgId && $orgName) { - return (object)[ + return (object) [ 'invitation' => $invite, 'organization' => $orgId, 'organizationName' => $orgName From cc3f08144d4cae4078fe7a2a4ef5630b57495fbf Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 30 Mar 2021 01:07:54 -0400 Subject: [PATCH 19/38] feat: Continue implimenting Organizations to Management API --- src/API/Management.php | 4 +- src/API/Management/Organizations.php | 291 ++++++++++++++++----------- 2 files changed, 176 insertions(+), 119 deletions(-) diff --git a/src/API/Management.php b/src/API/Management.php index 49e9b512..7ea1462a 100644 --- a/src/API/Management.php +++ b/src/API/Management.php @@ -394,13 +394,13 @@ public function logStreams() : LogStreams * * @return Organizations */ - public function organizations() : LogStreams + public function organizations() : Organizations { if (! $this->organizations instanceof Organizations) { $this->organizations = new Organizations($this->apiClient); } - return $this->organizations(); + return $this->organizations; } /** diff --git a/src/API/Management/Organizations.php b/src/API/Management/Organizations.php index f95fd77a..2cbaba5d 100644 --- a/src/API/Management/Organizations.php +++ b/src/API/Management/Organizations.php @@ -13,31 +13,55 @@ */ class Organizations extends GenericResource { + /** - * List all organizations. - * Required scope: "read:organizations" + * Create an organization. + * Required scope: "create:organizations" * - * @param array $params Optional. Options to include with the request, such as pagination or filtering parameters. + * @param string $name The name of the Organization. Cannot be changed later. + * @param string $displayName The displayed name of the Organization. + * @param null|array $branding An array containing branding customizations for the organization. + * @param null|array $metadata Optional. Additional metadata to store about the organization. + * @param array $additionalParameters Optional. Additional parameters to send with the API request. * * @return mixed * * @throws RequestException When API request fails. Reason for failure provided in exception message. + * + * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ - public function getAll( - array $params = [] + public function create( + string $name, + string $displayName, + ?array $branding = null, + ?array $metadata = null, + array $additionalParameters = [] ) { - return $this->apiClient->method('get') - ->addPath('organizations') - ->withDictParams($this->normalizeRequest($params)) - ->call(); + $this->validateBranding($branding); + + $payload = (object)array_filter([ + 'name' => $name, + 'display_name' => $displayName, + 'branding' => $branding ? (object)$branding : null, + 'metadata' => $metadata ? (object)$metadata : null, + ] + $additionalParameters); + + return $this->apiClient->method('post') + ->addPath('organizations') + ->withBody(json_encode($payload)) + ->call(); } /** - * Get details about an organization, queried by it's ID. - * Required scope: "read:organizations" + * Update an organization. + * Required scope: "update:organizations" * - * @param string $organization Organization (by ID) to retrieve details for. + * @param string $organization Organization (by ID) to update. + * @param string $displayName The displayed name of the Organization. + * @param null|array $branding An array containing branding customizations for the organization. + * @param null|array $metadata Optional. Additional metadata to store about the organization. + * @param array $additionalParameters Optional. Additional parameters to send with the API request. * * @return mixed * @@ -45,20 +69,33 @@ public function getAll( * * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ - public function get( - string $organization + public function update( + string $organization, + string $displayName, + ?array $branding = null, + ?array $metadata = null, + array $additionalParameters = [] ) { - return $this->apiClient->method('get') - ->addPath('organizations', $organization) - ->call(); + $this->validateBranding($branding); + + $payload = (object)array_filter([ + 'display_name' => $displayName, + 'branding' => $branding ? (object)$branding : null, + 'metadata' => $metadata ? (object)$metadata : null, + ] + $additionalParameters); + + return $this->apiClient->method('patch') + ->addPath('organizations', $organization) + ->withBody(json_encode($payload)) + ->call(); } /** - * Get details about an organization, queried by it's `name`. - * Required scope: "read:organizations" + * Delete an organization. + * Required scope: "delete:organizations" * - * @param string $organizationName Organization (by name parameter provided during creation) to retrieve details for. + * @param string $organization Organization (by ID) to delete. * * @return mixed * @@ -66,24 +103,40 @@ public function get( * * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ - public function getByName( - string $organizationName + public function delete( + string $organization + ) + { + return $this->apiClient->method('delete') + ->addPath('organizations', $organization) + ->call(); + } + + /** + * List all organizations. + * Required scope: "read:organizations" + * + * @param array $params Optional. Options to include with the request, such as pagination or filtering parameters. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + */ + public function getAll( + array $params = [] ) { return $this->apiClient->method('get') - ->addPath('organizations', 'name', $organizationName) - ->call(); + ->addPath('organizations') + ->withDictParams($this->normalizeRequest($params)) + ->call(); } /** - * Create an organization. - * Required scope: "create:organizations" + * Get details about an organization, queried by it's ID. + * Required scope: "read:organizations" * - * @param string $name The name of the Organization. Cannot be changed later. - * @param string $displayName The displayed name of the Organization. - * @param array $branding An array containing branding customizations for the organization. - * @param array $metadata Optional. Additional metadata to store about the organization. - * @param array $additionalParameters Optional. Additional parameters to send with the API request. + * @param string $organization Organization (by ID) to retrieve details for. * * @return mixed * @@ -91,38 +144,20 @@ public function getByName( * * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ - public function create( - string $name, - string $displayName, - array $branding, - array $metadata = [], - array $additionalParameters = [] + public function get( + string $organization ) { - $this->validateBranding($branding); - - $payload = [ - 'name' => $name, - 'display_name' => $displayName, - 'branding' => $branding, - 'metadata' => $metadata, - ] + $additionalParameters; - - return $this->apiClient->method('post') - ->addPath('organizations') - ->withBody(json_encode($payload)) - ->call(); + return $this->apiClient->method('get') + ->addPath('organizations', $organization) + ->call(); } /** - * Update an organization. - * Required scope: "update:organizations" + * Get details about an organization, queried by it's `name`. + * Required scope: "read:organizations" * - * @param string $organization Organization (by ID) to update. - * @param string $displayName The displayed name of the Organization. - * @param array $branding An array containing branding customizations for the organization. - * @param array $metadata Optional. Additional metadata to store about the organization. - * @param array $additionalParameters Optional. Additional parameters to send with the API request. + * @param string $organizationName Organization (by name parameter provided during creation) to retrieve details for. * * @return mixed * @@ -130,33 +165,21 @@ public function create( * * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ - public function patch( - string $organization, - string $displayName, - array $branding, - array $metadata = [], - array $additionalParameters = [] + public function getByName( + string $organizationName ) { - $this->validateBranding($branding); - - $payload = [ - 'display_name' => $displayName, - 'branding' => $branding, - 'metadata' => $metadata, - ] + $additionalParameters; - - return $this->apiClient->method('patch') - ->addPath('organizations', $organization) - ->withBody(json_encode($payload)) - ->call(); + return $this->apiClient->method('get') + ->addPath('organizations', 'name', $organizationName) + ->call(); } /** - * Delete an organization. - * Required scope: "delete:organizations" + * List the enabled connections associated with an organization. + * Required scope: "read:organization_connections" * - * @param string $organization Organization (by ID) to delete. + * @param string $organization Organization (by ID) to list connections of. + * @param array $params Optional. Additional options to include with the request, such as pagination or filtering parameters. * * @return mixed * @@ -164,17 +187,19 @@ public function patch( * * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ - public function delete( - string $organization + public function getEnabledConnections( + string $organization, + array $params = [] ) { - return $this->apiClient->method('delete') - ->addPath('organizations', $organization) - ->call(); + return $this->apiClient->method('get') + ->addPath('organizations', $organization, 'enabled_connections') + ->withDictParams($this->normalizeRequest($params)) + ->call(); } /** - * List the connections associated with an organization. + * Get a connection (by ID) associated with an organization. * Required scope: "read:organization_connections" * * @param string $organization Organization (by ID) to list connections of. @@ -186,23 +211,23 @@ public function delete( * * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ - public function getConnections( + public function getEnabledConnection( string $organization, - array $params = [] + string $connection ) { return $this->apiClient->method('get') - ->addPath('organizations', $organization, 'connections') - ->withDictParams($this->normalizeRequest($params)) - ->call(); + ->addPath('organizations', $organization, 'enabled_connections', $connection) + ->call(); } /** * Add a connection to an organization. * Required scope: "create:organization_connections" * - * @param string $organization Organization (by ID) to add a connection to. - * @param string $connection Connection (by ID) to add to organization. + * @param string $organization Organization (by ID) to add a connection to. + * @param string $connection Connection (by ID) to add to organization. + * @param array $additionalParameters Optional. Additional parameters to send with the API request. * * @return mixed * @@ -210,14 +235,46 @@ public function getConnections( * * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ - public function addConnection( + public function addEnabledConnection( string $organization, - string $connection + string $connection, + array $additionalParameters = [] ) { + $payload = (object)array_filter([ + 'connection_id' => $connection + ] + $additionalParameters); + return $this->apiClient->method('post') - ->addPath('organizations', $organization, 'connections', $connection) - ->call(); + ->addPath('organizations', $organization, 'enabled_connections') + ->withBody(json_encode($payload)) + ->call(); + } + + /** + * Update a connection to an organization. + * Required scope: "update:organization_connections" + * + * @param string $organization Organization (by ID) to add a connection to. + * @param string $connection Connection (by ID) to add to organization. + * @param array $additionalParameters Optional. Additional parameters to send with the API request. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + * + * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO + */ + public function updateEnabledConnection( + string $organization, + string $connection, + array $params = [] + ) + { + return $this->apiClient->method('patch') + ->addPath('organizations', $organization, 'enabled_connections', $connection) + ->withBody(json_encode((object)$params)) + ->call(); } /** @@ -233,14 +290,14 @@ public function addConnection( * * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ - public function removeConnection( + public function removeEnabledConnection( string $organization, string $connection ) { return $this->apiClient->method('delete') - ->addPath('organizations', $organization, 'connections', $connection) - ->call(); + ->addPath('organizations', $organization, 'enabled_connections', $connection) + ->call(); } /** @@ -262,9 +319,9 @@ public function getMembers( ) { return $this->apiClient->method('get') - ->addPath('organizations', $organization, 'members') - ->withDictParams($this->normalizeRequest($params)) - ->call(); + ->addPath('organizations', $organization, 'members') + ->withDictParams($this->normalizeRequest($params)) + ->call(); } /** @@ -311,9 +368,9 @@ public function addMembers( ]; return $this->apiClient->method('post') - ->addPath('organizations', $organization, 'members') - ->withBody(json_encode($payload)) - ->call(); + ->addPath('organizations', $organization, 'members') + ->withBody(json_encode($payload)) + ->call(); } /** @@ -360,9 +417,9 @@ public function removeMembers( ]; return $this->apiClient->method('delete') - ->addPath('organizations', $organization, 'members') - ->withBody(json_encode($payload)) - ->call(); + ->addPath('organizations', $organization, 'members') + ->withBody(json_encode($payload)) + ->call(); } /** @@ -386,9 +443,9 @@ public function getMemberRoles( ) { return $this->apiClient->method('get') - ->addPath('organizations', $organization, 'members', $user) - ->withDictParams($this->normalizeRequest($params)) - ->call(); + ->addPath('organizations', $organization, 'members', $user, 'roles') + ->withDictParams($this->normalizeRequest($params)) + ->call(); } /** @@ -439,9 +496,9 @@ public function addMemberRoles( ]; return $this->apiClient->method('post') - ->addPath('organizations', $organization, 'members', $user) - ->withBody(json_encode($payload)) - ->call(); + ->addPath('organizations', $organization, 'members', $user, 'roles') + ->withBody(json_encode($payload)) + ->call(); } /** @@ -492,9 +549,9 @@ public function removeMemberRoles( ]; return $this->apiClient->method('delete') - ->addPath('organizations', $organization, 'members', $user) - ->withBody(json_encode($payload)) - ->call(); + ->addPath('organizations', $organization, 'members', $user, 'roles') + ->withBody(json_encode($payload)) + ->call(); } /** @@ -507,7 +564,7 @@ public function removeMemberRoles( * @throws EmptyOrInvalidParameterException When an improperly formatted branding customization is provided. */ protected function validateBranding( - array $branding + ?array $branding = null ) { // #TODO From 878d845e0768d70afb518032c0183fc566200b76 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 30 Mar 2021 01:08:29 -0400 Subject: [PATCH 20/38] tests: Add integration tests for Organizations on Management API --- .../OrganizationsIntegrationTest.php | 283 ++++++++++++++++++ 1 file changed, 283 insertions(+) create mode 100644 tests/integration/API/Management/OrganizationsIntegrationTest.php diff --git a/tests/integration/API/Management/OrganizationsIntegrationTest.php b/tests/integration/API/Management/OrganizationsIntegrationTest.php new file mode 100644 index 00000000..d9101b4b --- /dev/null +++ b/tests/integration/API/Management/OrganizationsIntegrationTest.php @@ -0,0 +1,283 @@ +management = new Management($env['API_TOKEN'], $env['DOMAIN'], ['timeout' => 30]); + + $this->resources = [ + 'name' => uniqid('php-sdk-test-organization-'), + 'display_name' => 'PHP SDK Integration Test (DELETE)', + 'branding' => [ + 'logo_uri' => 'https://cdn.auth0.com/website/bob/press/logo-light.png', + 'colors' => [ + 'primary' => '#eb5424', + 'page_background' => '#222228' + ] + ] + ]; + + $this->api = $this->management->organizations(); + $this->organization = $this->api->create($this->resources['name'], $this->resources['display_name']); + + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $this->connection = $this->management->connections()->create([ + 'name' => uniqid('php-sdk-test-connection-'), + 'strategy' => 'auth0', + 'enabled_clients' => [ $env['APP_CLIENT_ID'] ] + ]); + + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $this->api->addEnabledConnection($this->organization['id'], $this->connection['id']); + + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $this->user = $this->management->users()->create([ + 'email' => uniqid('php-sdk-test-user-') . '@test.com', + 'connection' => $this->connection['name'], + 'email_verified' => true, + 'password' => password_hash(uniqid('php-sdk-test-password-'), PASSWORD_DEFAULT) + ]); + + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $this->api->addMember($this->organization['id'], $this->user['user_id']); + + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $this->role = $this->management->roles()->create(uniqid('php-sdk-test-role-')); + } + + public function tearDown(): void + { + // Cleanup our test role. + if ($this->role) { + $this->management->roles()->delete($this->role['id']); + } + + // Cleanup our test user. + if ($this->user) { + $this->management->users()->delete($this->user['user_id']); + } + + // Cleanup our test connection. + if ($this->connection) { + $this->management->connections()->delete($this->connection['id']); + } + + // Cleanup our test organization. + if ($this->organization) { + $this->api->delete($this->organization['id']); + } + } + + public function testCreate() + { + $this->assertArrayHasKey('id', $this->organization); + + $this->assertArrayHasKey('name', $this->organization); + $this->assertEquals($this->organization['name'], $this->resources['name']); + + $this->assertArrayHasKey('display_name', $this->organization); + $this->assertEquals($this->organization['display_name'], $this->resources['display_name']); + } + + public function testUpdate() + { + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $this->resources['display_name'] .= ' (UPDATED)'; + $this->api->update($this->organization['id'], $this->resources['display_name']); + + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $this->organization = $this->api->get($this->organization['id']); + + $this->assertArrayHasKey('name', $this->organization); + $this->assertEquals($this->organization['name'], $this->resources['name']); + } + + public function testGetAll() + { + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $response = $this->api->getAll(); + + $this->assertIsArray($response); + $this->assertNotEmpty($response); + } + + public function testGetAllSupportsPagination() + { + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $response = $this->api->getAll([ + 'page' => 1 + ]); + + $this->assertIsArray($response); + } + + public function testGet() + { + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $response = $this->api->get($this->organization['id']); + + $this->assertIsArray($response); + $this->assertNotEmpty($response); + $this->assertArrayHasKey('id', $this->organization); + } + + public function testGetByName() + { + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $response = $this->api->getByName($this->organization['name']); + + $this->assertIsArray($response); + $this->assertNotEmpty($response); + $this->assertArrayHasKey('id', $this->organization); + } + + public function testGetEnabledConnections() + { + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $response = $this->api->getEnabledConnections($this->organization['id']); + + $this->assertIsArray($response); + } + + public function testEnabledConnections() + { + // Confirm 'assign_membership_on_login' is currently false on the organization connection. + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $response = $this->api->getEnabledConnection($this->organization['id'], $this->connection['id']); + + $this->assertFalse($response['assign_membership_on_login']); + + // Change the 'assign_membership_on_login' property on the organization connection. + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $this->api->updateEnabledConnection($this->organization['id'], $this->connection['id'], [ 'assign_membership_on_login' => true ]); + + // Confirm the change was recorded. + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $response = $this->api->getEnabledConnection($this->organization['id'], $this->connection['id']); + + $this->assertIsArray($response); + $this->assertArrayHasKey('connection_id', $response); + $this->assertEquals($this->connection['id'], $response['connection_id']); + $this->assertTrue($response['assign_membership_on_login']); + } + + public function testRetrieveMembers() + { + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $response = $this->api->getMembers($this->organization['id']); + + $this->assertIsArray($response); + $this->assertNotEmpty($response); + } + + public function testRetrieveMembersPaginated() + { + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $response = $this->api->getMembers($this->organization['id'], [ + 'page' => 1 + ]); + + $this->assertIsArray($response); + } + + public function testMemberRoles() + { + // Confirm that the organization member has no roles. + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $response = $this->api->getMemberRoles($this->organization['id'], $this->user['user_id']); + + $this->assertIsArray($response); + $this->assertEmpty($response); + + // Add our role to the organization member. + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $this->api->addMemberRole($this->organization['id'], $this->user['user_id'], $this->role['id']); + + // Confirm that the organization member now has the role. + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $response = $this->api->getMemberRoles($this->organization['id'], $this->user['user_id']); + + $this->assertIsArray($response); + $this->assertNotEmpty($response); + $this->assertArrayHasKey('id', $response[0]); + $this->assertContainsEquals($this->role['id'], $response[0]); + + // Remove the role from the organization member. + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $response = $this->api->removeMemberRole($this->organization['id'], $this->user['user_id'], $this->role['id']); + + // Confirm that the organization member once again has no roles. + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $response = $this->api->getMemberRoles($this->organization['id'], $this->user['user_id']); + + $this->assertIsArray($response); + $this->assertEmpty($response); + } +} From 5bc8231f450791f6b755ecdf56cba0df96c2a46b Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 30 Mar 2021 20:15:28 -0400 Subject: [PATCH 21/38] feat: Add additional endpoints --- src/API/Management/Organizations.php | 149 ++++++++++++++++++++------- 1 file changed, 113 insertions(+), 36 deletions(-) diff --git a/src/API/Management/Organizations.php b/src/API/Management/Organizations.php index 2cbaba5d..9bba1181 100644 --- a/src/API/Management/Organizations.php +++ b/src/API/Management/Organizations.php @@ -18,11 +18,11 @@ class Organizations extends GenericResource * Create an organization. * Required scope: "create:organizations" * - * @param string $name The name of the Organization. Cannot be changed later. - * @param string $displayName The displayed name of the Organization. - * @param null|array $branding An array containing branding customizations for the organization. - * @param null|array $metadata Optional. Additional metadata to store about the organization. - * @param array $additionalParameters Optional. Additional parameters to send with the API request. + * @param string $name The name of the Organization. Cannot be changed later. + * @param string $displayName The displayed name of the Organization. + * @param null|array $branding An array containing branding customizations for the organization. + * @param null|array $metadata Optional. Additional metadata to store about the organization. + * @param array $params Optional. Additional parameters to send with the API request. * * @return mixed * @@ -35,17 +35,15 @@ public function create( string $displayName, ?array $branding = null, ?array $metadata = null, - array $additionalParameters = [] + array $params = [] ) { - $this->validateBranding($branding); - $payload = (object)array_filter([ 'name' => $name, 'display_name' => $displayName, 'branding' => $branding ? (object)$branding : null, 'metadata' => $metadata ? (object)$metadata : null, - ] + $additionalParameters); + ] + $params); return $this->apiClient->method('post') ->addPath('organizations') @@ -57,11 +55,11 @@ public function create( * Update an organization. * Required scope: "update:organizations" * - * @param string $organization Organization (by ID) to update. - * @param string $displayName The displayed name of the Organization. - * @param null|array $branding An array containing branding customizations for the organization. - * @param null|array $metadata Optional. Additional metadata to store about the organization. - * @param array $additionalParameters Optional. Additional parameters to send with the API request. + * @param string $organization Organization (by ID) to update. + * @param string $displayName The displayed name of the Organization. + * @param null|array $branding An array containing branding customizations for the organization. + * @param null|array $metadata Optional. Additional metadata to store about the organization. + * @param array $params Optional. Additional parameters to send with the API request. * * @return mixed * @@ -74,16 +72,14 @@ public function update( string $displayName, ?array $branding = null, ?array $metadata = null, - array $additionalParameters = [] + array $params = [] ) { - $this->validateBranding($branding); - $payload = (object)array_filter([ 'display_name' => $displayName, 'branding' => $branding ? (object)$branding : null, 'metadata' => $metadata ? (object)$metadata : null, - ] + $additionalParameters); + ] + $params); return $this->apiClient->method('patch') ->addPath('organizations', $organization) @@ -227,7 +223,7 @@ public function getEnabledConnection( * * @param string $organization Organization (by ID) to add a connection to. * @param string $connection Connection (by ID) to add to organization. - * @param array $additionalParameters Optional. Additional parameters to send with the API request. + * @param array $params Optional. Additional parameters to send with the API request. * * @return mixed * @@ -238,12 +234,12 @@ public function getEnabledConnection( public function addEnabledConnection( string $organization, string $connection, - array $additionalParameters = [] + array $params = [] ) { $payload = (object)array_filter([ 'connection_id' => $connection - ] + $additionalParameters); + ] + $params); return $this->apiClient->method('post') ->addPath('organizations', $organization, 'enabled_connections') @@ -257,7 +253,7 @@ public function addEnabledConnection( * * @param string $organization Organization (by ID) to add a connection to. * @param string $connection Connection (by ID) to add to organization. - * @param array $additionalParameters Optional. Additional parameters to send with the API request. + * @param array $params Optional. Additional parameters to send with the API request. * * @return mixed * @@ -475,9 +471,9 @@ public function addMemberRole( * Add one or more roles to a member (user) in an organization. * Required scope: "create:organization_member_roles" * - * @param string $organization Organization (by ID) user belongs to. - * @param string $user User (by ID) to add roles to. - * @param array $roles One or more roles (by ID) to add to the user. + * @param string $organization Organization (by ID) user belongs to. + * @param string $user User (by ID) to add roles to. + * @param array $roles One or more roles (by ID) to add to the user. * * @return mixed * @@ -528,9 +524,9 @@ public function removeMemberRole( * Remove one or more roles from a member (user) in an organization. * Required scope: "delete:organization_member_roles" * - * @param string $organization Organization (by ID) user belongs to. - * @param string $user User (by ID) to remove roles from. - * @param array $roles One or more roles (by ID) to remove from the user. + * @param string $organization Organization (by ID) user belongs to. + * @param string $user User (by ID) to remove roles from. + * @param array $roles One or more roles (by ID) to remove from the user. * * @return mixed * @@ -555,19 +551,100 @@ public function removeMemberRoles( } /** - * Validate an array containing branding customizations for use during the creation or updating of an organization. + * List invitations for an organization + * Required scope: "read:organization_invitations" + * + * @param string $organization Organization (by ID) to list invitations for. + * @param array $params Optional. Options to include with the request, such as pagination or filtering parameters. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + */ + public function getInvitations( + string $organization, + array $params = [] + ) + { + return $this->apiClient->method('get') + ->addPath('organizations', $organization, 'invitations') + ->withDictParams($this->normalizeRequest($params)) + ->call(); + } + + /** + * Get an invitation (by ID) for an organization + * Required scope: "read:organization_invitations" + * + * @param string $organization Organization (by ID) to request. + * @param string $invitation Invitation (by ID) to request. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + */ + public function getInvitation( + string $organization, + string $invitation + ) + { + return $this->apiClient->method('get') + ->addPath('organizations', $organization, 'invitations', $invitation) + ->call(); + } + + /** + * Create an invitation for an organization + * Required scope: "create:organization_invitations" + * + * @param string $organization Organization (by ID) to create the invitation for. + * @param string $clientId Client (by ID) to create the invitation for. This Client must be associated with the Organization. + * @param array $inviter An array containing information about the inviter. Requires a 'name' key, indicating who created the invitation. + * @param array $invitee An array containing information about the invitee. Requires an 'email' key, indicating where to send the invite. + * @param array $params Optional. Options to include with the request, such as pagination or filtering parameters. + * + * @return mixed + * + * @throws RequestException When API request fails. Reason for failure provided in exception message. + */ + public function createInvitation( + string $organization, + string $clientId, + array $inviter, + array $invitee, + array $params = [] + ) + { + $payload = (object)array_filter([ + 'client_id' => $clientId, + 'inviter' => (object)$inviter, + 'invitee' => (object)$invitee, + ] + $params); + + return $this->apiClient->method('post') + ->addPath('organizations', $organization, 'invitations') + ->withBody(json_encode($payload)) + ->call(); + } + + /** + * Delete an invitation (by ID) for an organization + * Required scope: "delete:organization_invitations" * - * @param array $branding An array containing branding customizations for the organization. + * @param string $organization Organization (by ID) to request. + * @param string $invitation Invitation (by ID) to request. * - * @return void + * @return mixed * - * @throws EmptyOrInvalidParameterException When an improperly formatted branding customization is provided. + * @throws RequestException When API request fails. Reason for failure provided in exception message. */ - protected function validateBranding( - ?array $branding = null + public function deleteInvitation( + string $organization, + string $invitation ) { - // #TODO - return true; + return $this->apiClient->method('delete') + ->addPath('organizations', $organization, 'invitations', $invitation) + ->call(); } } From 81ba10329536383de9097c70075cdb0fe2df6151 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 30 Mar 2021 20:15:56 -0400 Subject: [PATCH 22/38] tests: Improvements to integration tests --- .../OrganizationsIntegrationTest.php | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/tests/integration/API/Management/OrganizationsIntegrationTest.php b/tests/integration/API/Management/OrganizationsIntegrationTest.php index d9101b4b..d40e8aa1 100644 --- a/tests/integration/API/Management/OrganizationsIntegrationTest.php +++ b/tests/integration/API/Management/OrganizationsIntegrationTest.php @@ -70,8 +70,6 @@ public function setUp(): void { $env = self::getEnv(); - $this->management = new Management($env['API_TOKEN'], $env['DOMAIN'], ['timeout' => 30]); - $this->resources = [ 'name' => uniqid('php-sdk-test-organization-'), 'display_name' => 'PHP SDK Integration Test (DELETE)', @@ -84,9 +82,14 @@ public function setUp(): void ] ]; + // Initialize Management Client and Organization class + $this->management = new Management($env['API_TOKEN'], $env['DOMAIN'], ['timeout' => 30]); $this->api = $this->management->organizations(); + + // Create a new organization for our tests $this->organization = $this->api->create($this->resources['name'], $this->resources['display_name']); + // Create a new connection for our tests usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); $this->connection = $this->management->connections()->create([ 'name' => uniqid('php-sdk-test-connection-'), @@ -94,9 +97,11 @@ public function setUp(): void 'enabled_clients' => [ $env['APP_CLIENT_ID'] ] ]); + // Enable new connection with the organization for our tests usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); $this->api->addEnabledConnection($this->organization['id'], $this->connection['id']); + // Create a new user for our tests usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); $this->user = $this->management->users()->create([ 'email' => uniqid('php-sdk-test-user-') . '@test.com', @@ -104,32 +109,35 @@ public function setUp(): void 'email_verified' => true, 'password' => password_hash(uniqid('php-sdk-test-password-'), PASSWORD_DEFAULT) ]); + // var_dump($this->user); + // Add the new user to the organization as a member for our tests usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); $this->api->addMember($this->organization['id'], $this->user['user_id']); + // Add a role to the new member of the organization for our tests usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); $this->role = $this->management->roles()->create(uniqid('php-sdk-test-role-')); } public function tearDown(): void { - // Cleanup our test role. + // Cleanup our test role, if it's creation was successful if ($this->role) { $this->management->roles()->delete($this->role['id']); } - // Cleanup our test user. + // Cleanup our test user, if it's creation was successful if ($this->user) { $this->management->users()->delete($this->user['user_id']); } - // Cleanup our test connection. + // Cleanup our test connection, if it's creation was successful if ($this->connection) { $this->management->connections()->delete($this->connection['id']); } - // Cleanup our test organization. + // Cleanup our test organization, if it's creation was successful if ($this->organization) { $this->api->delete($this->organization['id']); } From e33f4069d238cca855ddc781ea3a9dafb2fa4476 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 30 Mar 2021 20:20:53 -0400 Subject: [PATCH 23/38] chores: Coding standards pass --- src/API/Management/Organizations.php | 32 ++++++++++++++-------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/API/Management/Organizations.php b/src/API/Management/Organizations.php index 9bba1181..736c282e 100644 --- a/src/API/Management/Organizations.php +++ b/src/API/Management/Organizations.php @@ -38,11 +38,11 @@ public function create( array $params = [] ) { - $payload = (object)array_filter([ + $payload = (object) array_filter([ 'name' => $name, 'display_name' => $displayName, - 'branding' => $branding ? (object)$branding : null, - 'metadata' => $metadata ? (object)$metadata : null, + 'branding' => $branding ? (object) $branding : null, + 'metadata' => $metadata ? (object) $metadata : null, ] + $params); return $this->apiClient->method('post') @@ -75,10 +75,10 @@ public function update( array $params = [] ) { - $payload = (object)array_filter([ + $payload = (object) array_filter([ 'display_name' => $displayName, - 'branding' => $branding ? (object)$branding : null, - 'metadata' => $metadata ? (object)$metadata : null, + 'branding' => $branding ? (object) $branding : null, + 'metadata' => $metadata ? (object) $metadata : null, ] + $params); return $this->apiClient->method('patch') @@ -237,7 +237,7 @@ public function addEnabledConnection( array $params = [] ) { - $payload = (object)array_filter([ + $payload = (object) array_filter([ 'connection_id' => $connection ] + $params); @@ -269,7 +269,7 @@ public function updateEnabledConnection( { return $this->apiClient->method('patch') ->addPath('organizations', $organization, 'enabled_connections', $connection) - ->withBody(json_encode((object)$params)) + ->withBody(json_encode( (object) $params)) ->call(); } @@ -597,11 +597,11 @@ public function getInvitation( * Create an invitation for an organization * Required scope: "create:organization_invitations" * - * @param string $organization Organization (by ID) to create the invitation for. - * @param string $clientId Client (by ID) to create the invitation for. This Client must be associated with the Organization. - * @param array $inviter An array containing information about the inviter. Requires a 'name' key, indicating who created the invitation. - * @param array $invitee An array containing information about the invitee. Requires an 'email' key, indicating where to send the invite. - * @param array $params Optional. Options to include with the request, such as pagination or filtering parameters. + * @param string $organization Organization (by ID) to create the invitation for. + * @param string $clientId Client (by ID) to create the invitation for. This Client must be associated with the Organization. + * @param array $inviter An array containing information about the inviter. Requires a 'name' key, indicating who created the invitation. + * @param array $invitee An array containing information about the invitee. Requires an 'email' key, indicating where to send the invite. + * @param array $params Optional. Options to include with the request, such as pagination or filtering parameters. * * @return mixed * @@ -615,10 +615,10 @@ public function createInvitation( array $params = [] ) { - $payload = (object)array_filter([ + $payload = (object) array_filter([ 'client_id' => $clientId, - 'inviter' => (object)$inviter, - 'invitee' => (object)$invitee, + 'inviter' => (object) $inviter, + 'invitee' => (object) $invitee, ] + $params); return $this->apiClient->method('post') From 2454368bdc06da042db30946a5ca01a88de2a1f5 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 30 Mar 2021 20:25:01 -0400 Subject: [PATCH 24/38] =?UTF-8?q?docs:=20Remove=20API=20documentation=20li?= =?UTF-8?q?nks=20until=20they=E2=80=99re=20available?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/API/Management/Organizations.php | 40 ---------------------------- src/API/Management/Users.php | 2 -- 2 files changed, 42 deletions(-) diff --git a/src/API/Management/Organizations.php b/src/API/Management/Organizations.php index 736c282e..27473bb1 100644 --- a/src/API/Management/Organizations.php +++ b/src/API/Management/Organizations.php @@ -27,8 +27,6 @@ class Organizations extends GenericResource * @return mixed * * @throws RequestException When API request fails. Reason for failure provided in exception message. - * - * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function create( string $name, @@ -64,8 +62,6 @@ public function create( * @return mixed * * @throws RequestException When API request fails. Reason for failure provided in exception message. - * - * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function update( string $organization, @@ -96,8 +92,6 @@ public function update( * @return mixed * * @throws RequestException When API request fails. Reason for failure provided in exception message. - * - * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function delete( string $organization @@ -137,8 +131,6 @@ public function getAll( * @return mixed * * @throws RequestException When API request fails. Reason for failure provided in exception message. - * - * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function get( string $organization @@ -158,8 +150,6 @@ public function get( * @return mixed * * @throws RequestException When API request fails. Reason for failure provided in exception message. - * - * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function getByName( string $organizationName @@ -180,8 +170,6 @@ public function getByName( * @return mixed * * @throws RequestException When API request fails. Reason for failure provided in exception message. - * - * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function getEnabledConnections( string $organization, @@ -204,8 +192,6 @@ public function getEnabledConnections( * @return mixed * * @throws RequestException When API request fails. Reason for failure provided in exception message. - * - * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function getEnabledConnection( string $organization, @@ -228,8 +214,6 @@ public function getEnabledConnection( * @return mixed * * @throws RequestException When API request fails. Reason for failure provided in exception message. - * - * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function addEnabledConnection( string $organization, @@ -258,8 +242,6 @@ public function addEnabledConnection( * @return mixed * * @throws RequestException When API request fails. Reason for failure provided in exception message. - * - * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function updateEnabledConnection( string $organization, @@ -283,8 +265,6 @@ public function updateEnabledConnection( * @return mixed * * @throws RequestException When API request fails. Reason for failure provided in exception message. - * - * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function removeEnabledConnection( string $organization, @@ -306,8 +286,6 @@ public function removeEnabledConnection( * @return mixed * * @throws RequestException When API request fails. Reason for failure provided in exception message. - * - * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function getMembers( string $organization, @@ -330,8 +308,6 @@ public function getMembers( * @return mixed * * @throws RequestException When API request fails. Reason for failure provided in exception message. - * - * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function addMember( string $organization, @@ -351,8 +327,6 @@ public function addMember( * @return mixed * * @throws RequestException When API request fails. Reason for failure provided in exception message. - * - * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function addMembers( string $organization, @@ -379,8 +353,6 @@ public function addMembers( * @return mixed * * @throws RequestException When API request fails. Reason for failure provided in exception message. - * - * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function removeMember( string $organization, @@ -400,8 +372,6 @@ public function removeMember( * @return mixed * * @throws RequestException When API request fails. Reason for failure provided in exception message. - * - * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function removeMembers( string $organization, @@ -429,8 +399,6 @@ public function removeMembers( * @return mixed * * @throws RequestException When API request fails. Reason for failure provided in exception message. - * - * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function getMemberRoles( string $organization, @@ -455,8 +423,6 @@ public function getMemberRoles( * @return mixed * * @throws RequestException When API request fails. Reason for failure provided in exception message. - * - * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function addMemberRole( string $organization, @@ -478,8 +444,6 @@ public function addMemberRole( * @return mixed * * @throws RequestException When API request fails. Reason for failure provided in exception message. - * - * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function addMemberRoles( string $organization, @@ -508,8 +472,6 @@ public function addMemberRoles( * @return mixed * * @throws RequestException When API request fails. Reason for failure provided in exception message. - * - * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function removeMemberRole( string $organization, @@ -531,8 +493,6 @@ public function removeMemberRole( * @return mixed * * @throws RequestException When API request fails. Reason for failure provided in exception message. - * - * @link https://auth0.com/docs/api/management/v2#!/Organizations/ #TODO */ public function removeMemberRoles( string $organization, diff --git a/src/API/Management/Users.php b/src/API/Management/Users.php index b7c1b04f..5099f2c9 100644 --- a/src/API/Management/Users.php +++ b/src/API/Management/Users.php @@ -471,8 +471,6 @@ public function getLogs($user_id, array $params = []) * @throws \Exception Thrown by the HTTP client when there is a problem with the API call. * * @return mixed - * - * @link https://auth0.com/docs/api/management/v2#!/Users/get_organizations_by_user #TODO */ public function getOrganizations($user_id, array $params = []) { From b8fb6fef011a7aa7e1064dfb0547b8948898bc6c Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 30 Mar 2021 20:25:16 -0400 Subject: [PATCH 25/38] fix: Use new normalizeRequest() on Users::getOrganizations --- src/API/Management/Users.php | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/API/Management/Users.php b/src/API/Management/Users.php index 5099f2c9..bad303df 100644 --- a/src/API/Management/Users.php +++ b/src/API/Management/Users.php @@ -476,12 +476,9 @@ public function getOrganizations($user_id, array $params = []) { $this->checkEmptyOrInvalidString($user_id, 'user_id'); - $params = $this->normalizePagination( $params ); - $params = $this->normalizeIncludeTotals( $params ); - return $this->apiClient->method('get') ->addPath('users', $user_id, 'organizations') - ->withDictParams($params) + ->withDictParams($this->normalizeRequest($params)) ->call(); } From 4c4d6c0c3e7b6d4fc4e3ac60e298c3c55d3a881b Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Tue, 30 Mar 2021 20:29:17 -0400 Subject: [PATCH 26/38] tests: Add Users::getOrganizations method to mocked unit tests --- tests/unit/API/Management/UsersMockedTest.php | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/tests/unit/API/Management/UsersMockedTest.php b/tests/unit/API/Management/UsersMockedTest.php index 4c5cea5b..a3bfce7c 100644 --- a/tests/unit/API/Management/UsersMockedTest.php +++ b/tests/unit/API/Management/UsersMockedTest.php @@ -1034,6 +1034,60 @@ public function testThatGetLogsRequestIsFormattedProperly() $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); } + /** + * Test that a get organizations call throws an exception if the user ID is missing. + * + * @return void + * + * @throws \Exception Should not be thrown in this test. + */ + public function testThatGetOrganizationsThrowsExceptionIfUserIdIsMissing() + { + $api = new MockManagementApi(); + + try { + $api->call()->users()->getOrganizations( '' ); + $caught_message = ''; + } catch (EmptyOrInvalidParameterException $e) { + $caught_message = $e->getMessage(); + } + + $this->assertStringContainsString( 'Empty or invalid user_id', $caught_message ); + } + + /** + * Test that a get organizations call is formatted properly. + * + * @return void + * + * @throws \Exception Should not be thrown in this test. + */ + public function testThatGetOrganizationsRequestIsFormattedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->users()->getOrganizations( + '__test_user_id__', + [ 'per_page' => 3, 'page' => 2, 'include_totals' => 0, 'fields' => 'date,type,ip' ] + ); + + $this->assertEquals( 'GET', $api->getHistoryMethod() ); + $this->assertStringStartsWith( + 'https://api.test.local/api/v2/users/__test_user_id__/organizations?', + $api->getHistoryUrl() + ); + + $query = $api->getHistoryQuery(); + $this->assertStringContainsString( 'per_page=3', $query ); + $this->assertStringContainsString( 'page=2', $query ); + $this->assertStringContainsString( 'include_totals=false', $query ); + $this->assertStringContainsString( 'fields=date,type,ip', $query ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + } + /** * Test that a generate recovery code call throws an exception if the user ID is missing. * From 3879e2420f6d3cad0743dd711b1f15a2f13064db Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Wed, 31 Mar 2021 02:34:18 -0400 Subject: [PATCH 27/38] feat: Add validation helper checkEmptyOrInvalidArray() --- src/API/Management/GenericResource.php | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/API/Management/GenericResource.php b/src/API/Management/GenericResource.php index 1a915455..7f63ee4b 100644 --- a/src/API/Management/GenericResource.php +++ b/src/API/Management/GenericResource.php @@ -183,4 +183,21 @@ protected function checkEmptyOrInvalidString($var, $var_name) throw new EmptyOrInvalidParameterException($var_name); } } + + /** + * Check that a variable is an array and is not empty. + * + * @param mixed $var The variable to check. + * @param string $var_name The variable name. + * + * @return void + * + * @throws EmptyOrInvalidParameterException If $var is empty or is not a string. + */ + protected function checkEmptyOrInvalidArray($var, $var_name) + { + if (! is_array($var) || ! count($var)) { + throw new EmptyOrInvalidParameterException($var_name); + } + } } From dfc02d5e4a1616bb9653064b134185674d6670bd Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Wed, 31 Mar 2021 02:34:36 -0400 Subject: [PATCH 28/38] feat: Add required parameter validators --- src/API/Management/Organizations.php | 80 ++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/src/API/Management/Organizations.php b/src/API/Management/Organizations.php index 27473bb1..d07b446f 100644 --- a/src/API/Management/Organizations.php +++ b/src/API/Management/Organizations.php @@ -36,6 +36,9 @@ public function create( array $params = [] ) { + $this->checkEmptyOrInvalidString($name, 'name'); + $this->checkEmptyOrInvalidString($displayName, 'displayName'); + $payload = (object) array_filter([ 'name' => $name, 'display_name' => $displayName, @@ -71,6 +74,9 @@ public function update( array $params = [] ) { + $this->checkEmptyOrInvalidString($organization, 'organization'); + $this->checkEmptyOrInvalidString($displayName, 'displayName'); + $payload = (object) array_filter([ 'display_name' => $displayName, 'branding' => $branding ? (object) $branding : null, @@ -97,6 +103,8 @@ public function delete( string $organization ) { + $this->checkEmptyOrInvalidString($organization, 'organization'); + return $this->apiClient->method('delete') ->addPath('organizations', $organization) ->call(); @@ -136,6 +144,8 @@ public function get( string $organization ) { + $this->checkEmptyOrInvalidString($organization, 'organization'); + return $this->apiClient->method('get') ->addPath('organizations', $organization) ->call(); @@ -155,6 +165,8 @@ public function getByName( string $organizationName ) { + $this->checkEmptyOrInvalidString($organizationName, 'organizationName'); + return $this->apiClient->method('get') ->addPath('organizations', 'name', $organizationName) ->call(); @@ -176,6 +188,8 @@ public function getEnabledConnections( array $params = [] ) { + $this->checkEmptyOrInvalidString($organization, 'organization'); + return $this->apiClient->method('get') ->addPath('organizations', $organization, 'enabled_connections') ->withDictParams($this->normalizeRequest($params)) @@ -198,6 +212,9 @@ public function getEnabledConnection( string $connection ) { + $this->checkEmptyOrInvalidString($organization, 'organization'); + $this->checkEmptyOrInvalidString($connection, 'connection'); + return $this->apiClient->method('get') ->addPath('organizations', $organization, 'enabled_connections', $connection) ->call(); @@ -221,6 +238,9 @@ public function addEnabledConnection( array $params = [] ) { + $this->checkEmptyOrInvalidString($organization, 'organization'); + $this->checkEmptyOrInvalidString($connection, 'connection'); + $payload = (object) array_filter([ 'connection_id' => $connection ] + $params); @@ -249,6 +269,9 @@ public function updateEnabledConnection( array $params = [] ) { + $this->checkEmptyOrInvalidString($organization, 'organization'); + $this->checkEmptyOrInvalidString($connection, 'connection'); + return $this->apiClient->method('patch') ->addPath('organizations', $organization, 'enabled_connections', $connection) ->withBody(json_encode( (object) $params)) @@ -271,6 +294,9 @@ public function removeEnabledConnection( string $connection ) { + $this->checkEmptyOrInvalidString($organization, 'organization'); + $this->checkEmptyOrInvalidString($connection, 'connection'); + return $this->apiClient->method('delete') ->addPath('organizations', $organization, 'enabled_connections', $connection) ->call(); @@ -292,6 +318,8 @@ public function getMembers( array $params = [] ) { + $this->checkEmptyOrInvalidString($organization, 'organization'); + return $this->apiClient->method('get') ->addPath('organizations', $organization, 'members') ->withDictParams($this->normalizeRequest($params)) @@ -314,6 +342,9 @@ public function addMember( string $user ) { + $this->checkEmptyOrInvalidString($organization, 'organization'); + $this->checkEmptyOrInvalidString($user, 'user'); + return $this->addMembers($organization, [ $user ]); } @@ -333,6 +364,9 @@ public function addMembers( array $users ) { + $this->checkEmptyOrInvalidString($organization, 'organization'); + $this->checkEmptyOrInvalidArray($users, 'users'); + $payload = [ 'members' => $users ]; @@ -359,6 +393,9 @@ public function removeMember( string $user ) { + $this->checkEmptyOrInvalidString($organization, 'organization'); + $this->checkEmptyOrInvalidString($user, 'user'); + return $this->removeMembers($organization, [ $user ]); } @@ -378,6 +415,9 @@ public function removeMembers( array $users ) { + $this->checkEmptyOrInvalidString($organization, 'organization'); + $this->checkEmptyOrInvalidArray($users, 'users'); + $payload = [ 'members' => $users ]; @@ -406,6 +446,9 @@ public function getMemberRoles( array $params = [] ) { + $this->checkEmptyOrInvalidString($organization, 'organization'); + $this->checkEmptyOrInvalidString($user, 'user'); + return $this->apiClient->method('get') ->addPath('organizations', $organization, 'members', $user, 'roles') ->withDictParams($this->normalizeRequest($params)) @@ -430,6 +473,10 @@ public function addMemberRole( string $role ) { + $this->checkEmptyOrInvalidString($organization, 'organization'); + $this->checkEmptyOrInvalidString($user, 'user'); + $this->checkEmptyOrInvalidString($role, 'role'); + return $this->addMemberRoles($organization, $user, [ $role ]); } @@ -451,6 +498,10 @@ public function addMemberRoles( array $roles ) { + $this->checkEmptyOrInvalidString($organization, 'organization'); + $this->checkEmptyOrInvalidString($user, 'user'); + $this->checkEmptyOrInvalidArray($roles, 'roles'); + $payload = [ 'roles' => $roles ]; @@ -479,6 +530,10 @@ public function removeMemberRole( string $role ) { + $this->checkEmptyOrInvalidString($organization, 'organization'); + $this->checkEmptyOrInvalidString($user, 'user'); + $this->checkEmptyOrInvalidString($role, 'role'); + return $this->removeMemberRoles($organization, $user, [ $role ]); } @@ -500,6 +555,10 @@ public function removeMemberRoles( array $roles ) { + $this->checkEmptyOrInvalidString($organization, 'organization'); + $this->checkEmptyOrInvalidString($user, 'user'); + $this->checkEmptyOrInvalidArray($roles, 'roles'); + $payload = [ 'roles' => $roles ]; @@ -526,6 +585,8 @@ public function getInvitations( array $params = [] ) { + $this->checkEmptyOrInvalidString($organization, 'organization'); + return $this->apiClient->method('get') ->addPath('organizations', $organization, 'invitations') ->withDictParams($this->normalizeRequest($params)) @@ -548,6 +609,9 @@ public function getInvitation( string $invitation ) { + $this->checkEmptyOrInvalidString($organization, 'organization'); + $this->checkEmptyOrInvalidString($invitation, 'invitation'); + return $this->apiClient->method('get') ->addPath('organizations', $organization, 'invitations', $invitation) ->call(); @@ -575,6 +639,19 @@ public function createInvitation( array $params = [] ) { + $this->checkEmptyOrInvalidString($organization, 'organization'); + $this->checkEmptyOrInvalidString($clientId, 'clientId'); + $this->checkEmptyOrInvalidArray($inviter, 'inviter'); + $this->checkEmptyOrInvalidArray($invitee, 'invitee'); + + if (! isset($inviter['name'])) { + throw new EmptyOrInvalidParameterException('inviter'); + } + + if (! isset($invitee['email'])) { + throw new EmptyOrInvalidParameterException('invitee'); + } + $payload = (object) array_filter([ 'client_id' => $clientId, 'inviter' => (object) $inviter, @@ -603,6 +680,9 @@ public function deleteInvitation( string $invitation ) { + $this->checkEmptyOrInvalidString($organization, 'organization'); + $this->checkEmptyOrInvalidString($invitation, 'invitation'); + return $this->apiClient->method('delete') ->addPath('organizations', $organization, 'invitations', $invitation) ->call(); From f95859a86420d05a429e2b6b5d116b17ad2f3075 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Wed, 31 Mar 2021 02:34:56 -0400 Subject: [PATCH 29/38] tests: Mark as integration test group --- .../integration/API/Management/OrganizationsIntegrationTest.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/integration/API/Management/OrganizationsIntegrationTest.php b/tests/integration/API/Management/OrganizationsIntegrationTest.php index d40e8aa1..72cbc885 100644 --- a/tests/integration/API/Management/OrganizationsIntegrationTest.php +++ b/tests/integration/API/Management/OrganizationsIntegrationTest.php @@ -14,6 +14,7 @@ * Class OrganizationsIntegrationTest. * Tests the Auth0\SDK\API\Management\Organizations class. * +* @group integration * @group management * @group organizations * From 7c34f5dd7faa718d507391c0086fe45f754d902b Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Wed, 31 Mar 2021 02:35:16 -0400 Subject: [PATCH 30/38] tests: Add Organizations unit tests --- .../unit/API/Management/OrganizationsTest.php | 1000 +++++++++++++++++ 1 file changed, 1000 insertions(+) create mode 100644 tests/unit/API/Management/OrganizationsTest.php diff --git a/tests/unit/API/Management/OrganizationsTest.php b/tests/unit/API/Management/OrganizationsTest.php new file mode 100644 index 00000000..ac8d53ae --- /dev/null +++ b/tests/unit/API/Management/OrganizationsTest.php @@ -0,0 +1,1000 @@ + 'json' ]; + + /** + * Runs before test suite starts. + */ + public static function setUpBeforeClass(): void + { + $infoHeadersData = new InformationHeaders; + $infoHeadersData->setCorePackage(); + self::$expectedTelemetry = $infoHeadersData->build(); + } + + public function testThatCreateOrganizationRequestIsFormedCorrectly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->create( + 'test-organization', + 'Test Organization', + [ + 'logo_url' => 'https://test.com/test.png' + ], + [ + 'meta' => 'data' + ] + ); + + $this->assertEquals( 'POST', $api->getHistoryMethod() ); + $this->assertEquals( 'https://api.test.local/api/v2/organizations', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( 'application/json', $headers['Content-Type'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + + $body = $api->getHistoryBody(); + + $this->assertArrayHasKey( 'name', $body ); + $this->assertEquals( 'test-organization', $body['name'] ); + + $this->assertArrayHasKey( 'display_name', $body ); + $this->assertEquals( 'Test Organization', $body['display_name'] ); + + $this->assertArrayHasKey( 'branding', $body ); + $this->assertArrayHasKey( 'logo_url', $body['branding']); + $this->assertEquals( 'https://test.com/test.png', $body['branding']['logo_url'] ); + + $this->assertArrayHasKey( 'metadata', $body ); + $this->assertArrayHasKey( 'meta', $body['metadata']); + $this->assertEquals( 'data', $body['metadata']['meta'] ); + } + + public function testThatCreateOrganizationRequestWithEmptyNameThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid name.'); + + $api->call()->organizations()->create( '', '' ); + } + + public function testThatCreateOrganizationRequestWithEmptyDisplayNameThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid displayName.'); + + $api->call()->organizations()->create( 'test-organization', '' ); + } + + public function testThatUpdateOrganizationRequestIsFormedCorrectly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->update( + 'test-organization', + 'Test Organization', + [ + 'logo_url' => 'https://test.com/test.png' + ], + [ + 'meta' => 'data' + ] + ); + + $this->assertEquals( 'PATCH', $api->getHistoryMethod() ); + $this->assertEquals( 'https://api.test.local/api/v2/organizations/test-organization', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( 'application/json', $headers['Content-Type'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + + $body = $api->getHistoryBody(); + + $this->assertArrayHasKey( 'display_name', $body ); + $this->assertEquals( 'Test Organization', $body['display_name'] ); + + $this->assertArrayHasKey( 'branding', $body ); + $this->assertArrayHasKey( 'logo_url', $body['branding']); + $this->assertEquals( 'https://test.com/test.png', $body['branding']['logo_url'] ); + + $this->assertArrayHasKey( 'metadata', $body ); + $this->assertArrayHasKey( 'meta', $body['metadata']); + $this->assertEquals( 'data', $body['metadata']['meta'] ); + } + + public function testThatUpdateOrganizationRequestWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organization.'); + + $api->call()->organizations()->update('', ''); + } + + public function testThatUpdateOrganizationRequestWithEmptyDisplayNameThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid displayName.'); + + $api->call()->organizations()->update( 'test-organization', '' ); + } + + public function testThatDeleteOrganizationRequestIsFormedCorrectly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->delete('test-organization'); + + $this->assertEquals( 'DELETE', $api->getHistoryMethod() ); + $this->assertEquals( 'https://api.test.local/api/v2/organizations/test-organization', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( 'application/json', $headers['Content-Type'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + } + + public function testThatDeleteOrganizationRequestWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organization.'); + + $api->call()->organizations()->update('', ''); + } + + public function testThatGetAllRequestIsFormedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->getAll(); + + $this->assertEquals( 'GET', $api->getHistoryMethod() ); + $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + } + + public function testThatGetRequestIsFormedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->get('123'); + + $this->assertEquals( 'GET', $api->getHistoryMethod() ); + $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/123', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + } + + public function testThatGetWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organization.'); + + $api->call()->organizations()->get( '' ); + } + + public function testThatGetByNameRequestIsFormedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->getByName('test-organization'); + + $this->assertEquals( 'GET', $api->getHistoryMethod() ); + $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/name/test-organization', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + } + + public function testThatGetByNameWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organizationName.'); + + $api->call()->organizations()->getByName( '' ); + } + + public function testThatGetEnabledConnectionsRequestIsFormedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->getEnabledConnections('test-organization'); + + $this->assertEquals( 'GET', $api->getHistoryMethod() ); + $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/enabled_connections', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + } + + public function testThatGetEnabledConnectionsWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organization.'); + + $api->call()->organizations()->getEnabledConnections( '' ); + } + + public function testThatGetEnabledConnectionRequestIsFormedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->getEnabledConnection('test-organization', 'test-connection'); + + $this->assertEquals( 'GET', $api->getHistoryMethod() ); + $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/enabled_connections/test-connection', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + } + + public function testThatGetEnabledConnectionWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organization.'); + + $api->call()->organizations()->getEnabledConnection( '', '' ); + } + + public function testThatGetEnabledConnectionWithEmptyConnectionThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid connection.'); + + $api->call()->organizations()->getEnabledConnection( 'test-organization', '' ); + } + + public function testThatAddEnabledConnectionRequestIsFormedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->addEnabledConnection('test-organization', 'test-connection'); + + $this->assertEquals( 'POST', $api->getHistoryMethod() ); + $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/enabled_connections', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + + $body = $api->getHistoryBody(); + $this->assertArrayHasKey( 'connection_id', $body ); + $this->assertEquals( 'test-connection', $body['connection_id'] ); + } + + public function testThatAddEnabledConnectionWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organization.'); + + $api->call()->organizations()->addEnabledConnection( '', '' ); + } + + public function testThatAddEnabledConnectionWithEmptyConnectionThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid connection.'); + + $api->call()->organizations()->addEnabledConnection( 'test-organization', '' ); + } + + public function testThatUpdateEnabledConnectionRequestIsFormedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->updateEnabledConnection('test-organization', 'test-connection', ['assign_membership_on_login' => true]); + + $this->assertEquals( 'PATCH', $api->getHistoryMethod() ); + $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/enabled_connections/test-connection', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + + $body = $api->getHistoryBody(); + $this->assertArrayHasKey( 'assign_membership_on_login', $body ); + $this->assertTrue( $body['assign_membership_on_login'] ); + } + + public function testThatUpdateEnabledConnectionWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organization.'); + + $api->call()->organizations()->updateEnabledConnection( '', '' ); + } + + public function testThatUpdateEnabledConnectionWithEmptyConnectionThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid connection.'); + + $api->call()->organizations()->updateEnabledConnection( 'test-organization', '' ); + } + + public function testThatRemoveEnabledConnectionRequestIsFormedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->removeEnabledConnection('test-organization', 'test-connection'); + + $this->assertEquals( 'DELETE', $api->getHistoryMethod() ); + $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/enabled_connections/test-connection', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + } + + public function testThatRemoveEnabledConnectionWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organization.'); + + $api->call()->organizations()->removeEnabledConnection( '', '' ); + } + + public function testThatRemoveEnabledConnectionWithEmptyConnectionThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid connection.'); + + $api->call()->organizations()->removeEnabledConnection( 'test-organization', '' ); + } + + public function testThatGetMembersRequestIsFormedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->getMembers('test-organization'); + + $this->assertEquals( 'GET', $api->getHistoryMethod() ); + $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/members', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + } + + public function testThatGetMembersWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organization.'); + + $api->call()->organizations()->getMembers( '' ); + } + + public function testThatAddMemberRequestIsFormedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->addMember('test-organization', 'test-user'); + + $this->assertEquals( 'POST', $api->getHistoryMethod() ); + $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/members', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + + $body = $api->getHistoryBody(); + $this->assertArrayHasKey( 'members', $body ); + $this->assertContains('test-user', $body['members']); + } + + public function testThatAddMemberWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organization.'); + + $api->call()->organizations()->addMember( '', '' ); + } + + public function testThatAddMemberWithEmptyUserThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid user.'); + + $api->call()->organizations()->addMember( 'test-organization', '' ); + } + + public function testThatAddMembersRequestIsFormedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->addMembers('test-organization', [ 'test-user' ]); + + $this->assertEquals( 'POST', $api->getHistoryMethod() ); + $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/members', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + + $body = $api->getHistoryBody(); + $this->assertArrayHasKey( 'members', $body ); + $this->assertContains('test-user', $body['members']); + } + + public function testThatAddMembersWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organization.'); + + $api->call()->organizations()->addMembers( '', [] ); + } + + public function testThatAddMembersWithEmptyUsersThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid users.'); + + $api->call()->organizations()->addMembers( 'test-organization', [] ); + } + + public function testThatRemoveMemberRequestIsFormedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->removeMember('test-organization', 'test-user'); + + $this->assertEquals( 'DELETE', $api->getHistoryMethod() ); + $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/members', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + + $body = $api->getHistoryBody(); + $this->assertArrayHasKey( 'members', $body ); + $this->assertContains('test-user', $body['members']); + } + + public function testThatRemoveMemberWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organization.'); + + $api->call()->organizations()->removeMember( '', '' ); + } + + public function testThatRemoveMemberWithEmptyUserThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid user.'); + + $api->call()->organizations()->removeMember( 'test-organization', '' ); + } + + public function testThatRemoveMembersRequestIsFormedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->removeMembers('test-organization', [ 'test-user']); + + $this->assertEquals( 'DELETE', $api->getHistoryMethod() ); + $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/members', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + + $body = $api->getHistoryBody(); + $this->assertArrayHasKey( 'members', $body ); + $this->assertContains('test-user', $body['members']); + } + + public function testThatRemoveMembersWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organization.'); + + $api->call()->organizations()->removeMembers( '', [] ); + } + + public function testThatRemoveMembersWithEmptyUsersThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid users.'); + + $api->call()->organizations()->removeMembers( 'test-organization', [] ); + } + + public function testThatGetMemberRolesRequestIsFormedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->getMemberRoles('test-organization', 'test-user'); + + $this->assertEquals( 'GET', $api->getHistoryMethod() ); + $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/members/test-user/roles', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + } + + public function testThatGetMemberRolesWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organization.'); + + $api->call()->organizations()->getMemberRoles( '', '' ); + } + + public function testThatGetMemberRolesWithEmptyUserThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid user.'); + + $api->call()->organizations()->getMemberRoles( 'test-organization', '' ); + } + + public function testThatAddMemberRoleRequestIsFormedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->addMemberRole('test-organization', 'test-user', 'test-role'); + + $this->assertEquals( 'POST', $api->getHistoryMethod() ); + $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/members/test-user/roles', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + + $body = $api->getHistoryBody(); + $this->assertArrayHasKey( 'roles', $body ); + $this->assertContains('test-role', $body['roles']); + } + + public function testThatAddMemberRoleWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organization.'); + + $api->call()->organizations()->addMemberRole( '', '', '' ); + } + + public function testThatAddMemberRoleWithEmptyUserThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid user.'); + + $api->call()->organizations()->addMemberRole( 'test-organization', '', '' ); + } + + public function testThatAddMemberRoleWithEmptyRoleThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid role.'); + + $api->call()->organizations()->addMemberRole( 'test-organization', 'test-rule', '' ); + } + + public function testThatAddMemberRolesRequestIsFormedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->addMemberRoles('test-organization', 'test-user', [ 'test-role' ]); + + $this->assertEquals( 'POST', $api->getHistoryMethod() ); + $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/members/test-user/roles', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + + $body = $api->getHistoryBody(); + $this->assertArrayHasKey( 'roles', $body ); + $this->assertContains('test-role', $body['roles']); + } + + public function testThatAddMemberRolesWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organization.'); + + $api->call()->organizations()->addMemberRoles( '', '', [] ); + } + + public function testThatAddMemberRolesWithEmptyUserThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid user.'); + + $api->call()->organizations()->addMemberRoles( 'test-organization', '', [] ); + } + + public function testThatAddMemberRolesWithEmptyRolesThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid roles.'); + + $api->call()->organizations()->addMemberRoles( 'test-organization', 'test-rule', [] ); + } + + public function testThatRemoveMemberRoleRequestIsFormedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->removeMemberRole('test-organization', 'test-user', 'test-role'); + + $this->assertEquals( 'DELETE', $api->getHistoryMethod() ); + $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/members/test-user/roles', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + + $body = $api->getHistoryBody(); + $this->assertArrayHasKey( 'roles', $body ); + $this->assertContains('test-role', $body['roles']); + } + + public function testThatRemoveMemberRoleWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organization.'); + + $api->call()->organizations()->removeMemberRole( '', '', '' ); + } + + public function testThatRemoveMemberRoleWithEmptyUserThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid user.'); + + $api->call()->organizations()->removeMemberRole( 'test-organization', '', '' ); + } + + public function testThatRemoveMemberRoleWithEmptyRoleThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid role.'); + + $api->call()->organizations()->removeMemberRole( 'test-organization', 'test-rule', '' ); + } + + public function testThatRemoveMemberRolesRequestIsFormedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->removeMemberRoles('test-organization', 'test-user', [ 'test-role' ]); + + $this->assertEquals( 'DELETE', $api->getHistoryMethod() ); + $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/members/test-user/roles', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + + $body = $api->getHistoryBody(); + $this->assertArrayHasKey( 'roles', $body ); + $this->assertContains('test-role', $body['roles']); + } + + public function testThatRemoveMemberRolesWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organization.'); + + $api->call()->organizations()->removeMemberRoles( '', '', [] ); + } + + public function testThatRemoveMemberRolesWithEmptyUserThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid user.'); + + $api->call()->organizations()->removeMemberRoles( 'test-organization', '', [] ); + } + + public function testThatRemoveMemberRolesWithEmptyRolesThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid roles.'); + + $api->call()->organizations()->removeMemberRoles( 'test-organization', 'test-rule', [] ); + } + + public function testThatGetInvitationsRequestIsFormedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->getInvitations('test-organization'); + + $this->assertEquals( 'GET', $api->getHistoryMethod() ); + $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/invitations', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + } + + public function testThatGetInvitationsWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organization.'); + + $api->call()->organizations()->getInvitations( '' ); + } + + public function testThatGetInvitationRequestIsFormedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->getInvitation('test-organization', 'test-invitation'); + + $this->assertEquals( 'GET', $api->getHistoryMethod() ); + $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/invitations/test-invitation', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + } + + public function testThatGetInvitationWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organization.'); + + $api->call()->organizations()->getInvitation( '', '' ); + } + + public function testThatGetInvitationWithEmptyInvitationThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid invitation.'); + + $api->call()->organizations()->getInvitation( 'test-organization', '' ); + } + + public function testThatCreateInvitationRequestIsFormedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->createInvitation( + 'test-organization', + 'test-client', + [ 'name' => 'Test Sender' ], + [ 'email' => 'email@test.com' ] + ); + + $this->assertEquals( 'POST', $api->getHistoryMethod() ); + $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/invitations', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + + $body = $api->getHistoryBody(); + $this->assertArrayHasKey( 'client_id', $body ); + $this->assertEquals('test-client', $body['client_id']); + $this->assertArrayHasKey( 'inviter', $body ); + $this->assertArrayHasKey('name', $body['inviter']); + $this->assertEquals('Test Sender', $body['inviter']['name']); + $this->assertArrayHasKey( 'invitee', $body ); + $this->assertArrayHasKey('email', $body['invitee']); + $this->assertEquals('email@test.com', $body['invitee']['email']); + } + + public function testThatCreateInvitationWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organization.'); + + $api->call()->organizations()->createInvitation( '', '', [], [] ); + } + + public function testThatCreateInvitationWithEmptyClientThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid clientId.'); + + $api->call()->organizations()->createInvitation( 'test-organization', '', [], [] ); + } + + public function testThatCreateInvitationWithEmptyInviterThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid inviter.'); + + $api->call()->organizations()->createInvitation( 'test-organization', 'test-client', [], [] ); + } + + public function testThatCreateInvitationWithEmptyInviteeThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid invitee.'); + + $api->call()->organizations()->createInvitation( 'test-organization', 'test-client', [ 'test' => 'test' ], [] ); + } + + public function testThatCreateInvitationWithMalformedInviterThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid inviter.'); + + $api->call()->organizations()->createInvitation( 'test-organization', 'test-client', [ 'test' => 'test' ], [ 'test' => 'test' ] ); + } + + public function testThatCreateInvitationWithMalformedInviteeThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid invitee.'); + + $api->call()->organizations()->createInvitation( 'test-organization', 'test-client', [ 'name' => 'Test Sender' ], [ 'test' => 'test' ] ); + } + + public function testThatDeleteInvitationRequestIsFormedProperly() + { + $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); + + $api->call()->organizations()->deleteInvitation('test-organization', 'test-invitation'); + + $this->assertEquals( 'DELETE', $api->getHistoryMethod() ); + $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/invitations/test-invitation', $api->getHistoryUrl() ); + + $headers = $api->getHistoryHeaders(); + $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); + $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); + } + + public function testThatDeleteInvitationWithEmptyIdThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid organization.'); + + $api->call()->organizations()->deleteInvitation( '', '', ); + } + + public function testThatDeeteInvitationWithEmptyClientThrowsException() + { + $api = new MockManagementApi(); + + $this->expectException(EmptyOrInvalidParameterException::class); + $this->expectExceptionMessage('Empty or invalid invitation.'); + + $api->call()->organizations()->deleteInvitation( 'test-organization', '' ); + } +} From 04758804184b812325d2d5d7745e37a4ae5ff7f4 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Wed, 31 Mar 2021 03:28:36 -0400 Subject: [PATCH 31/38] feat: Remove alias functions --- src/API/Management/Organizations.php | 94 ---------- .../unit/API/Management/OrganizationsTest.php | 172 ------------------ 2 files changed, 266 deletions(-) diff --git a/src/API/Management/Organizations.php b/src/API/Management/Organizations.php index d07b446f..56d9a1e8 100644 --- a/src/API/Management/Organizations.php +++ b/src/API/Management/Organizations.php @@ -326,28 +326,6 @@ public function getMembers( ->call(); } - /** - * Add a user to an organization as a member. - * Required scope: "update:organization_members" - * - * @param string $organization Organization (by ID) to add new members to. - * @param string $user User (by ID) to add from the organization. - * - * @return mixed - * - * @throws RequestException When API request fails. Reason for failure provided in exception message. - */ - public function addMember( - string $organization, - string $user - ) - { - $this->checkEmptyOrInvalidString($organization, 'organization'); - $this->checkEmptyOrInvalidString($user, 'user'); - - return $this->addMembers($organization, [ $user ]); - } - /** * Add one or more users to an organization as members. * Required scope: "update:organization_members" @@ -377,28 +355,6 @@ public function addMembers( ->call(); } - /** - * Remove a member (user) from an organization. - * Required scope: "delete:organization_members" - * - * @param string $organization Organization (by ID) user belongs to. - * @param string $user User (by ID) to remove from the organization. - * - * @return mixed - * - * @throws RequestException When API request fails. Reason for failure provided in exception message. - */ - public function removeMember( - string $organization, - string $user - ) - { - $this->checkEmptyOrInvalidString($organization, 'organization'); - $this->checkEmptyOrInvalidString($user, 'user'); - - return $this->removeMembers($organization, [ $user ]); - } - /** * Remove one or more members (users) from an organization. * Required scope: "delete:organization_members" @@ -455,31 +411,6 @@ public function getMemberRoles( ->call(); } - /** - * Add a role to a member (user) in an organization. - * Required scope: "create:organization_member_roles" - * - * @param string $organization Organization (by ID) user belongs to. - * @param string $user User (by ID) to add role to. - * @param string $role Role (by ID) to add to the user. - * - * @return mixed - * - * @throws RequestException When API request fails. Reason for failure provided in exception message. - */ - public function addMemberRole( - string $organization, - string $user, - string $role - ) - { - $this->checkEmptyOrInvalidString($organization, 'organization'); - $this->checkEmptyOrInvalidString($user, 'user'); - $this->checkEmptyOrInvalidString($role, 'role'); - - return $this->addMemberRoles($organization, $user, [ $role ]); - } - /** * Add one or more roles to a member (user) in an organization. * Required scope: "create:organization_member_roles" @@ -512,31 +443,6 @@ public function addMemberRoles( ->call(); } - /** - * Remove a role from a member (user) in an organization. - * Required scope: "delete:organization_member_roles" - * - * @param string $organization Organization (by ID) user belongs to. - * @param string $user User (by ID) to remove roles from. - * @param string $role Role (by ID) to remove from the user. - * - * @return mixed - * - * @throws RequestException When API request fails. Reason for failure provided in exception message. - */ - public function removeMemberRole( - string $organization, - string $user, - string $role - ) - { - $this->checkEmptyOrInvalidString($organization, 'organization'); - $this->checkEmptyOrInvalidString($user, 'user'); - $this->checkEmptyOrInvalidString($role, 'role'); - - return $this->removeMemberRoles($organization, $user, [ $role ]); - } - /** * Remove one or more roles from a member (user) in an organization. * Required scope: "delete:organization_member_roles" diff --git a/tests/unit/API/Management/OrganizationsTest.php b/tests/unit/API/Management/OrganizationsTest.php index ac8d53ae..471115b9 100644 --- a/tests/unit/API/Management/OrganizationsTest.php +++ b/tests/unit/API/Management/OrganizationsTest.php @@ -439,44 +439,6 @@ public function testThatGetMembersWithEmptyIdThrowsException() $api->call()->organizations()->getMembers( '' ); } - public function testThatAddMemberRequestIsFormedProperly() - { - $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); - - $api->call()->organizations()->addMember('test-organization', 'test-user'); - - $this->assertEquals( 'POST', $api->getHistoryMethod() ); - $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/members', $api->getHistoryUrl() ); - - $headers = $api->getHistoryHeaders(); - $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); - $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); - - $body = $api->getHistoryBody(); - $this->assertArrayHasKey( 'members', $body ); - $this->assertContains('test-user', $body['members']); - } - - public function testThatAddMemberWithEmptyIdThrowsException() - { - $api = new MockManagementApi(); - - $this->expectException(EmptyOrInvalidParameterException::class); - $this->expectExceptionMessage('Empty or invalid organization.'); - - $api->call()->organizations()->addMember( '', '' ); - } - - public function testThatAddMemberWithEmptyUserThrowsException() - { - $api = new MockManagementApi(); - - $this->expectException(EmptyOrInvalidParameterException::class); - $this->expectExceptionMessage('Empty or invalid user.'); - - $api->call()->organizations()->addMember( 'test-organization', '' ); - } - public function testThatAddMembersRequestIsFormedProperly() { $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); @@ -515,44 +477,6 @@ public function testThatAddMembersWithEmptyUsersThrowsException() $api->call()->organizations()->addMembers( 'test-organization', [] ); } - public function testThatRemoveMemberRequestIsFormedProperly() - { - $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); - - $api->call()->organizations()->removeMember('test-organization', 'test-user'); - - $this->assertEquals( 'DELETE', $api->getHistoryMethod() ); - $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/members', $api->getHistoryUrl() ); - - $headers = $api->getHistoryHeaders(); - $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); - $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); - - $body = $api->getHistoryBody(); - $this->assertArrayHasKey( 'members', $body ); - $this->assertContains('test-user', $body['members']); - } - - public function testThatRemoveMemberWithEmptyIdThrowsException() - { - $api = new MockManagementApi(); - - $this->expectException(EmptyOrInvalidParameterException::class); - $this->expectExceptionMessage('Empty or invalid organization.'); - - $api->call()->organizations()->removeMember( '', '' ); - } - - public function testThatRemoveMemberWithEmptyUserThrowsException() - { - $api = new MockManagementApi(); - - $this->expectException(EmptyOrInvalidParameterException::class); - $this->expectExceptionMessage('Empty or invalid user.'); - - $api->call()->organizations()->removeMember( 'test-organization', '' ); - } - public function testThatRemoveMembersRequestIsFormedProperly() { $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); @@ -625,54 +549,6 @@ public function testThatGetMemberRolesWithEmptyUserThrowsException() $api->call()->organizations()->getMemberRoles( 'test-organization', '' ); } - public function testThatAddMemberRoleRequestIsFormedProperly() - { - $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); - - $api->call()->organizations()->addMemberRole('test-organization', 'test-user', 'test-role'); - - $this->assertEquals( 'POST', $api->getHistoryMethod() ); - $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/members/test-user/roles', $api->getHistoryUrl() ); - - $headers = $api->getHistoryHeaders(); - $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); - $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); - - $body = $api->getHistoryBody(); - $this->assertArrayHasKey( 'roles', $body ); - $this->assertContains('test-role', $body['roles']); - } - - public function testThatAddMemberRoleWithEmptyIdThrowsException() - { - $api = new MockManagementApi(); - - $this->expectException(EmptyOrInvalidParameterException::class); - $this->expectExceptionMessage('Empty or invalid organization.'); - - $api->call()->organizations()->addMemberRole( '', '', '' ); - } - - public function testThatAddMemberRoleWithEmptyUserThrowsException() - { - $api = new MockManagementApi(); - - $this->expectException(EmptyOrInvalidParameterException::class); - $this->expectExceptionMessage('Empty or invalid user.'); - - $api->call()->organizations()->addMemberRole( 'test-organization', '', '' ); - } - - public function testThatAddMemberRoleWithEmptyRoleThrowsException() - { - $api = new MockManagementApi(); - - $this->expectException(EmptyOrInvalidParameterException::class); - $this->expectExceptionMessage('Empty or invalid role.'); - - $api->call()->organizations()->addMemberRole( 'test-organization', 'test-rule', '' ); - } - public function testThatAddMemberRolesRequestIsFormedProperly() { $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); @@ -721,54 +597,6 @@ public function testThatAddMemberRolesWithEmptyRolesThrowsException() $api->call()->organizations()->addMemberRoles( 'test-organization', 'test-rule', [] ); } - public function testThatRemoveMemberRoleRequestIsFormedProperly() - { - $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); - - $api->call()->organizations()->removeMemberRole('test-organization', 'test-user', 'test-role'); - - $this->assertEquals( 'DELETE', $api->getHistoryMethod() ); - $this->assertStringStartsWith( 'https://api.test.local/api/v2/organizations/test-organization/members/test-user/roles', $api->getHistoryUrl() ); - - $headers = $api->getHistoryHeaders(); - $this->assertEquals( 'Bearer __api_token__', $headers['Authorization'][0] ); - $this->assertEquals( self::$expectedTelemetry, $headers['Auth0-Client'][0] ); - - $body = $api->getHistoryBody(); - $this->assertArrayHasKey( 'roles', $body ); - $this->assertContains('test-role', $body['roles']); - } - - public function testThatRemoveMemberRoleWithEmptyIdThrowsException() - { - $api = new MockManagementApi(); - - $this->expectException(EmptyOrInvalidParameterException::class); - $this->expectExceptionMessage('Empty or invalid organization.'); - - $api->call()->organizations()->removeMemberRole( '', '', '' ); - } - - public function testThatRemoveMemberRoleWithEmptyUserThrowsException() - { - $api = new MockManagementApi(); - - $this->expectException(EmptyOrInvalidParameterException::class); - $this->expectExceptionMessage('Empty or invalid user.'); - - $api->call()->organizations()->removeMemberRole( 'test-organization', '', '' ); - } - - public function testThatRemoveMemberRoleWithEmptyRoleThrowsException() - { - $api = new MockManagementApi(); - - $this->expectException(EmptyOrInvalidParameterException::class); - $this->expectExceptionMessage('Empty or invalid role.'); - - $api->call()->organizations()->removeMemberRole( 'test-organization', 'test-rule', '' ); - } - public function testThatRemoveMemberRolesRequestIsFormedProperly() { $api = new MockManagementApi( [ new Response( 200, self::$headers ) ] ); From 0962a6439a617226db71a4b04c0495f4d797d896 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Wed, 31 Mar 2021 08:07:43 -0400 Subject: [PATCH 32/38] tests: Tweaks to integration test --- .../API/Management/OrganizationsIntegrationTest.php | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/integration/API/Management/OrganizationsIntegrationTest.php b/tests/integration/API/Management/OrganizationsIntegrationTest.php index 72cbc885..0d9d0744 100644 --- a/tests/integration/API/Management/OrganizationsIntegrationTest.php +++ b/tests/integration/API/Management/OrganizationsIntegrationTest.php @@ -5,10 +5,6 @@ use Auth0\SDK\API\Management; use Auth0\SDK\API\Management\Organizations; use Auth0\Tests\API\ApiTests; -use GuzzleHttp\Exception\RequestException; -use SebastianBergmann\RecursionContext\InvalidArgumentException; -use PHPUnit\Framework\Exception; -use PHPUnit\Framework\ExpectationFailedException; /** * Class OrganizationsIntegrationTest. @@ -114,7 +110,7 @@ public function setUp(): void // Add the new user to the organization as a member for our tests usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); - $this->api->addMember($this->organization['id'], $this->user['user_id']); + $this->api->addMembers($this->organization['id'], [ $this->user['user_id'] ]); // Add a role to the new member of the organization for our tests usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); @@ -267,7 +263,7 @@ public function testMemberRoles() // Add our role to the organization member. usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); - $this->api->addMemberRole($this->organization['id'], $this->user['user_id'], $this->role['id']); + $this->api->addMemberRoles($this->organization['id'], $this->user['user_id'], [ $this->role['id'] ]); // Confirm that the organization member now has the role. usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); @@ -280,7 +276,7 @@ public function testMemberRoles() // Remove the role from the organization member. usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); - $response = $this->api->removeMemberRole($this->organization['id'], $this->user['user_id'], $this->role['id']); + $response = $this->api->removeMemberRoles($this->organization['id'], $this->user['user_id'], [ $this->role['id'] ]); // Confirm that the organization member once again has no roles. usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); From f841bbc5ddac2410409392958858b2864b7f0572 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Wed, 31 Mar 2021 09:16:11 -0400 Subject: [PATCH 33/38] fix: Remove leftover debug code --- .../integration/API/Management/OrganizationsIntegrationTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/integration/API/Management/OrganizationsIntegrationTest.php b/tests/integration/API/Management/OrganizationsIntegrationTest.php index 0d9d0744..839fec1a 100644 --- a/tests/integration/API/Management/OrganizationsIntegrationTest.php +++ b/tests/integration/API/Management/OrganizationsIntegrationTest.php @@ -106,7 +106,6 @@ public function setUp(): void 'email_verified' => true, 'password' => password_hash(uniqid('php-sdk-test-password-'), PASSWORD_DEFAULT) ]); - // var_dump($this->user); // Add the new user to the organization as a member for our tests usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); From eeea4499b64c0a67a7f8d5c8af467586543a33a5 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Wed, 31 Mar 2021 12:15:01 -0400 Subject: [PATCH 34/38] feat: Support passing organization_id to email verification and change job/ticket endpoints --- src/API/Management/Jobs.php | 13 ++++-- src/API/Management/Tickets.php | 84 ++++++++++++++++++++++------------ 2 files changed, 64 insertions(+), 33 deletions(-) diff --git a/src/API/Management/Jobs.php b/src/API/Management/Jobs.php index 723cccf1..a84870bf 100644 --- a/src/API/Management/Jobs.php +++ b/src/API/Management/Jobs.php @@ -125,10 +125,11 @@ public function exportUsers($params = []) * * @param string $user_id User ID of the user to send the verification email to. * @param array $params Array of optional parameters to add. - * - client_id: Client ID of the requesting application. If not provided, the global Client ID will be used. - * - identity: The optional identity of the user, as an array. Required to verify primary identities when using social, enterprise, or passwordless connections. It is also required to verify secondary identities. - * - user_id: User ID of the identity to be verified. Must be a non-empty string. - * - provider: Identity provider name of the identity (e.g. google-oauth2). Must be a non-empty string. + * - client_id: Client ID of the requesting application. If not provided, the global Client ID will be used. + * - identity: The optional identity of the user, as an array. Required to verify primary identities when using social, enterprise, or passwordless connections. It is also required to verify secondary identities. + * - user_id: User ID of the identity to be verified. Must be a non-empty string. + * - provider: Identity provider name of the identity (e.g. google-oauth2). Must be a non-empty string. + * - organization_id: ID of the organization. If provided, the organization_id and organization_name will be included as query arguments in the link back to the application. * * @throws EmptyOrInvalidParameterException Thrown if any required parameters are empty or invalid. * @throws \Exception Thrown by the HTTP client when there is a problem with the API call. @@ -145,6 +146,10 @@ public function sendVerificationEmail($user_id, array $params = []) $body['client_id'] = $params['client_id']; } + if (! empty( $params['organization_id'] )) { + $body['organization_id'] = $params['organization_id']; + } + if (! empty( $params['identity'] )) { if (empty( $params['identity']['user_id']) || ! is_string($params['identity']['user_id'])) { throw new EmptyOrInvalidParameterException('Missing required "user_id" field of the "identity" object.'); diff --git a/src/API/Management/Tickets.php b/src/API/Management/Tickets.php index d39943fa..26680d40 100644 --- a/src/API/Management/Tickets.php +++ b/src/API/Management/Tickets.php @@ -8,14 +8,16 @@ class Tickets extends GenericResource { /** * - * @param string $user_id - * @param null|string $result_url + * @param string $user_id ID of the user for whom the ticket should be created. + * @param null|string $result_url URL the user will be redirected to in the classic Universal Login experience once the ticket is used. * @param array $params Array of optional parameters to add. - * - ttl_sec: Number of seconds for which the ticket is valid before expiration. If unspecified or set to 0, this value defaults to 432000 seconds (5 days). - * - includeEmailInRedirect: Whether to include the email address as part of the returnUrl in the reset_email (true), or not (false). - * - identity: The optional identity of the user, as an array. Required to verify primary identities when using social, enterprise, or passwordless connections. It is also required to verify secondary identities. - * - user_id: User ID of the identity to be verified. Must be a non-empty string. - * - provider: Identity provider name of the identity (e.g. google-oauth2). Must be a non-empty string. + * - ttl_sec: Number of seconds for which the ticket is valid before expiration. If unspecified or set to 0, this value defaults to 432000 seconds (5 days). + * - includeEmailInRedirect: Whether to include the email address as part of the returnUrl in the reset_email (true), or not (false). + * - identity: The optional identity of the user, as an array. Required to verify primary identities when using social, enterprise, or passwordless connections. It is also required to verify secondary identities. + * - user_id: User ID of the identity to be verified. Must be a non-empty string. + * - provider: Identity provider name of the identity (e.g. google-oauth2). Must be a non-empty string. + * - client_id: ID of the client. If provided for tenants using New Universal Login experience, the user will be prompted to redirect to the default login route of the corresponding application once the ticket is created. + * - organization_id: ID of the organization. If provided, the organization_id and organization_name will be included as query arguments in the link back to the application. * * @throws EmptyOrInvalidParameterException Thrown if any required parameters are empty or invalid. * @throws \Exception Thrown by the HTTP client when there is a problem with the API call. @@ -27,10 +29,19 @@ class Tickets extends GenericResource public function createEmailVerificationTicket($user_id, $result_url = null, array $params = []) { $body = ['user_id' => $user_id]; + if ($result_url !== null) { $body['result_url'] = $result_url; } + if (! empty( $params['client_id'] )) { + $body['client_id'] = $params['client_id']; + } + + if (! empty( $params['organization_id'] )) { + $body['organization_id'] = $params['organization_id']; + } + if (! empty( $params['identity'] )) { if (empty( $params['identity']['user_id']) || ! is_string($params['identity']['user_id'])) { throw new EmptyOrInvalidParameterException('Missing required "user_id" field of the "identity" object.'); @@ -52,12 +63,14 @@ public function createEmailVerificationTicket($user_id, $result_url = null, arra /** * - * @param string $user_id - * @param null|string $new_password - * @param null|string $result_url - * @param null|string $connection_id - * @param null|string $ttl - * @param null|string $client_id + * @param null|string $user_id User for whom the ticket should be created. Conflicts with: $connection_id + * @param null|string $new_password New password to assign to the user. + * @param null|string $result_url URL the user will be redirected to in the classic Universal Login experience once the ticket is used. + * @param null|string $connection_id Connection to use, allowing user to be specified using $email, rather than $user_id. Requires $email. Conflicts with $user_id. + * @param null|integer $ttl Number of seconds this ticket will be valid before expiring. Defaults to 432000 seconds (5 days.) + * @param null|string $client_id If provided for tenants using New Universal Login experience, the user will be prompted to redirect to the default login route of the corresponding application once the ticket is used. + * @param null|string $organization_id If provided, the organization_id and organization_name will be included as query arguments in the link back to the application. + * * @return mixed */ public function createPasswordChangeTicket( @@ -66,19 +79,23 @@ public function createPasswordChangeTicket( $result_url = null, $connection_id = null, $ttl = null, - $client_id = null + $client_id = null, + $organization_id = null ) { - return $this->createPasswordChangeTicketRaw($user_id, null, $new_password, $result_url, $connection_id, $ttl, $client_id); + return $this->createPasswordChangeTicketRaw($user_id, null, $new_password, $result_url, $connection_id, $ttl, $client_id, $organization_id); } /** * - * @param string $email - * @param null|string $new_password - * @param null|string $result_url - * @param null|string $connection_id - * @param null|integer $ttl + * @param null|string $email Email address of the user for whom the ticket should be created. Requires $connection_id. + * @param null|string $new_password New password to assign to the user. + * @param null|string $result_url URL the user will be redirected to in the classic Universal Login experience once the ticket is used. + * @param null|string $connection_id Connection to use, allowing user to be specified using $email, rather than $user_id. Requires $email. Conflicts with $user_id. + * @param null|integer $ttl Number of seconds this ticket will be valid before expiring. Defaults to 432000 seconds (5 days.) + * @param null|string $client_id If provided for tenants using New Universal Login experience, the user will be prompted to redirect to the default login route of the corresponding application once the ticket is used. + * @param null|string $organization_id If provided, the organization_id and organization_name will be included as query arguments in the link back to the application. + * * @return mixed */ public function createPasswordChangeTicketByEmail( @@ -87,20 +104,24 @@ public function createPasswordChangeTicketByEmail( $result_url = null, $connection_id = null, $ttl = null, - $client_id = null + $client_id = null, + $organization_id = null ) { - return $this->createPasswordChangeTicketRaw(null, $email, $new_password, $result_url, $connection_id, $ttl, $client_id); + return $this->createPasswordChangeTicketRaw(null, $email, $new_password, $result_url, $connection_id, $ttl, $client_id, $organization_id); } /** * - * @param null|string $user_id - * @param null|string $email - * @param null|string $new_password - * @param null|string $result_url - * @param null|string $connection_id - * @param null|integer $ttl + * @param null|string $user_id User for whom the ticket should be created. Conflicts with: $connection_id, $email + * @param null|string $email Email address of the user for whom the ticket should be created. Requires $connection_id. Conflicts with $user_id. + * @param null|string $new_password New password to assign to the user. + * @param null|string $result_url URL the user will be redirected to in the classic Universal Login experience once the ticket is used. + * @param null|string $connection_id Connection to use, allowing user to be specified using $email, rather than $user_id. Requires $email. Conflicts with $user_id. + * @param null|integer $ttl Number of seconds this ticket will be valid before expiring. Defaults to 432000 seconds (5 days.) + * @param null|string $client_id If provided for tenants using New Universal Login experience, the user will be prompted to redirect to the default login route of the corresponding application once the ticket is used. + * @param null|string $organization_id If provided, the organization_id and organization_name will be included as query arguments in the link back to the application. + * * @return mixed */ public function createPasswordChangeTicketRaw( @@ -110,7 +131,8 @@ public function createPasswordChangeTicketRaw( $result_url = null, $connection_id = null, $ttl = null, - $client_id = null + $client_id = null, + $organization_id = null ) { $body = []; @@ -143,6 +165,10 @@ public function createPasswordChangeTicketRaw( $body['client_id'] = $client_id; } + if ($organization_id) { + $body['organization_id'] = $organization_id; + } + return $this->apiClient->method('post') ->addPath('tickets', 'password-change') ->withBody(json_encode($body)) From 9369fad2e7b2d51c7674d0fe7a04189e9e1114b6 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Wed, 31 Mar 2021 15:15:00 -0400 Subject: [PATCH 35/38] docs: Specify the inviter.name and invtee.email requirements on Organizations::createInvitation() --- src/API/Management/Organizations.php | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/API/Management/Organizations.php b/src/API/Management/Organizations.php index 56d9a1e8..4fe97d96 100644 --- a/src/API/Management/Organizations.php +++ b/src/API/Management/Organizations.php @@ -529,8 +529,10 @@ public function getInvitation( * * @param string $organization Organization (by ID) to create the invitation for. * @param string $clientId Client (by ID) to create the invitation for. This Client must be associated with the Organization. - * @param array $inviter An array containing information about the inviter. Requires a 'name' key, indicating who created the invitation. - * @param array $invitee An array containing information about the invitee. Requires an 'email' key, indicating where to send the invite. + * @param array $inviter An array containing information about the inviter. Requires a 'name' key minimally. + * - 'name' Required. A name to identify who is sending the invitation. + * @param array $invitee An array containing information about the invitee. Requires an 'email' key. + * - 'email' Required. An email address where the invitation should be sent. * @param array $params Optional. Options to include with the request, such as pagination or filtering parameters. * * @return mixed From ec00082bcd8ee9d0e9f26134732ff72f337ef447 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Wed, 31 Mar 2021 15:15:22 -0400 Subject: [PATCH 36/38] tests: Add integration tests for invitations endpoints. --- .../OrganizationsIntegrationTest.php | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/tests/integration/API/Management/OrganizationsIntegrationTest.php b/tests/integration/API/Management/OrganizationsIntegrationTest.php index 839fec1a..9c0145ed 100644 --- a/tests/integration/API/Management/OrganizationsIntegrationTest.php +++ b/tests/integration/API/Management/OrganizationsIntegrationTest.php @@ -284,4 +284,82 @@ public function testMemberRoles() $this->assertIsArray($response); $this->assertEmpty($response); } + + public function testInvitations() + { + $env = self::getEnv(); + + // Confirm there are no invitations + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $response = $this->api->getInvitations($this->organization['id']); + + $this->assertIsArray($response); + $this->assertEmpty($response); + + // Create an invitation + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $response = $this->api->createInvitation( + $this->organization['id'], + $env['APP_CLIENT_ID'], + [ + 'name' => 'Evan Sims' + ], + [ + 'email' => uniqid('php-sdk-test-user-') . '@test.com' + ] + ); + + $this->assertIsArray($response); + $this->assertArrayHasKey('organization_id', $response); + $this->assertEquals($this->organization['id'], $response['organization_id']); + $this->assertArrayHasKey('client_id', $response); + $this->assertEquals($env['APP_CLIENT_ID'], $response['client_id']); + $this->assertArrayHasKey('invitation_url', $response); + $this->assertArrayHasKey('ticket_id', $response); + + // Confirm pagination works + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $response = $this->api->getInvitations($this->organization['id'], [ 'page' => 1 ]); + + $this->assertIsArray($response); + $this->assertEmpty($response); + + // Confirm there is one invitation + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $response = $this->api->getInvitations($this->organization['id']); + + $this->assertIsArray($response); + $this->assertNotEmpty($response); + $this->assertCount(1, $response); + $this->assertIsArray($response[0]); + $this->assertArrayHasKey('organization_id', $response[0]); + $this->assertEquals($this->organization['id'], $response[0]['organization_id']); + $this->assertArrayHasKey('client_id', $response[0]); + $this->assertEquals($env['APP_CLIENT_ID'], $response[0]['client_id']); + $this->assertArrayHasKey('invitation_url', $response[0]); + $this->assertArrayHasKey('ticket_id', $response[0]); + + // Confirm invitation querying works + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $response = $this->api->getInvitation($this->organization['id'], $response[0]['id']); + + $this->assertIsArray($response); + $this->assertArrayHasKey('organization_id', $response); + $this->assertEquals($this->organization['id'], $response['organization_id']); + $this->assertArrayHasKey('client_id', $response); + $this->assertEquals($env['APP_CLIENT_ID'], $response['client_id']); + $this->assertArrayHasKey('invitation_url', $response); + $this->assertArrayHasKey('ticket_id', $response); + + // Confirm invitation deletion works + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $this->api->deleteInvitation($this->organization['id'], $response['id']); + + // Confirm no invitations remain + usleep(AUTH0_PHP_TEST_INTEGRATION_SLEEP); + $response = $this->api->getInvitations($this->organization['id']); + + $this->assertIsArray($response); + $this->assertEmpty($response); + } } From 002d9265ce8c4e3bf2896a09aae9ecc8aee63e23 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Wed, 31 Mar 2021 15:55:40 -0400 Subject: [PATCH 37/38] fix: Typo in test name --- tests/unit/API/Management/OrganizationsTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/API/Management/OrganizationsTest.php b/tests/unit/API/Management/OrganizationsTest.php index 471115b9..7bb16742 100644 --- a/tests/unit/API/Management/OrganizationsTest.php +++ b/tests/unit/API/Management/OrganizationsTest.php @@ -816,7 +816,7 @@ public function testThatDeleteInvitationWithEmptyIdThrowsException() $api->call()->organizations()->deleteInvitation( '', '', ); } - public function testThatDeeteInvitationWithEmptyClientThrowsException() + public function testThatDeleteInvitationWithEmptyClientThrowsException() { $api = new MockManagementApi(); From 0a172a303ef6a4016bfff85ddcaca62ff28e4605 Mon Sep 17 00:00:00 2001 From: Evan Sims Date: Thu, 1 Apr 2021 11:29:21 -0400 Subject: [PATCH 38/38] docs: Fix docblock parameters on getEnabledConnection --- src/API/Management/Organizations.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/API/Management/Organizations.php b/src/API/Management/Organizations.php index 4fe97d96..bddc83b3 100644 --- a/src/API/Management/Organizations.php +++ b/src/API/Management/Organizations.php @@ -200,8 +200,8 @@ public function getEnabledConnections( * Get a connection (by ID) associated with an organization. * Required scope: "read:organization_connections" * - * @param string $organization Organization (by ID) to list connections of. - * @param array $params Optional. Additional options to include with the request, such as pagination or filtering parameters. + * @param string $organization Organization (by ID) that the connection is associated with. + * @param string $connection Connection (by ID) to retrieve details for. * * @return mixed *