From 8b9221f3a6fc1a1fd0000f7cfb511eeccfdac4eb Mon Sep 17 00:00:00 2001 From: Braden Keith Date: Tue, 11 Mar 2025 13:05:01 -0400 Subject: [PATCH 01/26] Improve Webhook and BaseWorkOSResource PHPDoc types --- lib/Resource/BaseWorkOSResource.php | 16 ++++++++++++++++ lib/Webhook.php | 2 +- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/Resource/BaseWorkOSResource.php b/lib/Resource/BaseWorkOSResource.php index b8004a0..71b0a48 100644 --- a/lib/Resource/BaseWorkOSResource.php +++ b/lib/Resource/BaseWorkOSResource.php @@ -4,6 +4,22 @@ class BaseWorkOSResource { + /** + * Maps response keys to resource keys. + * Child classes should override this constant. + * + * @var array + */ + protected const RESPONSE_TO_RESOURCE_KEY = []; + + /** + * List of attributes available in this resource. + * Child classes should override this constant. + * + * @var array + */ + protected const RESOURCE_ATTRIBUTES = []; + /** * @var array $values; */ diff --git a/lib/Webhook.php b/lib/Webhook.php index 1435a1d..ce720fe 100644 --- a/lib/Webhook.php +++ b/lib/Webhook.php @@ -14,7 +14,7 @@ class Webhook /** * Initializes an Event object from a JSON payload * - * @return string + * @return string|Resource\Webhook */ public function constructEvent($sigHeader, $payload, $secret, $tolerance) { From 8988129c13234bb898161baabb5b96addc4b7e70 Mon Sep 17 00:00:00 2001 From: Braden Keith Date: Tue, 11 Mar 2025 13:16:09 -0400 Subject: [PATCH 02/26] Enhance Webhook class with improved code style and PHPDoc annotations --- lib/Resource/Webhook.php | 65 ++++++++++++++++++++++++++++++++++++++-- lib/Webhook.php | 37 +++++++++++------------ 2 files changed, 79 insertions(+), 23 deletions(-) diff --git a/lib/Resource/Webhook.php b/lib/Resource/Webhook.php index cf29a31..90539e6 100644 --- a/lib/Resource/Webhook.php +++ b/lib/Resource/Webhook.php @@ -6,15 +6,74 @@ * Class Webhook. * * Representation of a webhook resulting from a client ConstructEvent function. + * + * @property-read 'user_registration_action_context'|'authentication_action_context' $object The type of webhook event + * + * User Registration Action Properties + * @property-read ?object{ + * object: 'user_data', + * email: string, + * first_name: string, + * last_name: string + * } $user_data User information for registration events + * @property-read ?object{ + * object: 'invitation', + * id: string, + * email: string, + * expires_at: string, + * created_at: string, + * updated_at: string, + * accepted_at: ?string, + * revoked_at: ?string, + * organization_id: string, + * inviter_user_id: string + * } $invitation Invitation details for registration events + * + * Authentication Action Properties + * @property-read ?object{ + * object: 'user', + * id: string, + * email: string, + * first_name: string, + * last_name: string, + * email_verified: bool, + * profile_picture_url: string, + * created_at: string, + * updated_at: string + * } $user User information for authentication events + * @property-read ?string $issuer The authentication issuer + * @property-read ?object{ + * object: 'organization', + * id: string, + * name: string, + * allow_profiles_outside_organization: bool, + * domains: array, + * created_at: string, + * updated_at: string + * } $organization Organization details for authentication events + * @property-read ?object{ + * object: 'organization_membership', + * id: string, + * user_id: string, + * organization_id: string, + * role: array{slug: string}, + * status: string, + * created_at: string, + * updated_at: string + * } $organization_membership Organization membership details for authentication events + * + * Common Properties + * @property-read string $ip_address IP address of the event + * @property-read string $user_agent User agent string of the event + * @property-read string $device_fingerprint Device fingerprint of the event */ class Webhook { /** * Creates a webhook object from a payload. * - * @param $payload - * - * @return Webhook + * @param string $payload JSON string containing webhook data + * @return static */ public static function constructFromPayload($payload) { diff --git a/lib/Webhook.php b/lib/Webhook.php index ce720fe..64ea1a5 100644 --- a/lib/Webhook.php +++ b/lib/Webhook.php @@ -20,7 +20,7 @@ public function constructEvent($sigHeader, $payload, $secret, $tolerance) { $eventResult = $this->verifyHeader($sigHeader, $payload, $secret, $tolerance); - if ($eventResult == "pass") { + if ($eventResult == 'pass') { return Resource\Webhook::constructFromPayload($payload); } else { return $eventResult; @@ -31,45 +31,43 @@ public function constructEvent($sigHeader, $payload, $secret, $tolerance) * Verifies the header returned from WorkOS contains a valid timestamp * no older than 3 minutes, and computes the signature. * - * @param string $sigHeader WorkOS header containing v1 signature and timestamp - * @param string $payload Body of the webhook - * @param string $secret Webhook secret from the WorkOS dashboard - * @param int $tolerance Number of seconds old the webhook can be before it's invalid - * + * @param string $sigHeader WorkOS header containing v1 signature and timestamp + * @param string $payload Body of the webhook + * @param string $secret Webhook secret from the WorkOS dashboard + * @param int $tolerance Number of seconds old the webhook can be before it's invalid * @return bool true */ public function verifyHeader($sigHeader, $payload, $secret, $tolerance) { - $timestamp = (int)$this->getTimeStamp($sigHeader); + $timestamp = (int) $this->getTimeStamp($sigHeader); $signature = $this->getSignature($sigHeader); $currentTime = time(); - $signedPayload = $timestamp . "." . $payload; - $expectedSignature = hash_hmac("sha256", $signedPayload, $secret, false); + $signedPayload = $timestamp.'.'.$payload; + $expectedSignature = hash_hmac('sha256', $signedPayload, $secret, false); if (empty($timestamp)) { - return "No Timestamp available"; + return 'No Timestamp available'; } elseif (empty($signature)) { - return "No signature hash found with expected scheme v1"; + return 'No signature hash found with expected scheme v1'; } elseif ($timestamp < $currentTime - $tolerance) { - return "Timestamp outside of tolerance"; + return 'Timestamp outside of tolerance'; } elseif ($signature != $expectedSignature) { - return "Constructed signature " . $expectedSignature . "Does not match WorkOS Header Signature " . $signature; + return 'Constructed signature '.$expectedSignature.'Does not match WorkOS Header Signature '.$signature; } else { - return "pass"; + return 'pass'; } } /** * Splits WorkOS header's two values and pulls out timestamp value and returns it * - * @param string $sigHeader WorkOS header containing v1 signature and timestamp - * + * @param string $sigHeader WorkOS header containing v1 signature and timestamp * @return $timestamp */ public function getTimeStamp($sigHeader) { - $workosHeadersSplit = explode(",", $sigHeader, 2); + $workosHeadersSplit = explode(',', $sigHeader, 2); $timestamp = substr($workosHeadersSplit[0], 2); return $timestamp; @@ -78,13 +76,12 @@ public function getTimeStamp($sigHeader) /** * Splits WorkOS headers two values and pulls out the signature value and returns it * - * @param string $sigHeader WorkOS header containing v1 signature and timestamp - * + * @param string $sigHeader WorkOS header containing v1 signature and timestamp * @return string */ public function getSignature($sigHeader) { - $workosHeadersSplit = explode(",", $sigHeader, 2); + $workosHeadersSplit = explode(',', $sigHeader, 2); $signature = substr($workosHeadersSplit[1], 4); return $signature; From 92bc0c672a5c88bb5b28fc7144847fc912323dfe Mon Sep 17 00:00:00 2001 From: Braden Keith Date: Sat, 15 Mar 2025 16:57:08 -0400 Subject: [PATCH 03/26] Add accessToken and refreshToken to AuthenticationResponse class --- lib/Resource/AuthenticationResponse.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/Resource/AuthenticationResponse.php b/lib/Resource/AuthenticationResponse.php index b7bc2bf..0d44b8f 100644 --- a/lib/Resource/AuthenticationResponse.php +++ b/lib/Resource/AuthenticationResponse.php @@ -14,10 +14,14 @@ class AuthenticationResponse extends BaseWorkOSResource "user", "organizationId", "impersonator", + "accessToken", + "refreshToken", ]; public const RESPONSE_TO_RESOURCE_KEY = [ "organization_id" => "organizationId", + "access_token" => "accessToken", + "refresh_token" => "refreshToken", ]; public static function constructFromResponse($response) From eb67567bddca0724eda769799ca1515987694098 Mon Sep 17 00:00:00 2001 From: Braden Keith Date: Sat, 15 Mar 2025 17:28:56 -0400 Subject: [PATCH 04/26] Update AuthenticationResponse class to include organizationId as nullable, and add accessToken, refreshToken, and impersonator properties --- lib/Resource/AuthenticationResponse.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/Resource/AuthenticationResponse.php b/lib/Resource/AuthenticationResponse.php index 0d44b8f..f4df188 100644 --- a/lib/Resource/AuthenticationResponse.php +++ b/lib/Resource/AuthenticationResponse.php @@ -6,7 +6,10 @@ * Class AuthenticationResponse. * * @property User $user - * @property string $organizationId + * @property ?string $organizationId + * @property string $accessToken + * @property string $refreshToken + * @property ?Impersonator $impersonator */ class AuthenticationResponse extends BaseWorkOSResource { From 0c5672a71d94fa905498968d1a9835cb336525ad Mon Sep 17 00:00:00 2001 From: Braden Keith Date: Sat, 15 Mar 2025 17:56:11 -0400 Subject: [PATCH 05/26] Enhance OrganizationMembership class with additional PHPDoc properties for improved type documentation --- lib/Resource/OrganizationMembership.php | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/Resource/OrganizationMembership.php b/lib/Resource/OrganizationMembership.php index 09f2749..00667dc 100644 --- a/lib/Resource/OrganizationMembership.php +++ b/lib/Resource/OrganizationMembership.php @@ -4,8 +4,16 @@ /** * Class OrganizationMembership. + * + * @property 'organization_membership' $object + * @property string $id + * @property string $userId + * @property string $organizationId + * @property RoleResponse $role + * @property 'active'|'inactive'|'pending' $status + * @property string $createdAt + * @property string $updatedAt */ - class OrganizationMembership extends BaseWorkOSResource { public const RESOURCE_TYPE = "organization_membership"; @@ -15,6 +23,7 @@ class OrganizationMembership extends BaseWorkOSResource "id", "userId", "organizationId", + "role", "status", "createdAt", "updatedAt" @@ -25,6 +34,7 @@ class OrganizationMembership extends BaseWorkOSResource "id" => "id", "user_id" => "userId", "organization_id" => "organizationId", + "role" => "role", "status" => "status", "created_at" => "createdAt", "updated_at" => "updatedAt" From ff55574e950969723ba9cf83a94f5862f2f143aa Mon Sep 17 00:00:00 2001 From: Eric Roberts Date: Fri, 21 Mar 2025 13:56:38 -0400 Subject: [PATCH 06/26] Add metadata and external id (#268) And allow to be passed when creating or updating a user or organization. --- lib/Organizations.php | 39 ++++++++++++++++++++++++++--- lib/Resource/Organization.php | 8 ++++-- lib/Resource/User.php | 8 ++++-- lib/UserManagement.php | 25 +++++++++++++++--- tests/WorkOS/OrganizationsTest.php | 20 +++++++++++---- tests/WorkOS/UserManagementTest.php | 32 +++++++++++++++++------ 6 files changed, 109 insertions(+), 23 deletions(-) diff --git a/lib/Organizations.php b/lib/Organizations.php index 2085901..4f9f095 100644 --- a/lib/Organizations.php +++ b/lib/Organizations.php @@ -67,13 +67,22 @@ public function listOrganizations( * @param null|boolean $allowProfilesOutsideOrganization [Deprecated] If you need to allow sign-ins from * any email domain, contact support@workos.com. * @param null|string $idempotencyKey is a unique string that identifies a distinct organization + * @param null|string $externalId The organization's external id + * @param null|array $metadata The organization's metadata * * @throws Exception\WorkOSException * * @return Resource\Organization */ - public function createOrganization($name, $domains = null, $allowProfilesOutsideOrganization = null, $idempotencyKey = null, $domain_data = null) - { + public function createOrganization( + $name, + $domains = null, + $allowProfilesOutsideOrganization = null, + $idempotencyKey = null, + $domain_data = null, + $externalId = null, + $metadata = null + ) { $idempotencyKey ? $headers = array("Idempotency-Key: $idempotencyKey") : $headers = null; $organizationsPath = "organizations"; @@ -88,6 +97,12 @@ public function createOrganization($name, $domains = null, $allowProfilesOutside if (isset($allowProfilesOutsideOrganization)) { $params["allow_profiles_outside_organization"] = $allowProfilesOutsideOrganization; } + if (isset($externalId)) { + $params["external_id"] = $externalId; + } + if (isset($metadata)) { + $params["metadata"] = $metadata; + } $response = Client::request(Client::METHOD_POST, $organizationsPath, $headers, $params, true); @@ -104,11 +119,21 @@ public function createOrganization($name, $domains = null, $allowProfilesOutside * @param null|boolean $allowProfilesOutsideOrganization [Deprecated] If you need to allow sign-ins from * any email domain, contact support@workos.com. * @param null|string $stripeCustomerId The Stripe Customer ID of the Organization. + * @param null|string $externalId The organization's external id + * @param null|array $metadata The organization's metadata * * @throws Exception\WorkOSException */ - public function updateOrganization($organization, $domains = null, $name = null, $allowProfilesOutsideOrganization = null, $domain_data = null, $stripeCustomerId = null) - { + public function updateOrganization( + $organization, + $domains = null, + $name = null, + $allowProfilesOutsideOrganization = null, + $domain_data = null, + $stripeCustomerId = null, + $externalId = null, + $metadata = null + ) { $organizationsPath = "organizations/{$organization}"; $params = [ "name" => $name ]; @@ -125,6 +150,12 @@ public function updateOrganization($organization, $domains = null, $name = null, if (isset($stripeCustomerId)) { $params["stripe_customer_id"] = $stripeCustomerId; } + if (isset($externalId)) { + $params["external_id"] = $externalId; + } + if (isset($metadata)) { + $params["metadata"] = $metadata; + } $response = Client::request(Client::METHOD_PUT, $organizationsPath, null, $params, true); diff --git a/lib/Resource/Organization.php b/lib/Resource/Organization.php index 659ee23..47ef5a2 100644 --- a/lib/Resource/Organization.php +++ b/lib/Resource/Organization.php @@ -14,13 +14,17 @@ class Organization extends BaseWorkOSResource "id", "name", "allowProfilesOutsideOrganization", - "domains" + "domains", + "externalId", + "metadata" ]; public const RESPONSE_TO_RESOURCE_KEY = [ "id" => "id", "name" => "name", "allow_profiles_outside_organization" => "allowProfilesOutsideOrganization", - "domains" => "domains" + "domains" => "domains", + "external_id" => "externalId", + "metadata" => "metadata" ]; } diff --git a/lib/Resource/User.php b/lib/Resource/User.php index 7df0c7a..b819a4a 100644 --- a/lib/Resource/User.php +++ b/lib/Resource/User.php @@ -19,7 +19,9 @@ class User extends BaseWorkOSResource "profilePictureUrl", "lastSignInAt", "createdAt", - "updatedAt" + "updatedAt", + "externalId", + "metadata" ]; public const RESPONSE_TO_RESOURCE_KEY = [ @@ -32,6 +34,8 @@ class User extends BaseWorkOSResource "profile_picture_url" => "profilePictureUrl", "last_sign_in_at" => "lastSignInAt", "created_at" => "createdAt", - "updated_at" => "updatedAt" + "updated_at" => "updatedAt", + "external_id" => "externalId", + "metadata" => "metadata" ]; } diff --git a/lib/UserManagement.php b/lib/UserManagement.php index febeecc..b402455 100644 --- a/lib/UserManagement.php +++ b/lib/UserManagement.php @@ -26,13 +26,24 @@ class UserManagement * @param boolean|null $emailVerified A boolean declaring if the user's email has been verified. * @param string|null $passwordHash The hashed password to set for the user. * @param string|null $passwordHashType The algorithm originally used to hash the password. Valid values are `bcrypt`, `ssha`, and `firebase-scrypt`. + * @param string|null $externalId The user's external ID. + * @param array $metadata The user's metadata. * * @throws Exception\WorkOSException * * @return Resource\User */ - public function createUser($email, $password = null, $firstName = null, $lastName = null, $emailVerified = null, $passwordHash = null, $passwordHashType = null) - { + public function createUser( + $email, + $password = null, + $firstName = null, + $lastName = null, + $emailVerified = null, + $passwordHash = null, + $passwordHashType = null, + $externalId = null, + $metadata = null + ) { $path = "user_management/users"; $params = [ "email" => $email, @@ -42,6 +53,8 @@ public function createUser($email, $password = null, $firstName = null, $lastNam "email_verified" => $emailVerified, "password_hash" => $passwordHash, "password_hash_type" => $passwordHashType, + "external_id" => $externalId, + "metadata" => $metadata ]; $response = Client::request(Client::METHOD_POST, $path, null, $params, true); @@ -77,6 +90,8 @@ public function getUser($userId) * @param string|null $password The password to set for the user. * @param string|null $passwordHash The hashed password to set for the user. * @param string|null $passwordHashType The algorithm originally used to hash the password. Valid values are `bcrypt`, `ssha`, and `firebase-scrypt`. + * @param string|null $externalId The user's external ID. + * @param array|null $metadata The user's metadata. * * @throws Exception\WorkOSException * @@ -89,7 +104,9 @@ public function updateUser( $emailVerified = null, $password = null, $passwordHash = null, - $passwordHashType = null + $passwordHashType = null, + $externalId = null, + $metadata = null ) { $path = "user_management/users/{$userId}"; @@ -100,6 +117,8 @@ public function updateUser( "password" => $password, "password_hash" => $passwordHash, "password_hash_type" => $passwordHashType, + "external_id" => $externalId, + "metadata" => $metadata ]; $response = Client::request(Client::METHOD_PUT, $path, null, $params, true); diff --git a/tests/WorkOS/OrganizationsTest.php b/tests/WorkOS/OrganizationsTest.php index 8f05b1c..89bbf42 100644 --- a/tests/WorkOS/OrganizationsTest.php +++ b/tests/WorkOS/OrganizationsTest.php @@ -205,7 +205,9 @@ private function createOrganizationResponseFixture() "id" => "org_domain_01EHQMYV71XT8H31WE5HF8YK4A", "domain" => "example.com" ] - ] + ], + "external_id" => null, + "metadata" => [] ]); } @@ -225,7 +227,9 @@ private function organizationsResponseFixture() "id" => "org_domain_01EHQMYV71XT8H31WE5HF8YK4A", "domain" => "example.com" ] - ] + ], + "external_id" => null, + "metadata" => [] ], [ "object" => "organization", @@ -238,7 +242,9 @@ private function organizationsResponseFixture() "id" => "org_domain_01EHQMVDTZVA27PK614ME4YK7V", "domain" => "example2.com" ] - ] + ], + "external_id" => null, + "metadata" => [] ], [ "object" => "organization", @@ -251,7 +257,9 @@ private function organizationsResponseFixture() "id" => "org_domain_01EGP9Z6S6HVQ5CPD152GJBEA5", "domain" => "example5.com" ] - ] + ], + "external_id" => null, + "metadata" => [] ] ], "list_metadata" => [ @@ -273,7 +281,9 @@ private function organizationFixture() "id" => "org_domain_01EHQMYV71XT8H31WE5HF8YK4A", "domain" => "example.com" ] - ] + ], + "externalId" => null, + "metadata" => [] ]; } diff --git a/tests/WorkOS/UserManagementTest.php b/tests/WorkOS/UserManagementTest.php index 2c6b9bf..1b6ff72 100644 --- a/tests/WorkOS/UserManagementTest.php +++ b/tests/WorkOS/UserManagementTest.php @@ -55,6 +55,8 @@ public function testUpdateUser() "password" => null, "password_hash" => null, "password_hash_type" => null, + "external_id" => null, + "metadata" => null ]; $this->mockRequest( @@ -481,6 +483,8 @@ public function testCreateUser() "email_verified" => true, "password_hash" => null, "password_hash_type" => null, + "external_id" => null, + "metadata" => null ]; $this->mockRequest( @@ -1442,7 +1446,9 @@ private function userResponseFixture() "profile_picture_url" => "https://example.com/photo.jpg", "last_sign_in_at" => "2021-06-25T19:07:33.155Z", "created_at" => "2021-06-25T19:07:33.155Z", - "updated_at" => "2021-06-25T19:07:33.155Z" + "updated_at" => "2021-06-25T19:07:33.155Z", + "external_id" => null, + "metadata" => [] ] ]); } @@ -1460,7 +1466,9 @@ private function userAndImpersonatorResponseFixture() "profile_picture_url" => "https://example.com/photo.jpg", "last_sign_in_at" => "2021-06-25T19:07:33.155Z", "created_at" => "2021-06-25T19:07:33.155Z", - "updated_at" => "2021-06-25T19:07:33.155Z" + "updated_at" => "2021-06-25T19:07:33.155Z", + "external_id" => null, + "metadata" => [] ], "impersonator" => [ "email" => "admin@foocorp.com", @@ -1500,7 +1508,9 @@ private function createUserResponseFixture() 'profile_picture_url' => 'https://example.com/photo.jpg', "last_sign_in_at" => "2021-06-25T19:07:33.155Z", "created_at" => "2021-06-25T19:07:33.155Z", - "updated_at" => "2021-06-25T19:07:33.155Z" + "updated_at" => "2021-06-25T19:07:33.155Z", + "external_id" => null, + "metadata" => [] ]); } @@ -1608,7 +1618,9 @@ private function getUserResponseFixture() "profile_picture_url" => "https://example.com/photo.jpg", "last_sign_in_at" => "2021-06-25T19:07:33.155Z", "created_at" => "2021-06-25T19:07:33.155Z", - "updated_at" => "2021-06-25T19:07:33.155Z" + "updated_at" => "2021-06-25T19:07:33.155Z", + "external_id" => null, + "metadata" => [] ]); } @@ -1626,7 +1638,9 @@ private function listUsersResponseFixture() "profile_picture_url" => "https://example.com/photo.jpg", "last_sign_in_at" => "2021-06-25T19:07:33.155Z", "created_at" => "2021-06-25T19:07:33.155Z", - "updated_at" => "2021-06-25T19:07:33.155Z" + "updated_at" => "2021-06-25T19:07:33.155Z", + "external_id" => null, + "metadata" => [] ] ], "list_metadata" => [ @@ -1656,7 +1670,9 @@ private function userFixture() "profilePictureUrl" => "https://example.com/photo.jpg", "lastSignInAt" => "2021-06-25T19:07:33.155Z", "createdAt" => "2021-06-25T19:07:33.155Z", - "updatedAt" => "2021-06-25T19:07:33.155Z" + "updatedAt" => "2021-06-25T19:07:33.155Z", + "externalId" => null, + "metadata" => [] ]; } @@ -1673,7 +1689,9 @@ private function userAndOrgResponseFixture() "profile_picture_url" => "https://example.com/photo.jpg", "last_sign_in_at" => "2021-06-25T19:07:33.155Z", "created_at" => "2021-06-25T19:07:33.155Z", - "updated_at" => "2021-06-25T19:07:33.155Z" + "updated_at" => "2021-06-25T19:07:33.155Z", + "external_id" => null, + "metadata" => [] ], "organization_id" => "org_01EHQMYV6MBK39QC5PZXHY59C3", ]); From 09c8949b0b54cc6505ab3678a39530789905647f Mon Sep 17 00:00:00 2001 From: Matt Dzwonczyk <9063128+mattgd@users.noreply.github.com> Date: Fri, 21 Mar 2025 14:08:26 -0400 Subject: [PATCH 07/26] Add email standard attribute to DirectoryUser and mark deprecated standard attributes (#261) --- lib/Resource/DirectoryUser.php | 20 ++++++++++++++++++++ tests/WorkOS/DirectorySyncTest.php | 4 ++++ 2 files changed, 24 insertions(+) diff --git a/lib/Resource/DirectoryUser.php b/lib/Resource/DirectoryUser.php index 2dfed82..eb5a809 100644 --- a/lib/Resource/DirectoryUser.php +++ b/lib/Resource/DirectoryUser.php @@ -14,9 +14,25 @@ class DirectoryUser extends BaseWorkOSResource "rawAttributes", "customAttributes", "firstName", + "email", + /** + * [Deprecated] Will be removed in a future major version. + * Enable the `emails` custom attribute in dashboard and pull from customAttributes instead. + * See https://workos.com/docs/directory-sync/attributes/custom-attributes/auto-mapped-attributes for details. + */ "emails", + /** + * [Deprecated] Will be removed in a future major version. + * Enable the `username` custom attribute in dashboard and pull from customAttributes instead. + * See https://workos.com/docs/directory-sync/attributes/custom-attributes/auto-mapped-attributes for details. + */ "username", "lastName", + /** + * [Deprecated] Will be removed in a future major version. + * Enable the `job_title` custom attribute in dashboard and pull from customAttributes instead. + * See https://workos.com/docs/directory-sync/attributes/custom-attributes/auto-mapped-attributes for details. + */ "jobTitle", "state", "idpId", @@ -30,6 +46,7 @@ class DirectoryUser extends BaseWorkOSResource "raw_attributes" => "rawAttributes", "custom_attributes" => "customAttributes", "first_name" => "firstName", + "email" => "email", "emails" => "emails", "username" => "username", "last_name" => "lastName", @@ -41,6 +58,9 @@ class DirectoryUser extends BaseWorkOSResource "organization_id" => "organizationId" ]; + /** + * [Deprecated] Use `email` instead. + */ public function primaryEmail() { $response = $this; diff --git a/tests/WorkOS/DirectorySyncTest.php b/tests/WorkOS/DirectorySyncTest.php index ebed37d..c75dcd9 100644 --- a/tests/WorkOS/DirectorySyncTest.php +++ b/tests/WorkOS/DirectorySyncTest.php @@ -354,6 +354,7 @@ private function usersResponseFixture() "organization_id" => "org_123", "idp_id" => null, "groups" => null, + "email" => "yoon@seri.com", "emails" => [ [ "primary" => true, @@ -409,6 +410,7 @@ private function userResponseFixture() "organization_id" => "org_123", "idp_id" => null, "groups" => null, + "email" => "yoon@seri.com", "emails" => [ [ "primary" => true, @@ -462,6 +464,7 @@ private function userResponseFixtureNoEmail() "organization_id" => "org_123", "idp_id" => null, "groups" => null, + "email" => null, "emails" => [], "raw_attributes" => [ "schemas" => ["urn:scim:schemas:core:1.0"], @@ -532,6 +535,7 @@ private function userFixture() "fullName" => "Yoon Seri" ], "firstName" => "Yoon", + "email" => "yoon@seri.com", "emails" => [ [ "primary" => true, From 829e4186b9538b784e6e805f2feb777b58d7e686 Mon Sep 17 00:00:00 2001 From: Eric Roberts Date: Fri, 21 Mar 2025 14:46:09 -0400 Subject: [PATCH 08/26] Add function to get organization by external id (#270) And fix typo in getOrganization docstring --- lib/Organizations.php | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/Organizations.php b/lib/Organizations.php index 4f9f095..dc62b78 100644 --- a/lib/Organizations.php +++ b/lib/Organizations.php @@ -163,7 +163,7 @@ public function updateOrganization( } /** - * Get a Directory Group. + * Get an Organization * * @param string $organization WorkOS organization ID * @@ -180,6 +180,24 @@ public function getOrganization($organization) return Resource\Organization::constructFromResponse($response); } + /** + * Get an Organization by its external id + * + * @param string $externalId external id + * + * @throws Exception\WorkOSException + * + * @return Resource\Organization + */ + public function getOrganizationByExternalId($externalId) + { + $organizationsPath = "organizations/external_id/{$externalId}"; + + $response = Client::request(Client::METHOD_GET, $organizationsPath, null, null, true); + + return Resource\Organization::constructFromResponse($response); + } + /** * Delete an Organization. * From f2d7f7bfbe045ee10290bcef980c081a42ecbd5f Mon Sep 17 00:00:00 2001 From: Pepe Date: Fri, 21 Mar 2025 19:49:05 +0100 Subject: [PATCH 09/26] Add support for creating, getting and updating users with external_id property (#267) Co-authored-by: Eric Roberts --- lib/UserManagement.php | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/UserManagement.php b/lib/UserManagement.php index b402455..f40bd88 100644 --- a/lib/UserManagement.php +++ b/lib/UserManagement.php @@ -33,6 +33,7 @@ class UserManagement * * @return Resource\User */ + public function createUser( $email, $password = null, @@ -80,6 +81,24 @@ public function getUser($userId) return Resource\User::constructFromResponse($response); } + /** + * Get a User by external ID. + * + * @param string $externalId The external ID of the user. + * + * @throws Exception\WorkOSException + * + * @return Resource\User + */ + public function getUserByExternalId($externalId) + { + $path = "user_management/users/external_id/{$externalId}"; + + $response = Client::request(Client::METHOD_GET, $path, null, null, true); + + return Resource\User::constructFromResponse($response); + } + /** * Update a User * From d366c83050863fd4e902febd623f799d1432d60a Mon Sep 17 00:00:00 2001 From: Matt Dzwonczyk <9063128+mattgd@users.noreply.github.com> Date: Fri, 21 Mar 2025 15:08:31 -0400 Subject: [PATCH 10/26] Bump to version 4.22.0. (#269) --- lib/Version.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Version.php b/lib/Version.php index 6a589a7..5005602 100644 --- a/lib/Version.php +++ b/lib/Version.php @@ -6,5 +6,5 @@ final class Version { public const SDK_IDENTIFIER = 'WorkOS PHP'; - public const SDK_VERSION = '4.21.0'; + public const SDK_VERSION = '4.22.0'; } From 1ec12f4b234e367e351479ca528b5b73de3a6614 Mon Sep 17 00:00:00 2001 From: Braden Keith Date: Fri, 21 Mar 2025 16:19:52 -0400 Subject: [PATCH 11/26] Structured responses to webhook events (#265) * Add WebhookResponse class for handling webhook actions and responses * Refactor WebhookResponse create method and improve validation * Resolve linting error --------- Co-authored-by: Braden Keith --- lib/Resource/WebhookResponse.php | 111 +++++++++++++ lib/Webhook.php | 17 +- tests/WorkOS/Resource/WebhookResponseTest.php | 149 ++++++++++++++++++ 3 files changed, 275 insertions(+), 2 deletions(-) create mode 100644 lib/Resource/WebhookResponse.php create mode 100644 tests/WorkOS/Resource/WebhookResponseTest.php diff --git a/lib/Resource/WebhookResponse.php b/lib/Resource/WebhookResponse.php new file mode 100644 index 0000000..3f2032c --- /dev/null +++ b/lib/Resource/WebhookResponse.php @@ -0,0 +1,111 @@ +object = $type; + + $payload = [ + 'timestamp' => time() * 1000, + 'verdict' => $verdict + ]; + + if ($verdict === self::VERDICT_DENY) { + $payload['error_message'] = $errorMessage; + } + + $instance->payload = $payload; + + $timestamp = $payload['timestamp']; + $payloadString = json_encode($payload); + $instance->signature = (new Webhook())->computeSignature($timestamp, $payloadString, $secret); + + return $instance; + } + + /** + * Get the response as an array + * + * @return array + */ + public function toArray() + { + $response = [ + 'object' => $this->object, + 'payload' => $this->payload + ]; + + if ($this->signature) { + $response['signature'] = $this->signature; + } + + return $response; + } + + /** + * Get the response as a JSON string + * + * @return string + */ + public function toJson() + { + return json_encode($this->toArray()); + } +} diff --git a/lib/Webhook.php b/lib/Webhook.php index 64ea1a5..4d5d517 100644 --- a/lib/Webhook.php +++ b/lib/Webhook.php @@ -43,8 +43,7 @@ public function verifyHeader($sigHeader, $payload, $secret, $tolerance) $signature = $this->getSignature($sigHeader); $currentTime = time(); - $signedPayload = $timestamp.'.'.$payload; - $expectedSignature = hash_hmac('sha256', $signedPayload, $secret, false); + $expectedSignature = $this->computeSignature($timestamp, $payload, $secret); if (empty($timestamp)) { return 'No Timestamp available'; @@ -86,4 +85,18 @@ public function getSignature($sigHeader) return $signature; } + + /** + * Computes a signature for a webhook payload using the provided timestamp and secret + * + * @param int $timestamp Unix timestamp to use in signature + * @param string $payload The payload to sign + * @param string $secret Secret key used for signing + * @return string The computed HMAC SHA-256 signature + */ + public function computeSignature($timestamp, $payload, $secret) + { + $signedPayload = $timestamp . '.' . $payload; + return hash_hmac('sha256', $signedPayload, $secret, false); + } } diff --git a/tests/WorkOS/Resource/WebhookResponseTest.php b/tests/WorkOS/Resource/WebhookResponseTest.php new file mode 100644 index 0000000..9bda146 --- /dev/null +++ b/tests/WorkOS/Resource/WebhookResponseTest.php @@ -0,0 +1,149 @@ +traitSetUp(); + $this->withApiKey(); + + $this->secret = 'test_secret'; + $this->timestamp = time() * 1000; // milliseconds + } + + public function testCreateAllowResponse() + { + $response = WebhookResponse::create( + WebhookResponse::USER_REGISTRATION_ACTION, + $this->secret, + WebhookResponse::VERDICT_ALLOW + ); + + $array = $response->toArray(); + + $this->assertEquals(WebhookResponse::USER_REGISTRATION_ACTION, $array['object']); + $this->assertArrayHasKey('payload', $array); + $this->assertArrayHasKey('signature', $array); + $this->assertEquals(WebhookResponse::VERDICT_ALLOW, $array['payload']['verdict']); + $this->assertArrayHasKey('timestamp', $array['payload']); + $this->assertArrayNotHasKey('error_message', $array['payload']); + } + + public function testCreateDenyResponse() + { + $errorMessage = 'Registration denied due to risk assessment'; + $response = WebhookResponse::create( + WebhookResponse::USER_REGISTRATION_ACTION, + $this->secret, + WebhookResponse::VERDICT_DENY, + $errorMessage + ); + + $array = $response->toArray(); + + $this->assertEquals(WebhookResponse::USER_REGISTRATION_ACTION, $array['object']); + $this->assertArrayHasKey('payload', $array); + $this->assertArrayHasKey('signature', $array); + $this->assertEquals(WebhookResponse::VERDICT_DENY, $array['payload']['verdict']); + $this->assertEquals($errorMessage, $array['payload']['error_message']); + $this->assertArrayHasKey('timestamp', $array['payload']); + } + + public function testCreateAuthenticationResponse() + { + $response = WebhookResponse::create( + WebhookResponse::AUTHENTICATION_ACTION, + $this->secret, + WebhookResponse::VERDICT_ALLOW + ); + + $array = $response->toArray(); + + $this->assertEquals(WebhookResponse::AUTHENTICATION_ACTION, $array['object']); + $this->assertArrayHasKey('payload', $array); + $this->assertArrayHasKey('signature', $array); + } + + public function testCreateWithoutSecret() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Secret is required'); + + WebhookResponse::create( + WebhookResponse::USER_REGISTRATION_ACTION, + '', + WebhookResponse::VERDICT_ALLOW + ); + } + + public function testInvalidResponseType() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid response type'); + + WebhookResponse::create( + 'invalid_type', + $this->secret, + WebhookResponse::VERDICT_ALLOW + ); + } + + public function testInvalidVerdict() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Invalid verdict'); + + WebhookResponse::create( + WebhookResponse::USER_REGISTRATION_ACTION, + $this->secret, + 'invalid_verdict' + ); + } + + public function testDenyWithoutErrorMessage() + { + $this->expectException(\InvalidArgumentException::class); + $this->expectExceptionMessage('Error message is required when verdict is Deny'); + + WebhookResponse::create( + WebhookResponse::USER_REGISTRATION_ACTION, + $this->secret, + WebhookResponse::VERDICT_DENY + ); + } + + public function testToJson() + { + $response = WebhookResponse::create( + WebhookResponse::USER_REGISTRATION_ACTION, + $this->secret, + WebhookResponse::VERDICT_ALLOW + ); + + $json = $response->toJson(); + $decoded = json_decode($json, true); + + $this->assertIsString($json); + $this->assertIsArray($decoded); + $this->assertEquals(WebhookResponse::USER_REGISTRATION_ACTION, $decoded['object']); + } +} From 19e68d1ffe506b9ad608a41600eedd6dadd07d53 Mon Sep 17 00:00:00 2001 From: Braden Keith Date: Fri, 28 Mar 2025 14:01:56 -0400 Subject: [PATCH 12/26] Update deprecation notices in DirectoryUser class to include version information and improve clarity --- lib/Resource/DirectoryUser.php | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/Resource/DirectoryUser.php b/lib/Resource/DirectoryUser.php index eb5a809..78be8ec 100644 --- a/lib/Resource/DirectoryUser.php +++ b/lib/Resource/DirectoryUser.php @@ -16,20 +16,20 @@ class DirectoryUser extends BaseWorkOSResource "firstName", "email", /** - * [Deprecated] Will be removed in a future major version. + * @deprecated 4.22.0 Will be removed in a future major version. * Enable the `emails` custom attribute in dashboard and pull from customAttributes instead. * See https://workos.com/docs/directory-sync/attributes/custom-attributes/auto-mapped-attributes for details. */ "emails", /** - * [Deprecated] Will be removed in a future major version. + * @deprecated 4.22.0 Will be removed in a future major version. * Enable the `username` custom attribute in dashboard and pull from customAttributes instead. * See https://workos.com/docs/directory-sync/attributes/custom-attributes/auto-mapped-attributes for details. */ "username", "lastName", /** - * [Deprecated] Will be removed in a future major version. + * @deprecated 4.22.0 Will be removed in a future major version. * Enable the `job_title` custom attribute in dashboard and pull from customAttributes instead. * See https://workos.com/docs/directory-sync/attributes/custom-attributes/auto-mapped-attributes for details. */ @@ -59,7 +59,9 @@ class DirectoryUser extends BaseWorkOSResource ]; /** - * [Deprecated] Use `email` instead. + * @deprecated 4.22.0 Use `email` property instead. + * + * @return string|null The primary email address if found, null otherwise */ public function primaryEmail() { From 52dc0ba7248630612ec9aeacdecf37b680df7e75 Mon Sep 17 00:00:00 2001 From: Braden Keith Date: Fri, 28 Mar 2025 14:10:42 -0400 Subject: [PATCH 13/26] Update deprecation notices in Organizations class to include version information and improve formatting --- lib/Organizations.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/Organizations.php b/lib/Organizations.php index dc62b78..283be2e 100644 --- a/lib/Organizations.php +++ b/lib/Organizations.php @@ -34,11 +34,11 @@ public function listOrganizations( ) { $organizationsPath = "organizations"; $params = [ - "limit" => $limit, - "before" => $before, - "after" => $after, - "domains" => $domains, - "order" => $order + "limit" => $limit, + "before" => $before, + "after" => $after, + "domains" => $domains, + "order" => $order ]; $response = Client::request( @@ -62,9 +62,9 @@ public function listOrganizations( * Create Organization. * * @param string $name The name of the Organization. - * @param null|array $domains [Deprecated] The domains of the Organization. Use domain_data instead. + * @param null|array $domains @deprecated 4.5.0 The domains of the Organization. Use domain_data instead. * @param null|array $domain_data The domains of the Organization. - * @param null|boolean $allowProfilesOutsideOrganization [Deprecated] If you need to allow sign-ins from + * @param null|boolean $allowProfilesOutsideOrganization @deprecated 4.5.0 If you need to allow sign-ins from * any email domain, contact support@workos.com. * @param null|string $idempotencyKey is a unique string that identifies a distinct organization * @param null|string $externalId The organization's external id @@ -86,7 +86,7 @@ public function createOrganization( $idempotencyKey ? $headers = array("Idempotency-Key: $idempotencyKey") : $headers = null; $organizationsPath = "organizations"; - $params = [ "name" => $name ]; + $params = ["name" => $name]; if (isset($domains)) { $params["domains"] = $domains; @@ -113,10 +113,10 @@ public function createOrganization( * Update Organization. * * @param string $organization An Organization identifier. - * @param null|array $domains [Deprecated] The domains of the Organization. Use domain_data instead. + * @param null|array $domains @deprecated 4.5.0 The domains of the Organization. Use domain_data instead. * @param null|array $domain_data The domains of the Organization. * @param null|string $name The name of the Organization. - * @param null|boolean $allowProfilesOutsideOrganization [Deprecated] If you need to allow sign-ins from + * @param null|boolean $allowProfilesOutsideOrganization @deprecated 4.5.0 If you need to allow sign-ins from * any email domain, contact support@workos.com. * @param null|string $stripeCustomerId The Stripe Customer ID of the Organization. * @param null|string $externalId The organization's external id @@ -136,7 +136,7 @@ public function updateOrganization( ) { $organizationsPath = "organizations/{$organization}"; - $params = [ "name" => $name ]; + $params = ["name" => $name]; if (isset($domains)) { $params["domains"] = $domains; From b25b57d8924f1b7f39df07c0222f4a751393a630 Mon Sep 17 00:00:00 2001 From: Braden Keith Date: Fri, 28 Mar 2025 14:53:23 -0400 Subject: [PATCH 14/26] Update doc blocks for deprecation notices --- lib/AuditLogs.php | 57 +++++++++++++++++++++--------------------- lib/MFA.php | 3 ++- lib/SSO.php | 2 +- lib/UserManagement.php | 2 ++ 4 files changed, 33 insertions(+), 31 deletions(-) diff --git a/lib/AuditLogs.php b/lib/AuditLogs.php index 40b0835..464e1ac 100644 --- a/lib/AuditLogs.php +++ b/lib/AuditLogs.php @@ -10,36 +10,35 @@ class AuditLogs { /** + * Creates an audit log event for an organization. * - * @param string $organizationId the unique identifier for the organization. - * @param array $event Associative array containing the keys detailed below - * string "action" Specific activity performed by the actor. REQUIRED. - * string "occurred_at" ISO-8601 datetime at which the event happened. REQUIRED. - * array "actor" Associative array describing Actor of the event. REQUIRED. - * KEYS: - * string "id" - REQUIRED - * string "name" - NOT REQUIRED - * string "type" - REQUIRED - * array "metadata" - Associative array ["Any Key" => "Any Value] - NOT REQUIRED - * array "targets" Targets of the event. Nested array as there can be multiple targets. REQUIRED - * KEYS: - * string "id" - REQUIRED - * string "name" - NOT REQUIRED - * string "type" - REQUIRED - * array "metadata" - Associative array ["Any Key" => "Any Value] - NOT REQUIRED - * array "context" Context of the event. REQUIRED. - * KEYS: - * string "location" - REQUIRED - * string "user_agent" - NOT REQUIRED - * int "version" Version of the event. Required if version is anything other than 1. NOT REQUIRED. - * array "metadata" Arbitrary key-value data containing information associated with the event. NOT REQUIRED - * @param string $idempotencyKey Unique key guaranteeing idempotency of events for 24 hours. + * @param string $organizationId The unique identifier for the organization. + * @param array $event An associative array with the following keys: + * - **action** (string, *required*): Specific activity performed by the actor. + * - **occurred_at** (string, *required*): ISO-8601 datetime when the event occurred. + * - **actor** (array, *required*): Associative array describing the actor. + * - **id** (string, *required*): Unique identifier for the actor. + * - **name** (string, *optional*): Name of the actor. + * - **type** (string, *required*): Type or role of the actor. + * - **metadata** (array, *optional*): Arbitrary key-value data. + * - **targets** (array, *required*): Array of associative arrays for each target. + * Each target includes: + * - **id** (string, *required*): Unique identifier for the target. + * - **name** (string, *optional*): Name of the target. + * - **type** (string, *required*): Type or category of the target. + * - **metadata** (array, *optional*): Arbitrary key-value data. + * - **context** (array, *required*): Associative array providing additional context. + * - **location** (string, *required*): Location associated with the event. + * - **user_agent** (string, *optional*): User agent string if applicable. + * - **version** (int, *optional*): Event version. Required if the version is not 1. + * - **metadata** (array, *optional*): Additional arbitrary key-value data for the event. + * + * @param string $idempotencyKey A unique key ensuring idempotency of events for 24 hours. * * @throws Exception\WorkOSException * - * @return Resource\AuditLogCreateEventStatus + * @return Resource\AuditLogCreateEventStatus */ - public function createEvent($organizationId, $event, $idempotencyKey = null) { $eventsPath = "audit_logs/events"; @@ -64,7 +63,7 @@ public function createEvent($organizationId, $event, $idempotencyKey = null) * @var null|string $rangeStart ISO-8601 Timestamp of the start of Export's the date range. * @var null|string $rangeEnd ISO-8601 Timestamp of the end of Export's the date range. * @var null|array $actions Actions that Audit Log Events will be filtered by. - * @var null|array $actors Actor names that Audit Log Events will be filtered by. + * @var null|array $actors Actor names that Audit Log Events will be filtered by. @deprecated 3.3.0 Use $actorNames instead. This method will be removed in a future major version. * @var null|array $targets Target types that Audit Log Events will be filtered by. * @var null|array $actorNames Actor names that Audit Log Events will be filtered by. * @var null|array $actorIds Actor IDs that Audit Log Events will be filtered by. @@ -79,9 +78,9 @@ public function createExport($organizationId, $rangeStart, $rangeEnd, $actions = $createExportPath = "audit_logs/exports"; $params = [ - "organization_id" => $organizationId, - "range_end" => $rangeEnd, - "range_start" => $rangeStart + "organization_id" => $organizationId, + "range_end" => $rangeEnd, + "range_start" => $rangeStart ]; if (!is_null($actions)) { diff --git a/lib/MFA.php b/lib/MFA.php index 771d5c3..abdb358 100644 --- a/lib/MFA.php +++ b/lib/MFA.php @@ -108,11 +108,12 @@ public function challengeFactor( /** + * @deprecated 1.12.0 Use `verifyChallenge` instead. This method will be removed in a future major version. * Verifies the one time password provided by the end-user. * * @param string $authenticationChallengeId - The ID of the authentication challenge that provided the user the verification code. * @param string $code - The verification code sent to and provided by the end user. - */ + */ public function verifyFactor( $authenticationChallengeId, diff --git a/lib/SSO.php b/lib/SSO.php index 746f7e0..096f581 100644 --- a/lib/SSO.php +++ b/lib/SSO.php @@ -13,7 +13,7 @@ class SSO /** * Generates an OAuth 2.0 authorization URL used to initiate the SSO flow with WorkOS. * - * @param null|string $domain Domain of the user that will be going through SSO + * @param null|string $domain Domain of the user that will be going through SSO @deprecated 1.5.0 Use $connection or $organization instead. * @param null|string $redirectUri URI to direct the user to upon successful completion of SSO * @param null|array $state Associative array containing state that will be returned from WorkOS as a json encoded string * @param null|string $provider Service provider that handles the identity of the user diff --git a/lib/UserManagement.php b/lib/UserManagement.php index f40bd88..e21eae6 100644 --- a/lib/UserManagement.php +++ b/lib/UserManagement.php @@ -1096,6 +1096,7 @@ public function createPasswordReset( } /** + * @deprecated 4.9.0 Use `createPasswordReset` instead. This method will be removed in a future major version. * Create Password Reset Email. * * @param string $email The email of the user that wishes to reset their password. @@ -1204,6 +1205,7 @@ public function createMagicAuth( } /** + * @deprecated 4.6.0 Use `createMagicAuth` instead. This method will be removed in a future major version. * Creates a one-time Magic Auth code and emails it to the user. * * @param string $email The email address the one-time code will be sent to. From 053ff559a919487c29082f9bb1ea8705080729e1 Mon Sep 17 00:00:00 2001 From: Braden Keith Date: Fri, 28 Mar 2025 14:54:24 -0400 Subject: [PATCH 15/26] Update tests to expect Role Slug --- tests/WorkOS/UserManagementTest.php | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/tests/WorkOS/UserManagementTest.php b/tests/WorkOS/UserManagementTest.php index 1b6ff72..9a2b74a 100644 --- a/tests/WorkOS/UserManagementTest.php +++ b/tests/WorkOS/UserManagementTest.php @@ -601,10 +601,10 @@ public function testCreatePasswordReset() { $path = "user_management/password_reset"; - $result = $this->passwordResetResponseFixture(); + $response = $this->passwordResetResponseFixture(); $params = [ - "email" => "someemail@test.com", + "email" => "someemail@test.com" ]; $this->mockRequest( @@ -613,7 +613,7 @@ public function testCreatePasswordReset() null, $params, true, - $result + $response ); $response = $this->userManagement->createPasswordReset( @@ -621,14 +621,14 @@ public function testCreatePasswordReset() ); $expected = $this->passwordResetFixture(); - - $this->assertSame($response->toArray(), $expected); + $this->assertSame($expected, $response->toArray()); } public function testSendPasswordResetEmail() { $path = "user_management/password_reset/send"; + // Mock the API request $responseCode = 200; $params = [ "email" => "test@test.com", @@ -647,6 +647,7 @@ public function testSendPasswordResetEmail() ); $response = $this->userManagement->sendPasswordResetEmail("test@test.com", "https://your-app.com/reset-password"); + // Test the functionality $this->assertSame(200, $responseCode); $this->assertSame($response, []); } @@ -1341,6 +1342,9 @@ private function organizationMembershipResponseFixture($status = "active") "id" => "om_01E4ZCR3C56J083X43JQXF3JK5", "user_id" => "user_01H7X1M4TZJN5N4HG4XXMA1234", "organization_id" => "org_01EHQMYV6MBK39QC5PZXHY59C3", + "role" => [ + "slug" => "admin", + ], "status" => $status, "created_at" => "2021-06-25T19:07:33.155Z", "updated_at" => "2021-06-25T19:07:33.155Z", @@ -1357,6 +1361,9 @@ private function organizationMembershipListResponseFixture() "id" => "om_01E4ZCR3C56J083X43JQXF3JK5", "user_id" => "user_01H7X1M4TZJN5N4HG4XXMA1234", "organization_id" => "org_01EHQMYV6MBK39QC5PZXHY59C3", + "role" => [ + "slug" => "admin", + ], "status" => "active", "created_at" => "2021-06-25T19:07:33.155Z", "updated_at" => "2021-06-25T19:07:33.155Z", @@ -1377,6 +1384,9 @@ private function organizationMembershipFixture() "id" => "om_01E4ZCR3C56J083X43JQXF3JK5", "userId" => "user_01H7X1M4TZJN5N4HG4XXMA1234", "organizationId" => "org_01EHQMYV6MBK39QC5PZXHY59C3", + "role" => [ + "slug" => "admin", + ], "status" => "active", "createdAt" => "2021-06-25T19:07:33.155Z", "updatedAt" => "2021-06-25T19:07:33.155Z", From d19ee7955f513ee0cc076ff5b9947d9d2b809fd3 Mon Sep 17 00:00:00 2001 From: Braden Keith Date: Fri, 28 Mar 2025 15:30:48 -0400 Subject: [PATCH 16/26] Refactor deprecation handling across multiple classes to use trigger_error for better error reporting. Update phpunit.xml to display details on deprecations and warnings. Enhance test cases to assert deprecation warnings for deprecated methods. --- lib/AuditLogs.php | 4 +- lib/MFA.php | 18 +++---- lib/SSO.php | 2 +- lib/UserManagement.php | 10 ++-- phpunit.xml | 6 ++- tests/TestHelper.php | 39 ++++++++++++++ tests/WorkOS/AuditLogsTest.php | 38 ++++++++----- tests/WorkOS/DirectorySyncTest.php | 6 +++ tests/WorkOS/MFATest.php | 35 +++++++----- tests/WorkOS/OrganizationsTest.php | 84 +++++++++++++++-------------- tests/WorkOS/PasswordlessTest.php | 6 +++ tests/WorkOS/PortalTest.php | 16 ++++-- tests/WorkOS/SSOTest.php | 33 ++++++++---- tests/WorkOS/UserManagementTest.php | 16 +++++- tests/WorkOS/WebhookTest.php | 58 ++++++++++++++++---- tests/WorkOS/WidgetsTest.php | 10 +++- 16 files changed, 267 insertions(+), 114 deletions(-) diff --git a/lib/AuditLogs.php b/lib/AuditLogs.php index 464e1ac..7e209ed 100644 --- a/lib/AuditLogs.php +++ b/lib/AuditLogs.php @@ -88,9 +88,9 @@ public function createExport($organizationId, $rangeStart, $rangeEnd, $actions = }; if (!is_null($actors)) { - $msg = "'actors' is deprecated. Please use 'actorNames' instead'"; + $msg = "'actors' is deprecated. Please use 'actorNames' instead"; - error_log($msg); + trigger_error($msg, E_USER_DEPRECATED); $params["actors"] = $actors; }; diff --git a/lib/MFA.php b/lib/MFA.php index abdb358..b568961 100644 --- a/lib/MFA.php +++ b/lib/MFA.php @@ -89,8 +89,8 @@ public function challengeFactor( $challengePath = "auth/factors/{$authenticationFactorId}/challenge"; $params = [ - "sms_template" => $smsTemplate - ]; + "sms_template" => $smsTemplate + ]; $response = Client::request( Client::METHOD_POST, @@ -126,13 +126,13 @@ public function verifyFactor( $msg = "'verifyFactor' is deprecated. Please use 'verifyChallenge' instead"; - error_log($msg); + trigger_error($msg, E_USER_DEPRECATED); $response = (new \WorkOS\MFA()) - ->verifyChallenge( - $authenticationChallengeId, - $code - ); + ->verifyChallenge( + $authenticationChallengeId, + $code + ); return $response; } @@ -158,8 +158,8 @@ public function verifyChallenge( $verifyPath = "auth/challenges/{$authenticationChallengeId}/verify"; $params = [ - "code" => $code - ]; + "code" => $code + ]; $response = Client::request( Client::METHOD_POST, diff --git a/lib/SSO.php b/lib/SSO.php index 096f581..f948512 100644 --- a/lib/SSO.php +++ b/lib/SSO.php @@ -48,7 +48,7 @@ public function getAuthorizationUrl( if (isset($domain)) { $msg = "Domain is being deprecated, please switch to using Connection or Organization ID"; - error_log($msg); + trigger_error($msg, E_USER_DEPRECATED); } $params = [ diff --git a/lib/UserManagement.php b/lib/UserManagement.php index e21eae6..1cca88d 100644 --- a/lib/UserManagement.php +++ b/lib/UserManagement.php @@ -1110,7 +1110,7 @@ public function sendPasswordResetEmail($email, $passwordResetUrl) { $msg = "'sendPasswordResetEmail' is deprecated. Please use 'createPasswordReset' instead. This method will be removed in a future major version."; - error_log($msg); + trigger_error($msg, E_USER_DEPRECATED); $path = "user_management/password_reset/send"; @@ -1224,7 +1224,7 @@ public function sendMagicAuthCode($email) $msg = "'sendMagicAuthCode' is deprecated. Please use 'createMagicAuth' instead. This method will be removed in a future major version."; - error_log($msg); + trigger_error($msg, E_USER_DEPRECATED); $response = Client::request( Client::METHOD_POST, @@ -1261,17 +1261,17 @@ public function getJwksUrl(string $clientId) * Returns the logout URL to end a user's session and redirect to your home page. * * @param string $sessionId The session ID of the user. - * @param string $return_to The URL to redirect to after the user logs out. + * @param string|null $return_to The URL to redirect to after the user logs out. * * @return string */ - public function getLogoutUrl(string $sessionId, string $return_to = null) + public function getLogoutUrl(string $sessionId, ?string $return_to = null) { if (!isset($sessionId) || empty($sessionId)) { throw new Exception\UnexpectedValueException("sessionId must not be empty"); } - $params = [ "session_id" => $sessionId ]; + $params = ["session_id" => $sessionId]; if ($return_to) { $params["return_to"] = $return_to; } diff --git a/phpunit.xml b/phpunit.xml index 43711ed..4d566a8 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,7 +1,9 @@ - + tests - + \ No newline at end of file diff --git a/tests/TestHelper.php b/tests/TestHelper.php index 2db39c2..52c1adc 100644 --- a/tests/TestHelper.php +++ b/tests/TestHelper.php @@ -2,9 +2,18 @@ namespace WorkOS; +use WorkOS\Client; + trait TestHelper { + /** + * @var \WorkOS\RequestClient\RequestClientInterface + */ protected $defaultRequestClient; + + /** + * @var \PHPUnit\Framework\MockObject\MockObject + */ protected $requestClientMock; protected function setUp(): void @@ -106,4 +115,34 @@ private function prepareRequestMock($method, $url, $headers, $params) static::identicalTo($params) ); } + + /** + * Asserts that a specific deprecation warning is triggered when callable is executed + * + * @param string $expected_warning The expected deprecation message + * @param callable $callable The function or method that should trigger the deprecation + * @return mixed The return value from the callable + */ + protected function assertDeprecationTriggered(string $expected_warning, callable $callable) + { + $caught = false; + + set_error_handler(function ($errno, $errstr) use ($expected_warning, &$caught) { + if ($errno === E_USER_DEPRECATED && $errstr === $expected_warning) { + $caught = true; + return true; + } + return false; + }); + + $result = $callable(); + + restore_error_handler(); + + if (!$caught) { + $this->fail('Expected deprecation warning was not triggered: ' . $expected_warning); + } + + return $result; + } } diff --git a/tests/WorkOS/AuditLogsTest.php b/tests/WorkOS/AuditLogsTest.php index 757b400..8cbfd82 100644 --- a/tests/WorkOS/AuditLogsTest.php +++ b/tests/WorkOS/AuditLogsTest.php @@ -2,10 +2,16 @@ namespace WorkOS; +use WorkOS\AuditLogs; use PHPUnit\Framework\TestCase; class AuditLogsTest extends TestCase { + /** + * @var AuditLogs + */ + protected $al; + use TestHelper { setUp as protected traitSetUp; } @@ -25,22 +31,23 @@ public function testCreateEvent() $idempotencyKey = null; $organizationId = "org_123"; $auditLogEvent = - [ - "action" => "document.updated", - "occurred_at" => time(), - "version" => 1, - "actor" => - [ - "Id" => "user_123", - "Type" => "user", - "Name" => "User", - ], - "targets" => [ + "action" => "document.updated", + "occurred_at" => time(), + "version" => 1, + "actor" => + [ + "Id" => "user_123", + "Type" => "user", + "Name" => "User", + ], + "targets" => + [ "id" => "team_123", "type" => "team", "name" => "team", - ]]; + ] + ]; $params = [ "organization_id" => $organizationId, "event" => $auditLogEvent @@ -60,7 +67,6 @@ public function testCreateEvent() true, $result ); - $eventStatus = $this->al->createEvent($organizationId, $auditLogEvent); $eventFixture = $this->createEventFixture(); @@ -105,7 +111,11 @@ public function testCreateExport() $result ); - $auditLogExport = $this->al->createExport($organizationId, $rangeStart, $rangeEnd, $actions, $actors, $targets, $actorNames, $actorIds); + $auditLogExport = $this->assertDeprecationTriggered( + "'actors' is deprecated. Please use 'actorNames' instead", + fn() => $this->al->createExport($organizationId, $rangeStart, $rangeEnd, $actions, $actors, $targets, $actorNames, $actorIds) + ); + $exportFixture = $this->createExportFixture(); $this->assertSame($exportFixture, $auditLogExport->toArray()); diff --git a/tests/WorkOS/DirectorySyncTest.php b/tests/WorkOS/DirectorySyncTest.php index c75dcd9..172c2a2 100644 --- a/tests/WorkOS/DirectorySyncTest.php +++ b/tests/WorkOS/DirectorySyncTest.php @@ -2,10 +2,16 @@ namespace WorkOS; +use WorkOS\DirectorySync; use PHPUnit\Framework\TestCase; class DirectorySyncTest extends TestCase { + /** + * @var DirectorySync + */ + protected $ds; + use TestHelper { setUp as traitSetUp; } diff --git a/tests/WorkOS/MFATest.php b/tests/WorkOS/MFATest.php index 423f38c..0d6746c 100644 --- a/tests/WorkOS/MFATest.php +++ b/tests/WorkOS/MFATest.php @@ -2,10 +2,16 @@ namespace WorkOS; +use WorkOS\MFA; use PHPUnit\Framework\TestCase; class MFATest extends TestCase { + /** + * @var MFA + */ + protected $mfa; + use TestHelper { setUp as traitSetUp; } @@ -126,7 +132,10 @@ public function testVerifyFactor() $result ); - $verifyFactor = $this->mfa->verifyFactor($authenticationChallengeId, $code); + $verifyFactor = $this->assertDeprecationTriggered( + "'verifyFactor' is deprecated. Please use 'verifyChallenge' instead", + fn() => $this->mfa->verifyFactor($authenticationChallengeId, $code) + ); $verifyFactorResponseFixture = $this->verifyFactorFixture(); $this->assertSame($verifyFactorResponseFixture, $verifyFactor->toArray()); @@ -244,8 +253,8 @@ private function enrollFactorSmsResponseFixture() "updated_at" => "2022-03-08T23:12:20.157Z", "type" => "sms", "sms" => [ - "phone_number" => "+7208675309" - ] + "phone_number" => "+7208675309" + ] ]); } @@ -258,8 +267,8 @@ private function enrollFactorSmsFixture() "updatedAt" => "2022-03-08T23:12:20.157Z", "type" => "sms", "sms" => [ - "phone_number" => "+7208675309" - ] + "phone_number" => "+7208675309" + ] ]; } @@ -298,8 +307,8 @@ private function verifyFactorResponseFixture() "expires_at" => "2022-02-15T15:36:53.279Z", "authentication_factor_id" => "auth_factor_01FXNWW32G7F3MG8MYK5D1HJJM", ], - "valid" => "true" - ]); + "valid" => "true" + ]); } private function verifyFactorFixture() @@ -313,8 +322,8 @@ private function verifyFactorFixture() "expires_at" => "2022-02-15T15:36:53.279Z", "authentication_factor_id" => "auth_factor_01FXNWW32G7F3MG8MYK5D1HJJM", ], - "valid" => "true" - ]; + "valid" => "true" + ]; } private function verifyChallengeResponseFixture() @@ -328,8 +337,8 @@ private function verifyChallengeResponseFixture() "expires_at" => "2022-02-15T15:36:53.279Z", "authentication_factor_id" => "auth_factor_01FXNWW32G7F3MG8MYK5D1HJJM", ], - "valid" => "true" - ]); + "valid" => "true" + ]); } private function verifyChallengeFixture() @@ -343,8 +352,8 @@ private function verifyChallengeFixture() "expires_at" => "2022-02-15T15:36:53.279Z", "authentication_factor_id" => "auth_factor_01FXNWW32G7F3MG8MYK5D1HJJM", ], - "valid" => "true" - ]; + "valid" => "true" + ]; } private function getFactorResponseFixture() diff --git a/tests/WorkOS/OrganizationsTest.php b/tests/WorkOS/OrganizationsTest.php index 89bbf42..13d6818 100644 --- a/tests/WorkOS/OrganizationsTest.php +++ b/tests/WorkOS/OrganizationsTest.php @@ -2,10 +2,16 @@ namespace WorkOS; +use WorkOS\Organizations; use PHPUnit\Framework\TestCase; class OrganizationsTest extends TestCase { + /** + * @var Organizations + */ + protected $organizations; + use TestHelper { setUp as protected traitSetUp; } @@ -217,49 +223,49 @@ private function organizationsResponseFixture() "object" => "list", "data" => [ [ - "object" => "organization", - "id" => "org_01EHQMYV6MBK39QC5PZXHY59C3", - "name" => "Organization Name", - "allow_profiles_outside_organization" => false, - "domains" => [ - [ - "object" => "organization_domain", - "id" => "org_domain_01EHQMYV71XT8H31WE5HF8YK4A", - "domain" => "example.com" - ] - ], - "external_id" => null, - "metadata" => [] + "object" => "organization", + "id" => "org_01EHQMYV6MBK39QC5PZXHY59C3", + "name" => "Organization Name", + "allow_profiles_outside_organization" => false, + "domains" => [ + [ + "object" => "organization_domain", + "id" => "org_domain_01EHQMYV71XT8H31WE5HF8YK4A", + "domain" => "example.com" + ] + ], + "external_id" => null, + "metadata" => [] ], [ - "object" => "organization", - "id" => "org_01EHQMVDTC2GRAHFCCRNTSKH46", - "name" => "example2.com", - "allow_profiles_outside_organization" => false, - "domains" => [ - [ - "object" => "organization_domain", - "id" => "org_domain_01EHQMVDTZVA27PK614ME4YK7V", - "domain" => "example2.com" - ] - ], - "external_id" => null, - "metadata" => [] + "object" => "organization", + "id" => "org_01EHQMVDTC2GRAHFCCRNTSKH46", + "name" => "example2.com", + "allow_profiles_outside_organization" => false, + "domains" => [ + [ + "object" => "organization_domain", + "id" => "org_domain_01EHQMVDTZVA27PK614ME4YK7V", + "domain" => "example2.com" + ] + ], + "external_id" => null, + "metadata" => [] ], [ - "object" => "organization", - "id" => "org_01EGP9Z6RY2J6YE0ZV57CGEXV2", - "name" => "example5.com", - "allow_profiles_outside_organization" => false, - "domains" => [ - [ - "object" => "organization_domain", - "id" => "org_domain_01EGP9Z6S6HVQ5CPD152GJBEA5", - "domain" => "example5.com" - ] - ], - "external_id" => null, - "metadata" => [] + "object" => "organization", + "id" => "org_01EGP9Z6RY2J6YE0ZV57CGEXV2", + "name" => "example5.com", + "allow_profiles_outside_organization" => false, + "domains" => [ + [ + "object" => "organization_domain", + "id" => "org_domain_01EGP9Z6S6HVQ5CPD152GJBEA5", + "domain" => "example5.com" + ] + ], + "external_id" => null, + "metadata" => [] ] ], "list_metadata" => [ diff --git a/tests/WorkOS/PasswordlessTest.php b/tests/WorkOS/PasswordlessTest.php index 11e03f0..abcd904 100644 --- a/tests/WorkOS/PasswordlessTest.php +++ b/tests/WorkOS/PasswordlessTest.php @@ -2,10 +2,16 @@ namespace WorkOS; +use WorkOS\Passwordless; use PHPUnit\Framework\TestCase; class PasswordlessTest extends TestCase { + /** + * @var Passwordless + */ + protected $passwordless; + use TestHelper { setUp as traitSetUp; } diff --git a/tests/WorkOS/PortalTest.php b/tests/WorkOS/PortalTest.php index d0b1b58..3d3eb45 100644 --- a/tests/WorkOS/PortalTest.php +++ b/tests/WorkOS/PortalTest.php @@ -2,10 +2,16 @@ namespace WorkOS; +use WorkOS\Portal; use PHPUnit\Framework\TestCase; class PortalTest extends TestCase { + /** + * @var Portal + */ + protected $portal; + use TestHelper { setUp as protected traitSetUp; } @@ -15,7 +21,7 @@ protected function setUp(): void $this->traitSetUp(); $this->withApiKey(); - $this->ap = new Portal(); + $this->portal = new Portal(); } public function testGenerateLinkSSO() @@ -42,7 +48,7 @@ public function testGenerateLinkSSO() $expectation = "https://id.workos.com/portal/launch?secret=secret"; - $response = $this->ap->generateLink("org_01EHZNVPK3SFK441A1RGBFSHRT", "sso"); + $response = $this->portal->generateLink("org_01EHZNVPK3SFK441A1RGBFSHRT", "sso"); $this->assertSame($expectation, $response->link); } @@ -70,7 +76,7 @@ public function testGenerateLinkDSync() $expectation = "https://id.workos.com/portal/launch?secret=secret"; - $response = $this->ap->generateLink("org_01EHZNVPK3SFK441A1RGBFSHRT", "dsync"); + $response = $this->portal->generateLink("org_01EHZNVPK3SFK441A1RGBFSHRT", "dsync"); $this->assertSame($expectation, $response->link); } @@ -98,7 +104,7 @@ public function testGenerateLinkAuditLogs() $expectation = "https://id.workos.com/portal/launch?secret=secret"; - $response = $this->ap->generateLink("org_01EHZNVPK3SFK441A1RGBFSHRT", "audit_logs"); + $response = $this->portal->generateLink("org_01EHZNVPK3SFK441A1RGBFSHRT", "audit_logs"); $this->assertSame($expectation, $response->link); } @@ -126,7 +132,7 @@ public function testGenerateLinkLogStreams() $expectation = "https://id.workos.com/portal/launch?secret=secret"; - $response = $this->ap->generateLink("org_01EHZNVPK3SFK441A1RGBFSHRT", "log_streams"); + $response = $this->portal->generateLink("org_01EHZNVPK3SFK441A1RGBFSHRT", "log_streams"); $this->assertSame($expectation, $response->link); } diff --git a/tests/WorkOS/SSOTest.php b/tests/WorkOS/SSOTest.php index 9baccda..d5da134 100644 --- a/tests/WorkOS/SSOTest.php +++ b/tests/WorkOS/SSOTest.php @@ -2,12 +2,18 @@ namespace WorkOS; -use PHPUnit\Framework\Attributes\DataProvider; +use WorkOS\SSO; use PHPUnit\Framework\TestCase; use WorkOS\Resource\RoleResponse; +use PHPUnit\Framework\Attributes\DataProvider; class SSOTest extends TestCase { + /** + * @var SSO + */ + protected $sso; + use TestHelper { setUp as traitSetUp; } @@ -37,7 +43,6 @@ public function testAuthorizationURLExpectedParams( "client_id" => WorkOS::getClientId(), "response_type" => "code" ]; - if ($domain) { $expectedParams["domain"] = $domain; } @@ -70,7 +75,7 @@ public function testAuthorizationURLExpectedParams( $expectedParams["login_hint"] = $loginHint; } - $authorizationUrl = $this->sso->getAuthorizationUrl( + $fn = fn() => $this->sso->getAuthorizationUrl( $domain, $redirectUri, $state, @@ -80,6 +85,16 @@ public function testAuthorizationURLExpectedParams( $domainHint, $loginHint ); + + if ($domain) { + $authorizationUrl = $this->assertDeprecationTriggered( + "Domain is being deprecated, please switch to using Connection or Organization ID", + $fn + ); + } else { + $authorizationUrl = $fn(); + } + $paramsString = \parse_url($authorizationUrl, \PHP_URL_QUERY); \parse_str($paramsString, $paramsArray); @@ -260,10 +275,10 @@ private function connectionFixture() return [ "id" => "conn_01E0CG2C820RP4VS50PRJF8YPX", "domains" => [ - [ - "id" => "conn_dom_01E2GCC7Q3KCNEFA2BW9MXR4T5", - "domain" => "workos.com" - ] + [ + "id" => "conn_dom_01E2GCC7Q3KCNEFA2BW9MXR4T5", + "domain" => "workos.com" + ] ], "state" => "active", "status" => "linked", @@ -300,8 +315,8 @@ private function connectionsResponseFixture() "id" => "conn_01E0CG2C820RP4VS50PRJF8YPX", "domains" => [ [ - "id" => "conn_dom_01E2GCC7Q3KCNEFA2BW9MXR4T5", - "domain" => "workos.com" + "id" => "conn_dom_01E2GCC7Q3KCNEFA2BW9MXR4T5", + "domain" => "workos.com" ] ], "state" => "active", diff --git a/tests/WorkOS/UserManagementTest.php b/tests/WorkOS/UserManagementTest.php index 9a2b74a..74382ae 100644 --- a/tests/WorkOS/UserManagementTest.php +++ b/tests/WorkOS/UserManagementTest.php @@ -2,11 +2,17 @@ namespace WorkOS; -use PHPUnit\Framework\Attributes\DataProvider; +use WorkOS\UserManagement; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\Attributes\DataProvider; class UserManagementTest extends TestCase { + /** + * @var UserManagement + */ + protected $userManagement; + use TestHelper { setUp as traitSetUp; } @@ -627,6 +633,7 @@ public function testCreatePasswordReset() public function testSendPasswordResetEmail() { $path = "user_management/password_reset/send"; + $msg = "'sendPasswordResetEmail' is deprecated. Please use 'createPasswordReset' instead. This method will be removed in a future major version."; // Mock the API request $responseCode = 200; @@ -646,7 +653,12 @@ public function testSendPasswordResetEmail() $responseCode ); - $response = $this->userManagement->sendPasswordResetEmail("test@test.com", "https://your-app.com/reset-password"); + // Call the deprecated method + $response = $this->assertDeprecationTriggered( + $msg, + fn() => $this->userManagement->sendPasswordResetEmail("test@test.com", "https://your-app.com/reset-password") + ); + // Test the functionality $this->assertSame(200, $responseCode); $this->assertSame($response, []); diff --git a/tests/WorkOS/WebhookTest.php b/tests/WorkOS/WebhookTest.php index 298bab0..ff3bd4a 100644 --- a/tests/WorkOS/WebhookTest.php +++ b/tests/WorkOS/WebhookTest.php @@ -2,10 +2,46 @@ namespace WorkOS; +use WorkOS\Webhook; use PHPUnit\Framework\TestCase; class WebhookTest extends TestCase { + /** + * @var Webhook + */ + protected $webhook; + + /** + * @var string + */ + protected $payload; + + /** + * @var string + */ + protected $secret; + + /** + * @var int + */ + protected $tolerance; + + /** + * @var int + */ + protected $time; + + /** + * @var string + */ + protected $expectedSignature; + + /** + * @var string + */ + protected $sigHeader; + use TestHelper { setUp as protected traitSetUp; } @@ -15,13 +51,13 @@ protected function setUp(): void $this->traitSetUp(); $this->withApiKey(); - $this->ap = new Webhook(); + $this->webhook = new Webhook(); $this->payload = '{"id":"wh_01FGCG6SDYCT5XWZT9CDW0XEB8","data":{"id":"conn_01EHWNC0FCBHZ3BJ7EGKYXK0E6","name":"Foo Corp\'s Connection","state":"active","object":"connection","domains":[{"id":"conn_domain_01EHWNFTAFCF3CQAE5A9Q0P1YB","domain":"foo-corp.com","object":"connection_domain"}],"connection_type":"OktaSAML","organization_id":"org_01EHWNCE74X7JSDV0X3SZ3KJNY"},"event":"connection.activated"}'; $this->secret = 'secret'; $this->tolerance = 180; $this->time = time(); - $decodedBody = utf8_decode($this->payload); + $decodedBody = mb_convert_encoding($this->payload, 'ISO-8859-1', 'UTF-8'); $signedPayload = $this->time . "." . $decodedBody; $this->expectedSignature = hash_hmac("sha256", $signedPayload, $this->secret, false); $this->sigHeader = 't=' . $this->time . ', v1=' . $this->expectedSignature; @@ -29,11 +65,11 @@ protected function setUp(): void public function testConstructEventWebhook() { - $result = $this->generateConnectionFixture(); + $this->generateConnectionFixture(); $expectation = $this->payload; - $response = $this->ap->constructEvent($this->sigHeader, $this->payload, $this->secret, $this->tolerance); + $response = $this->webhook->constructEvent($this->sigHeader, $this->payload, $this->secret, $this->tolerance); $this->assertSame($expectation, json_encode($response)); } @@ -41,7 +77,7 @@ public function testVerifyHeaderWebhook() { $expectation = 'pass'; - $response = $this->ap->verifyHeader($this->sigHeader, $this->payload, $this->secret, $this->tolerance); + $response = $this->webhook->verifyHeader($this->sigHeader, $this->payload, $this->secret, $this->tolerance); $this->assertSame($expectation, $response); } @@ -49,7 +85,7 @@ public function testGetTimeStamp() { $expectation = strval($this->time); - $response = $this->ap->getTimeStamp($this->sigHeader); + $response = $this->webhook->getTimeStamp($this->sigHeader); $this->assertSame($expectation, $response); } @@ -57,7 +93,7 @@ public function testGetSignature() { $expectation = $this->expectedSignature; - $response = $this->ap->getSignature($this->sigHeader); + $response = $this->webhook->getSignature($this->sigHeader); $this->assertSame($expectation, $response); } @@ -68,10 +104,10 @@ private function generateConnectionFixture() return json_encode([ "id" => "conn_01E0CG2C820RP4VS50PRJF8YPX", "domains" => [ - [ - "id" => "conn_dom_01E2GCC7Q3KCNEFA2BW9MXR4T5", - "domain" => "workos.com" - ] + [ + "id" => "conn_dom_01E2GCC7Q3KCNEFA2BW9MXR4T5", + "domain" => "workos.com" + ] ], "state" => "active", "status" => "linked", diff --git a/tests/WorkOS/WidgetsTest.php b/tests/WorkOS/WidgetsTest.php index 6120f3b..bdd16af 100644 --- a/tests/WorkOS/WidgetsTest.php +++ b/tests/WorkOS/WidgetsTest.php @@ -2,10 +2,16 @@ namespace WorkOS; +use WorkOS\Widgets; use PHPUnit\Framework\TestCase; class WidgetsTest extends TestCase { + /** + * @var Widgets + */ + protected $widgets; + use TestHelper { setUp as protected traitSetUp; } @@ -15,7 +21,7 @@ protected function setUp(): void $this->traitSetUp(); $this->withApiKey(); - $this->ap = new Widgets(); + $this->widgets = new Widgets(); } public function testGenerateLinkSSO() @@ -41,7 +47,7 @@ public function testGenerateLinkSSO() $expectation = "abc123456"; - $response = $this->ap->getToken("org_01EHZNVPK3SFK441A1RGBFSHRT", "user_01EHZNVPK3SFK441A1RGBFSHRT", ["widgets:users-table:manage"]); + $response = $this->widgets->getToken("org_01EHZNVPK3SFK441A1RGBFSHRT", "user_01EHZNVPK3SFK441A1RGBFSHRT", ["widgets:users-table:manage"]); $this->assertSame($expectation, $response->token); } From 2646568741d335c2797685b6a81d1982569be686 Mon Sep 17 00:00:00 2001 From: Braden Keith Date: Fri, 28 Mar 2025 16:42:47 -0400 Subject: [PATCH 17/26] Implement deprecation warnings for 'domains' and 'allowProfilesOutsideOrganization' in Organizations class, and 'primaryEmail' in DirectoryUser class. Update tests to assert deprecation messages for these changes. --- lib/Organizations.php | 16 ++++++++++++++++ lib/Resource/DirectoryUser.php | 4 ++++ tests/WorkOS/DirectorySyncTest.php | 10 ++++++++-- tests/WorkOS/OrganizationsTest.php | 15 ++++++++++++--- 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/lib/Organizations.php b/lib/Organizations.php index 283be2e..6b509bb 100644 --- a/lib/Organizations.php +++ b/lib/Organizations.php @@ -89,12 +89,20 @@ public function createOrganization( $params = ["name" => $name]; if (isset($domains)) { + $msg = "'domains' is deprecated. Please use 'domain_data' instead"; + + trigger_error($msg, E_USER_DEPRECATED); + $params["domains"] = $domains; } if (isset($domain_data)) { $params["domain_data"] = $domain_data; } if (isset($allowProfilesOutsideOrganization)) { + $msg = "'allowProfilesOutsideOrganization' is deprecated. Please use 'allow_profiles_outside_organization' instead"; + + trigger_error($msg, E_USER_DEPRECATED); + $params["allow_profiles_outside_organization"] = $allowProfilesOutsideOrganization; } if (isset($externalId)) { @@ -139,12 +147,20 @@ public function updateOrganization( $params = ["name" => $name]; if (isset($domains)) { + $msg = "'domains' is deprecated. Please use 'domain_data' instead"; + + trigger_error($msg, E_USER_DEPRECATED); + $params["domains"] = $domains; } if (isset($domain_data)) { $params["domain_data"] = $domain_data; } if (isset($allowProfilesOutsideOrganization)) { + $msg = "'allowProfilesOutsideOrganization' is deprecated. Please use 'allow_profiles_outside_organization' instead"; + + trigger_error($msg, E_USER_DEPRECATED); + $params["allow_profiles_outside_organization"] = $allowProfilesOutsideOrganization; } if (isset($stripeCustomerId)) { diff --git a/lib/Resource/DirectoryUser.php b/lib/Resource/DirectoryUser.php index 78be8ec..319f916 100644 --- a/lib/Resource/DirectoryUser.php +++ b/lib/Resource/DirectoryUser.php @@ -65,6 +65,10 @@ class DirectoryUser extends BaseWorkOSResource */ public function primaryEmail() { + $msg = "'primaryEmail' is deprecated. Please use 'email' instead"; + + trigger_error($msg, E_USER_DEPRECATED); + $response = $this; if (count($response->raw["emails"]) == 0) { diff --git a/tests/WorkOS/DirectorySyncTest.php b/tests/WorkOS/DirectorySyncTest.php index 172c2a2..251750b 100644 --- a/tests/WorkOS/DirectorySyncTest.php +++ b/tests/WorkOS/DirectorySyncTest.php @@ -164,7 +164,10 @@ public function testGetUserPrimaryEmail() ); $user = $this->ds->getUser($directoryUser); - $userEmail = $user->primaryEmail(); + $userEmail = $this->assertDeprecationTriggered( + "'primaryEmail' is deprecated. Please use 'email' instead", + fn() => $user->primaryEmail(), + ); $this->assertSame($userEmail, $expectedEmail); } @@ -186,7 +189,10 @@ public function testGetUserPrimaryEmailNoPrimaryEmail() ); $user = $this->ds->getUser($directoryUser); - $userEmail = $user->primaryEmail(); + $userEmail = $this->assertDeprecationTriggered( + "'primaryEmail' is deprecated. Please use 'email' instead", + fn() => $user->primaryEmail(), + ); $this->assertSame($userEmail, $expectedEmail); } diff --git a/tests/WorkOS/OrganizationsTest.php b/tests/WorkOS/OrganizationsTest.php index 13d6818..f17ce55 100644 --- a/tests/WorkOS/OrganizationsTest.php +++ b/tests/WorkOS/OrganizationsTest.php @@ -46,7 +46,10 @@ public function testCreateOrganizationWithDomains() $organization = $this->organizationFixture(); - $response = $this->organizations->createOrganization("Organization Name", array("example.com")); + $response = $this->assertDeprecationTriggered( + "'domains' is deprecated. Please use 'domain_data' instead", + fn() => $this->organizations->createOrganization("Organization Name", array("example.com")), + ); $this->assertSame($organization, $response->toArray()); } @@ -140,8 +143,14 @@ public function testCreateOrganizationSendsIdempotencyKey() $result ); - $response = $this->organizations->createOrganization("Organization Name", array("example.com"), null, $idempotencyKey); - $response2 = $this->organizations->createOrganization("Organization Name", array("example.com"), null, $idempotencyKey); + $response = $this->assertDeprecationTriggered( + "'domains' is deprecated. Please use 'domain_data' instead", + fn() => $this->organizations->createOrganization("Organization Name", array("example.com"), null, $idempotencyKey), + ); + $response2 = $this->assertDeprecationTriggered( + "'domains' is deprecated. Please use 'domain_data' instead", + fn() => $this->organizations->createOrganization("Organization Name", array("example.com"), null, $idempotencyKey), + ); $this->assertSame($response2->toArray()["id"], $response->toArray()["id"]); } From a621976271fbf8db78fe0f981b3d254192a02f60 Mon Sep 17 00:00:00 2001 From: Braden Keith Date: Fri, 28 Mar 2025 16:48:54 -0400 Subject: [PATCH 18/26] Update deprecation notice for 'allowProfilesOutsideOrganization' in Organizations class to provide clearer guidance for users. Add a test to assert the deprecation warning is triggered correctly. --- lib/Organizations.php | 4 ++-- tests/WorkOS/OrganizationsTest.php | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/lib/Organizations.php b/lib/Organizations.php index 6b509bb..723ee7c 100644 --- a/lib/Organizations.php +++ b/lib/Organizations.php @@ -99,7 +99,7 @@ public function createOrganization( $params["domain_data"] = $domain_data; } if (isset($allowProfilesOutsideOrganization)) { - $msg = "'allowProfilesOutsideOrganization' is deprecated. Please use 'allow_profiles_outside_organization' instead"; + $msg = "'allowProfilesOutsideOrganization' is deprecated. If you need to allow sign-ins from any email domain, contact support@workos.com."; trigger_error($msg, E_USER_DEPRECATED); @@ -157,7 +157,7 @@ public function updateOrganization( $params["domain_data"] = $domain_data; } if (isset($allowProfilesOutsideOrganization)) { - $msg = "'allowProfilesOutsideOrganization' is deprecated. Please use 'allow_profiles_outside_organization' instead"; + $msg = "'allowProfilesOutsideOrganization' is deprecated. If you need to allow sign-ins from any email domain, contact support@workos.com."; trigger_error($msg, E_USER_DEPRECATED); diff --git a/tests/WorkOS/OrganizationsTest.php b/tests/WorkOS/OrganizationsTest.php index f17ce55..58829dd 100644 --- a/tests/WorkOS/OrganizationsTest.php +++ b/tests/WorkOS/OrganizationsTest.php @@ -88,6 +88,32 @@ public function testCreateOrganizationWithDomainData() $this->assertSame($organization, $response->toArray()); } + public function testCreateOrganizationWithAllowProfilesOutsideOrganizationDeprecationNotice() + { + $organizationsPath = "organizations"; + + $result = $this->createOrganizationResponseFixture(); + + $params = [ + "name" => "Organization Name", + "allow_profiles_outside_organization" => true, + ]; + + $this->mockRequest( + Client::METHOD_POST, + $organizationsPath, + null, + $params, + true, + $result + ); + + $this->assertDeprecationTriggered( + "'allowProfilesOutsideOrganization' is deprecated. If you need to allow sign-ins from any email domain, contact support@workos.com.", + fn() => $this->organizations->createOrganization("Organization Name", null, true), + ); + } + public function testUpdateOrganizationWithDomainData() { $organizationsPath = "organizations/org_01EHQMYV6MBK39QC5PZXHY59C3"; From a445265067bf9b6ee7f1a677df565271e25ea405 Mon Sep 17 00:00:00 2001 From: Braden Keith Date: Fri, 28 Mar 2025 16:57:40 -0400 Subject: [PATCH 19/26] Update deprecation messages across multiple classes to ensure consistent formatting and clarity. Adjust tests to reflect the updated messages for 'actors', 'primaryEmail', 'domains', and other parameters, enhancing overall error reporting. --- lib/AuditLogs.php | 2 +- lib/MFA.php | 12 ++++++------ lib/Organizations.php | 4 ++-- lib/Resource/BaseWorkOSResource.php | 8 +++----- lib/Resource/DirectoryUser.php | 2 +- lib/SSO.php | 4 ++-- lib/UserManagement.php | 4 ++-- lib/WorkOS.php | 4 ++-- tests/WorkOS/AuditLogsTest.php | 2 +- tests/WorkOS/DirectorySyncTest.php | 4 ++-- tests/WorkOS/MFATest.php | 2 +- tests/WorkOS/OrganizationsTest.php | 6 +++--- tests/WorkOS/SSOTest.php | 2 +- tests/WorkOS/UserManagementTest.php | 3 +-- 14 files changed, 28 insertions(+), 31 deletions(-) diff --git a/lib/AuditLogs.php b/lib/AuditLogs.php index 7e209ed..852c10e 100644 --- a/lib/AuditLogs.php +++ b/lib/AuditLogs.php @@ -88,7 +88,7 @@ public function createExport($organizationId, $rangeStart, $rangeEnd, $actions = }; if (!is_null($actors)) { - $msg = "'actors' is deprecated. Please use 'actorNames' instead"; + $msg = "'actors' is deprecated. Please use 'actorNames' instead."; trigger_error($msg, E_USER_DEPRECATED); diff --git a/lib/MFA.php b/lib/MFA.php index b568961..2cd6443 100644 --- a/lib/MFA.php +++ b/lib/MFA.php @@ -28,12 +28,12 @@ public function enrollFactor( $enrollPath = "auth/factors/enroll"; if (!isset($type)) { - $msg = "Incomplete arguments: Need to specify a type of factor"; + $msg = "Incomplete arguments: Need to specify a type of factor."; throw new Exception\UnexpectedValueException($msg); } if ($type != "sms" && $type != "totp") { - $msg = "Type Parameter must either be 'sms' or 'totp'"; + $msg = "Type Parameter must either be 'sms' or 'totp'."; throw new Exception\UnexpectedValueException($msg); } @@ -82,7 +82,7 @@ public function challengeFactor( $smsTemplate = null ) { if (!isset($authenticationFactorId)) { - $msg = "Incomplete arguments: 'authentication_factor_id' is a required parameter"; + $msg = "Incomplete arguments: 'authentication_factor_id' is a required parameter."; throw new Exception\UnexpectedValueException($msg); } @@ -120,11 +120,11 @@ public function verifyFactor( $code ) { if (!isset($authenticationChallengeId) || !isset($code)) { - $msg = "Incomplete arguments: 'authenticationChallengeId' and 'code' are required parameters"; + $msg = "Incomplete arguments: 'authenticationChallengeId' and 'code' are required parameters."; throw new Exception\UnexpectedValueException($msg); } - $msg = "'verifyFactor' is deprecated. Please use 'verifyChallenge' instead"; + $msg = "'verifyFactor' is deprecated. Please use 'verifyChallenge' instead."; trigger_error($msg, E_USER_DEPRECATED); @@ -151,7 +151,7 @@ public function verifyChallenge( $code ) { if (!isset($authenticationChallengeId) || !isset($code)) { - $msg = "Incomplete arguments: 'authenticationChallengeId' and 'code' are required parameters"; + $msg = "Incomplete arguments: 'authenticationChallengeId' and 'code' are required parameters."; throw new Exception\UnexpectedValueException($msg); } diff --git a/lib/Organizations.php b/lib/Organizations.php index 723ee7c..992d781 100644 --- a/lib/Organizations.php +++ b/lib/Organizations.php @@ -89,7 +89,7 @@ public function createOrganization( $params = ["name" => $name]; if (isset($domains)) { - $msg = "'domains' is deprecated. Please use 'domain_data' instead"; + $msg = "'domains' is deprecated. Please use 'domain_data' instead."; trigger_error($msg, E_USER_DEPRECATED); @@ -147,7 +147,7 @@ public function updateOrganization( $params = ["name" => $name]; if (isset($domains)) { - $msg = "'domains' is deprecated. Please use 'domain_data' instead"; + $msg = "'domains' is deprecated. Please use 'domain_data' instead."; trigger_error($msg, E_USER_DEPRECATED); diff --git a/lib/Resource/BaseWorkOSResource.php b/lib/Resource/BaseWorkOSResource.php index 71b0a48..1ea3fc9 100644 --- a/lib/Resource/BaseWorkOSResource.php +++ b/lib/Resource/BaseWorkOSResource.php @@ -30,9 +30,7 @@ class BaseWorkOSResource */ public $raw; - private function __construct() - { - } + private function __construct() {} /** * Creates a Resource from a Response. @@ -76,7 +74,7 @@ public function __set($key, $value) $this->values[$key] = $value; } - $msg = "{$key} does not exist on " . static::class; + $msg = "{$key} does not exist on " . static::class . "."; throw new \WorkOS\Exception\UnexpectedValueException($msg); } @@ -100,7 +98,7 @@ public function &__get($key) return $this->raw[$key]; } - $msg = "{$key} does not exist on " . static::class; + $msg = "{$key} does not exist on " . static::class . "."; throw new \WorkOS\Exception\UnexpectedValueException($msg); } } diff --git a/lib/Resource/DirectoryUser.php b/lib/Resource/DirectoryUser.php index 319f916..37a43a5 100644 --- a/lib/Resource/DirectoryUser.php +++ b/lib/Resource/DirectoryUser.php @@ -65,7 +65,7 @@ class DirectoryUser extends BaseWorkOSResource */ public function primaryEmail() { - $msg = "'primaryEmail' is deprecated. Please use 'email' instead"; + $msg = "'primaryEmail' is deprecated. Please use 'email' instead."; trigger_error($msg, E_USER_DEPRECATED); diff --git a/lib/SSO.php b/lib/SSO.php index f948512..4aa606d 100644 --- a/lib/SSO.php +++ b/lib/SSO.php @@ -40,13 +40,13 @@ public function getAuthorizationUrl( $authorizationPath = "sso/authorize"; if (!isset($domain) && !isset($provider) && !isset($connection) && !isset($organization)) { - $msg = "Either \$domain, \$provider, \$connection, or \$organization is required"; + $msg = "Either \$domain, \$provider, \$connection, or \$organization is required."; throw new Exception\UnexpectedValueException($msg); } if (isset($domain)) { - $msg = "Domain is being deprecated, please switch to using Connection or Organization ID"; + $msg = "'domain' is being deprecated, please switch to using 'connection' or 'organization'."; trigger_error($msg, E_USER_DEPRECATED); } diff --git a/lib/UserManagement.php b/lib/UserManagement.php index 1cca88d..af8c0e4 100644 --- a/lib/UserManagement.php +++ b/lib/UserManagement.php @@ -627,7 +627,7 @@ public function getAuthorizationUrl( $path = "user_management/authorize"; if (!isset($provider) && !isset($connectionId) && !isset($organizationId)) { - $msg = "Either \$provider, \$connectionId, or \$organizationId is required"; + $msg = "Either \$provider, \$connectionId, or \$organizationId is required."; throw new Exception\UnexpectedValueException($msg); } @@ -640,7 +640,7 @@ public function getAuthorizationUrl( ]; if (isset($provider) && !\in_array($provider, $supportedProviders)) { - $msg = "Only " . implode("','", $supportedProviders) . " providers are supported"; + $msg = "Only " . implode("','", $supportedProviders) . " providers are supported."; throw new Exception\UnexpectedValueException($msg); } diff --git a/lib/WorkOS.php b/lib/WorkOS.php index fbc9536..197e811 100644 --- a/lib/WorkOS.php +++ b/lib/WorkOS.php @@ -48,7 +48,7 @@ public static function getApiKey() return self::$apiKey; } - $msg = "\$apiKey is required"; + $msg = "\$apiKey is required."; throw new \WorkOS\Exception\ConfigurationException($msg); } @@ -76,7 +76,7 @@ public static function getClientId() return self::$clientId; } - $msg = "\$clientId is required"; + $msg = "\$clientId is required."; throw new \WorkOS\Exception\ConfigurationException($msg); } diff --git a/tests/WorkOS/AuditLogsTest.php b/tests/WorkOS/AuditLogsTest.php index 8cbfd82..7af0e4a 100644 --- a/tests/WorkOS/AuditLogsTest.php +++ b/tests/WorkOS/AuditLogsTest.php @@ -112,7 +112,7 @@ public function testCreateExport() ); $auditLogExport = $this->assertDeprecationTriggered( - "'actors' is deprecated. Please use 'actorNames' instead", + "'actors' is deprecated. Please use 'actorNames' instead.", fn() => $this->al->createExport($organizationId, $rangeStart, $rangeEnd, $actions, $actors, $targets, $actorNames, $actorIds) ); diff --git a/tests/WorkOS/DirectorySyncTest.php b/tests/WorkOS/DirectorySyncTest.php index 251750b..d2243d6 100644 --- a/tests/WorkOS/DirectorySyncTest.php +++ b/tests/WorkOS/DirectorySyncTest.php @@ -165,7 +165,7 @@ public function testGetUserPrimaryEmail() $user = $this->ds->getUser($directoryUser); $userEmail = $this->assertDeprecationTriggered( - "'primaryEmail' is deprecated. Please use 'email' instead", + "'primaryEmail' is deprecated. Please use 'email' instead.", fn() => $user->primaryEmail(), ); @@ -190,7 +190,7 @@ public function testGetUserPrimaryEmailNoPrimaryEmail() $user = $this->ds->getUser($directoryUser); $userEmail = $this->assertDeprecationTriggered( - "'primaryEmail' is deprecated. Please use 'email' instead", + "'primaryEmail' is deprecated. Please use 'email' instead.", fn() => $user->primaryEmail(), ); diff --git a/tests/WorkOS/MFATest.php b/tests/WorkOS/MFATest.php index 0d6746c..26c68bc 100644 --- a/tests/WorkOS/MFATest.php +++ b/tests/WorkOS/MFATest.php @@ -133,7 +133,7 @@ public function testVerifyFactor() ); $verifyFactor = $this->assertDeprecationTriggered( - "'verifyFactor' is deprecated. Please use 'verifyChallenge' instead", + "'verifyFactor' is deprecated. Please use 'verifyChallenge' instead.", fn() => $this->mfa->verifyFactor($authenticationChallengeId, $code) ); $verifyFactorResponseFixture = $this->verifyFactorFixture(); diff --git a/tests/WorkOS/OrganizationsTest.php b/tests/WorkOS/OrganizationsTest.php index 58829dd..22c6392 100644 --- a/tests/WorkOS/OrganizationsTest.php +++ b/tests/WorkOS/OrganizationsTest.php @@ -47,7 +47,7 @@ public function testCreateOrganizationWithDomains() $organization = $this->organizationFixture(); $response = $this->assertDeprecationTriggered( - "'domains' is deprecated. Please use 'domain_data' instead", + "'domains' is deprecated. Please use 'domain_data' instead.", fn() => $this->organizations->createOrganization("Organization Name", array("example.com")), ); $this->assertSame($organization, $response->toArray()); @@ -170,11 +170,11 @@ public function testCreateOrganizationSendsIdempotencyKey() ); $response = $this->assertDeprecationTriggered( - "'domains' is deprecated. Please use 'domain_data' instead", + "'domains' is deprecated. Please use 'domain_data' instead.", fn() => $this->organizations->createOrganization("Organization Name", array("example.com"), null, $idempotencyKey), ); $response2 = $this->assertDeprecationTriggered( - "'domains' is deprecated. Please use 'domain_data' instead", + "'domains' is deprecated. Please use 'domain_data' instead.", fn() => $this->organizations->createOrganization("Organization Name", array("example.com"), null, $idempotencyKey), ); diff --git a/tests/WorkOS/SSOTest.php b/tests/WorkOS/SSOTest.php index d5da134..b17fe99 100644 --- a/tests/WorkOS/SSOTest.php +++ b/tests/WorkOS/SSOTest.php @@ -88,7 +88,7 @@ public function testAuthorizationURLExpectedParams( if ($domain) { $authorizationUrl = $this->assertDeprecationTriggered( - "Domain is being deprecated, please switch to using Connection or Organization ID", + "'domain' is being deprecated, please switch to using 'connection' or 'organization'.", $fn ); } else { diff --git a/tests/WorkOS/UserManagementTest.php b/tests/WorkOS/UserManagementTest.php index 74382ae..d6c00f7 100644 --- a/tests/WorkOS/UserManagementTest.php +++ b/tests/WorkOS/UserManagementTest.php @@ -633,7 +633,6 @@ public function testCreatePasswordReset() public function testSendPasswordResetEmail() { $path = "user_management/password_reset/send"; - $msg = "'sendPasswordResetEmail' is deprecated. Please use 'createPasswordReset' instead. This method will be removed in a future major version."; // Mock the API request $responseCode = 200; @@ -655,7 +654,7 @@ public function testSendPasswordResetEmail() // Call the deprecated method $response = $this->assertDeprecationTriggered( - $msg, + "'sendPasswordResetEmail' is deprecated. Please use 'createPasswordReset' instead. This method will be removed in a future major version.", fn() => $this->userManagement->sendPasswordResetEmail("test@test.com", "https://your-app.com/reset-password") ); From 1507906d1f610fe5d5f7c7150ee98a1e335f9716 Mon Sep 17 00:00:00 2001 From: Braden Keith Date: Wed, 2 Apr 2025 16:16:06 -0400 Subject: [PATCH 20/26] IDE & Typehint improvements (#264) * Improve Webhook and BaseWorkOSResource PHPDoc types * Enhance Webhook class with improved code style and PHPDoc annotations * Add accessToken and refreshToken to AuthenticationResponse class * Update AuthenticationResponse class to include organizationId as nullable, and add accessToken, refreshToken, and impersonator properties * Enhance OrganizationMembership class with additional PHPDoc properties for improved type documentation * Add metadata and external id (#268) And allow to be passed when creating or updating a user or organization. * Add email standard attribute to DirectoryUser and mark deprecated standard attributes (#261) * Add function to get organization by external id (#270) And fix typo in getOrganization docstring * Add support for creating, getting and updating users with external_id property (#267) Co-authored-by: Eric Roberts * Bump to version 4.22.0. (#269) * Structured responses to webhook events (#265) * Add WebhookResponse class for handling webhook actions and responses * Refactor WebhookResponse create method and improve validation * Resolve linting error --------- Co-authored-by: Braden Keith * Update deprecation notices in DirectoryUser class to include version information and improve clarity * Update deprecation notices in Organizations class to include version information and improve formatting * Update doc blocks for deprecation notices * Update tests to expect Role Slug --------- Co-authored-by: Braden Keith Co-authored-by: Eric Roberts Co-authored-by: Matt Dzwonczyk <9063128+mattgd@users.noreply.github.com> Co-authored-by: Pepe From c31d9dc0c34224c5226200dd0053c0aaa346cdb0 Mon Sep 17 00:00:00 2001 From: Braden Keith Date: Tue, 11 Mar 2025 13:16:09 -0400 Subject: [PATCH 21/26] Enhance Webhook class with improved code style and PHPDoc annotations From 99975bddea5c14ee0f1ca0d98a35853e1c64d748 Mon Sep 17 00:00:00 2001 From: Eric Roberts Date: Fri, 21 Mar 2025 13:56:38 -0400 Subject: [PATCH 22/26] Add metadata and external id (#268) And allow to be passed when creating or updating a user or organization. --- lib/UserManagement.php | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/UserManagement.php b/lib/UserManagement.php index af8c0e4..d397f5e 100644 --- a/lib/UserManagement.php +++ b/lib/UserManagement.php @@ -33,7 +33,6 @@ class UserManagement * * @return Resource\User */ - public function createUser( $email, $password = null, From 838f26fa7c39b5bf0b910620963498af4206b717 Mon Sep 17 00:00:00 2001 From: Matt Dzwonczyk <9063128+mattgd@users.noreply.github.com> Date: Fri, 21 Mar 2025 14:08:26 -0400 Subject: [PATCH 23/26] Add email standard attribute to DirectoryUser and mark deprecated standard attributes (#261) From 84b072bb27dd793c27b27d9a4f0288d622acfa21 Mon Sep 17 00:00:00 2001 From: Pepe Date: Fri, 21 Mar 2025 19:49:05 +0100 Subject: [PATCH 24/26] Add support for creating, getting and updating users with external_id property (#267) Co-authored-by: Eric Roberts --- lib/UserManagement.php | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/UserManagement.php b/lib/UserManagement.php index d397f5e..af8c0e4 100644 --- a/lib/UserManagement.php +++ b/lib/UserManagement.php @@ -33,6 +33,7 @@ class UserManagement * * @return Resource\User */ + public function createUser( $email, $password = null, From 5cda751b4d6c41d9a7e6fc52ac1fa026cf266088 Mon Sep 17 00:00:00 2001 From: Braden Keith Date: Wed, 2 Apr 2025 20:59:31 -0400 Subject: [PATCH 25/26] Improve code style across multiple test classes. --- lib/Resource/BaseWorkOSResource.php | 4 +++- lib/UserManagement.php | 1 - tests/WorkOS/AuditLogsTest.php | 9 ++++----- tests/WorkOS/DirectorySyncTest.php | 11 +++++------ tests/WorkOS/MFATest.php | 9 ++++----- tests/WorkOS/OrganizationsTest.php | 15 +++++++-------- tests/WorkOS/PasswordlessTest.php | 7 +++---- tests/WorkOS/PortalTest.php | 7 +++---- tests/WorkOS/SSOTest.php | 9 ++++----- tests/WorkOS/UserManagementTest.php | 9 ++++----- tests/WorkOS/WebhookTest.php | 7 +++---- tests/WorkOS/WidgetsTest.php | 7 +++---- 12 files changed, 43 insertions(+), 52 deletions(-) diff --git a/lib/Resource/BaseWorkOSResource.php b/lib/Resource/BaseWorkOSResource.php index 1ea3fc9..23c7d25 100644 --- a/lib/Resource/BaseWorkOSResource.php +++ b/lib/Resource/BaseWorkOSResource.php @@ -30,7 +30,9 @@ class BaseWorkOSResource */ public $raw; - private function __construct() {} + private function __construct() + { + } /** * Creates a Resource from a Response. diff --git a/lib/UserManagement.php b/lib/UserManagement.php index af8c0e4..d397f5e 100644 --- a/lib/UserManagement.php +++ b/lib/UserManagement.php @@ -33,7 +33,6 @@ class UserManagement * * @return Resource\User */ - public function createUser( $email, $password = null, diff --git a/tests/WorkOS/AuditLogsTest.php b/tests/WorkOS/AuditLogsTest.php index 7af0e4a..8d20014 100644 --- a/tests/WorkOS/AuditLogsTest.php +++ b/tests/WorkOS/AuditLogsTest.php @@ -7,15 +7,14 @@ class AuditLogsTest extends TestCase { + use TestHelper { + setUp as protected traitSetUp; + } /** * @var AuditLogs */ protected $al; - use TestHelper { - setUp as protected traitSetUp; - } - protected function setUp(): void { $this->traitSetUp(); @@ -113,7 +112,7 @@ public function testCreateExport() $auditLogExport = $this->assertDeprecationTriggered( "'actors' is deprecated. Please use 'actorNames' instead.", - fn() => $this->al->createExport($organizationId, $rangeStart, $rangeEnd, $actions, $actors, $targets, $actorNames, $actorIds) + fn () => $this->al->createExport($organizationId, $rangeStart, $rangeEnd, $actions, $actors, $targets, $actorNames, $actorIds) ); $exportFixture = $this->createExportFixture(); diff --git a/tests/WorkOS/DirectorySyncTest.php b/tests/WorkOS/DirectorySyncTest.php index d2243d6..73fc128 100644 --- a/tests/WorkOS/DirectorySyncTest.php +++ b/tests/WorkOS/DirectorySyncTest.php @@ -7,15 +7,14 @@ class DirectorySyncTest extends TestCase { + use TestHelper { + setUp as traitSetUp; + } /** * @var DirectorySync */ protected $ds; - use TestHelper { - setUp as traitSetUp; - } - protected function setUp(): void { $this->traitSetUp(); @@ -166,7 +165,7 @@ public function testGetUserPrimaryEmail() $user = $this->ds->getUser($directoryUser); $userEmail = $this->assertDeprecationTriggered( "'primaryEmail' is deprecated. Please use 'email' instead.", - fn() => $user->primaryEmail(), + fn () => $user->primaryEmail(), ); $this->assertSame($userEmail, $expectedEmail); @@ -191,7 +190,7 @@ public function testGetUserPrimaryEmailNoPrimaryEmail() $user = $this->ds->getUser($directoryUser); $userEmail = $this->assertDeprecationTriggered( "'primaryEmail' is deprecated. Please use 'email' instead.", - fn() => $user->primaryEmail(), + fn () => $user->primaryEmail(), ); $this->assertSame($userEmail, $expectedEmail); diff --git a/tests/WorkOS/MFATest.php b/tests/WorkOS/MFATest.php index 26c68bc..138dfe8 100644 --- a/tests/WorkOS/MFATest.php +++ b/tests/WorkOS/MFATest.php @@ -7,15 +7,14 @@ class MFATest extends TestCase { + use TestHelper { + setUp as traitSetUp; + } /** * @var MFA */ protected $mfa; - use TestHelper { - setUp as traitSetUp; - } - protected function setUp(): void { $this->traitSetUp(); @@ -134,7 +133,7 @@ public function testVerifyFactor() $verifyFactor = $this->assertDeprecationTriggered( "'verifyFactor' is deprecated. Please use 'verifyChallenge' instead.", - fn() => $this->mfa->verifyFactor($authenticationChallengeId, $code) + fn () => $this->mfa->verifyFactor($authenticationChallengeId, $code) ); $verifyFactorResponseFixture = $this->verifyFactorFixture(); diff --git a/tests/WorkOS/OrganizationsTest.php b/tests/WorkOS/OrganizationsTest.php index 22c6392..b3a4750 100644 --- a/tests/WorkOS/OrganizationsTest.php +++ b/tests/WorkOS/OrganizationsTest.php @@ -7,15 +7,14 @@ class OrganizationsTest extends TestCase { + use TestHelper { + setUp as protected traitSetUp; + } /** * @var Organizations */ protected $organizations; - use TestHelper { - setUp as protected traitSetUp; - } - protected function setUp(): void { $this->traitSetUp(); @@ -48,7 +47,7 @@ public function testCreateOrganizationWithDomains() $response = $this->assertDeprecationTriggered( "'domains' is deprecated. Please use 'domain_data' instead.", - fn() => $this->organizations->createOrganization("Organization Name", array("example.com")), + fn () => $this->organizations->createOrganization("Organization Name", array("example.com")), ); $this->assertSame($organization, $response->toArray()); } @@ -110,7 +109,7 @@ public function testCreateOrganizationWithAllowProfilesOutsideOrganizationDeprec $this->assertDeprecationTriggered( "'allowProfilesOutsideOrganization' is deprecated. If you need to allow sign-ins from any email domain, contact support@workos.com.", - fn() => $this->organizations->createOrganization("Organization Name", null, true), + fn () => $this->organizations->createOrganization("Organization Name", null, true), ); } @@ -171,11 +170,11 @@ public function testCreateOrganizationSendsIdempotencyKey() $response = $this->assertDeprecationTriggered( "'domains' is deprecated. Please use 'domain_data' instead.", - fn() => $this->organizations->createOrganization("Organization Name", array("example.com"), null, $idempotencyKey), + fn () => $this->organizations->createOrganization("Organization Name", array("example.com"), null, $idempotencyKey), ); $response2 = $this->assertDeprecationTriggered( "'domains' is deprecated. Please use 'domain_data' instead.", - fn() => $this->organizations->createOrganization("Organization Name", array("example.com"), null, $idempotencyKey), + fn () => $this->organizations->createOrganization("Organization Name", array("example.com"), null, $idempotencyKey), ); $this->assertSame($response2->toArray()["id"], $response->toArray()["id"]); diff --git a/tests/WorkOS/PasswordlessTest.php b/tests/WorkOS/PasswordlessTest.php index abcd904..e393059 100644 --- a/tests/WorkOS/PasswordlessTest.php +++ b/tests/WorkOS/PasswordlessTest.php @@ -7,15 +7,14 @@ class PasswordlessTest extends TestCase { + use TestHelper { + setUp as traitSetUp; + } /** * @var Passwordless */ protected $passwordless; - use TestHelper { - setUp as traitSetUp; - } - protected function setUp(): void { $this->traitSetUp(); diff --git a/tests/WorkOS/PortalTest.php b/tests/WorkOS/PortalTest.php index 3d3eb45..30d069e 100644 --- a/tests/WorkOS/PortalTest.php +++ b/tests/WorkOS/PortalTest.php @@ -7,15 +7,14 @@ class PortalTest extends TestCase { + use TestHelper { + setUp as protected traitSetUp; + } /** * @var Portal */ protected $portal; - use TestHelper { - setUp as protected traitSetUp; - } - protected function setUp(): void { $this->traitSetUp(); diff --git a/tests/WorkOS/SSOTest.php b/tests/WorkOS/SSOTest.php index b17fe99..5bfdb1d 100644 --- a/tests/WorkOS/SSOTest.php +++ b/tests/WorkOS/SSOTest.php @@ -9,15 +9,14 @@ class SSOTest extends TestCase { + use TestHelper { + setUp as traitSetUp; + } /** * @var SSO */ protected $sso; - use TestHelper { - setUp as traitSetUp; - } - protected function setUp(): void { $this->traitSetUp(); @@ -75,7 +74,7 @@ public function testAuthorizationURLExpectedParams( $expectedParams["login_hint"] = $loginHint; } - $fn = fn() => $this->sso->getAuthorizationUrl( + $fn = fn () => $this->sso->getAuthorizationUrl( $domain, $redirectUri, $state, diff --git a/tests/WorkOS/UserManagementTest.php b/tests/WorkOS/UserManagementTest.php index d6c00f7..e14ac06 100644 --- a/tests/WorkOS/UserManagementTest.php +++ b/tests/WorkOS/UserManagementTest.php @@ -8,15 +8,14 @@ class UserManagementTest extends TestCase { + use TestHelper { + setUp as traitSetUp; + } /** * @var UserManagement */ protected $userManagement; - use TestHelper { - setUp as traitSetUp; - } - protected function setUp(): void { $this->traitSetUp(); @@ -655,7 +654,7 @@ public function testSendPasswordResetEmail() // Call the deprecated method $response = $this->assertDeprecationTriggered( "'sendPasswordResetEmail' is deprecated. Please use 'createPasswordReset' instead. This method will be removed in a future major version.", - fn() => $this->userManagement->sendPasswordResetEmail("test@test.com", "https://your-app.com/reset-password") + fn () => $this->userManagement->sendPasswordResetEmail("test@test.com", "https://your-app.com/reset-password") ); // Test the functionality diff --git a/tests/WorkOS/WebhookTest.php b/tests/WorkOS/WebhookTest.php index ff3bd4a..cdcc16d 100644 --- a/tests/WorkOS/WebhookTest.php +++ b/tests/WorkOS/WebhookTest.php @@ -7,6 +7,9 @@ class WebhookTest extends TestCase { + use TestHelper { + setUp as protected traitSetUp; + } /** * @var Webhook */ @@ -42,10 +45,6 @@ class WebhookTest extends TestCase */ protected $sigHeader; - use TestHelper { - setUp as protected traitSetUp; - } - protected function setUp(): void { $this->traitSetUp(); diff --git a/tests/WorkOS/WidgetsTest.php b/tests/WorkOS/WidgetsTest.php index bdd16af..1549a9e 100644 --- a/tests/WorkOS/WidgetsTest.php +++ b/tests/WorkOS/WidgetsTest.php @@ -7,15 +7,14 @@ class WidgetsTest extends TestCase { + use TestHelper { + setUp as protected traitSetUp; + } /** * @var Widgets */ protected $widgets; - use TestHelper { - setUp as protected traitSetUp; - } - protected function setUp(): void { $this->traitSetUp(); From 49532241589b5c2e33428d14b200cfec3045e581 Mon Sep 17 00:00:00 2001 From: Braden Keith Date: Wed, 2 Apr 2025 21:51:55 -0400 Subject: [PATCH 26/26] Remove unnecessary blank line in UserManagementTest.php following deprecation notice update for 'sendPasswordResetEmail' method. --- tests/WorkOS/UserManagementTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/WorkOS/UserManagementTest.php b/tests/WorkOS/UserManagementTest.php index 7ffab69..e14ac06 100644 --- a/tests/WorkOS/UserManagementTest.php +++ b/tests/WorkOS/UserManagementTest.php @@ -656,7 +656,7 @@ public function testSendPasswordResetEmail() "'sendPasswordResetEmail' is deprecated. Please use 'createPasswordReset' instead. This method will be removed in a future major version.", fn () => $this->userManagement->sendPasswordResetEmail("test@test.com", "https://your-app.com/reset-password") ); - + // Test the functionality $this->assertSame(200, $responseCode); $this->assertSame($response, []);