Repository: auth0/auth0-PHP (php)

Issue:
Recent changes break existing auth0/login configuration
### SDK Version

8.3

### PHP Version

PHP 8.1

### What happened?

Recent changes to this library (#659) and to the Laravel library (auth0/login, https://github.com/auth0/laravel-auth0/commit/e132b28b9fb623339ba1f236f6430effa29f2c89) break backward compatibility.

`SdkConfiguration::setScope()` no longer handles a `null` empty value correctly. Although the recent change to auth0/login corrects this for anyone who installs and publishes the Laravel configuration after auth0/login:7.2.2, anyone with an **existing configuration** file (which would have code like this: `Configuration::stringToArrayOrNull(env('AUTH0_SCOPE')),`)  will default their scope to null.

This library should be able to handle both empty states, empty array and null.

I suggest patching to replace `if ([] === $scope)` with `if (empty($scope)) {` in SdkConfiguration::setScope.

There may be additional test cases required here.


### How can we reproduce this issue?

- Install Laravel.
- Composer-require auth0/auth0-php:8.3.0.
- Composer-require auth0/login:7.1.0.
- `php artisan vendor:publish --tag=auth0-config`
- set up the rest of the app per the instructions.
- Loosen Composer requirements to `auth0/auth0-php:^8.3.0` and `auth0/login:^7.1.0`
- composer update
- confirm that `Validation of "scope" was unsuccessful` is reported.

### Additional context

_No response_

Pull request description:
fix: Restore previous behavior of SdkConfiguration::setScope() being nullable
<!--
  Please only send a pull request to branches that are currently supported.
  Pull requests without a descriptive title, thorough description, or tests will be closed.
  -
  When proposing enhancements, please ensure you have created an Issue and given our engineers time to
  review your suggestion there prior to commiting work.
-->

### Changes

<!--
  Would you please describe both what is changing and why this is important?
  Explain the benefit to end-users, why it does not break any existing features, how it makes building applications easier, etc.
-->

This PR restores previous behavior that allowed SdkConfiguration::setScope() to accept `null` values. In previous versions, setScope() allowed `null | string[]`. This was inadvertently changed to only accept `string[]` in the previous release.

This was mistakenly altered during the migration from the previous magic method approach of offering virtual methods for altering configurations, to the PHP 8.0+ syntax and offering concrete methods.

### References

<!--
  All pull requests should link to an associated issue tagged 'selected for development' by an Auth0 engineer.
-->

Resolves #664 

### Testing

<!--
  Would you please describe how reviewers can test this?
  Be specific about anything not tested and the reasons why.
  Tests must be added for new functionality, and existing tests should complete without errors.
-->

Run `/vendor/bin/pest` from a local cloned repo, or review the GitHub status checks below.

### Contributor Checklist

- [x] I have read the [Auth0 general contribution guidelines](https://github.com/auth0/open-source-template/blob/master/GENERAL-CONTRIBUTING.md)
- [x] I have read the [Auth0 code of conduct](https://github.com/auth0/open-source-template/blob/master/CODE-OF-CONDUCT.md)


Patch:
diff --git a/src/Configuration/SdkConfiguration.php b/src/Configuration/SdkConfiguration.php
index 241f7b8..a128f77 100644
--- a/src/Configuration/SdkConfiguration.php
+++ b/src/Configuration/SdkConfiguration.php
@@ -52,7 +52,7 @@ final class SdkConfiguration implements ConfigurableContract
      * @param array<string>|null             $audience              One or more API identifiers, found in your Auth0 API settings. The SDK uses the first value for building links. If provided, at least one of these values must match the 'aud' claim to validate an ID Token successfully.
      * @param array<string>|null             $organization          One or more Organization IDs, found in your Auth0 Organization settings. The SDK uses the first value for building links. If provided, at least one of these values must match the 'org_id' claim to validate an ID Token successfully.
      * @param bool                           $usePkce               Defaults to true. Use PKCE (Proof Key of Code Exchange) with Authorization Code Flow requests. See https://auth0.com/docs/flows/call-your-api-using-the-authorization-code-flow-with-pkce
-     * @param array<string>                  $scope                 One or more scopes to request for Tokens. See https://auth0.com/docs/scopes
+     * @param array<string>|null             $scope                 One or more scopes to request for Tokens. See https://auth0.com/docs/scopes
      * @param string                         $responseMode          Defaults to 'query.' Where to extract request parameters from, either 'query' for GET or 'form_post' for POST requests.
      * @param string                         $responseType          Defaults to 'code.' Use 'code' for server-side flows and 'token' for application side flow.
      * @param string                         $tokenAlgorithm        Defaults to 'RS256'. Algorithm to use for Token verification. Expects either 'RS256' or 'HS256'.
@@ -99,7 +99,7 @@ final class SdkConfiguration implements ConfigurableContract
         private ?array $audience = null,
         private ?array $organization = null,
         private bool $usePkce = true,
-        private array $scope = ['openid', 'profile', 'email'],
+        private ?array $scope = ['openid', 'profile', 'email'],
         private string $responseMode = 'query',
         private string $responseType = 'code',
         private string $tokenAlgorithm = Token::ALGO_RS256,
@@ -768,9 +768,9 @@ final class SdkConfiguration implements ConfigurableContract
     /**
      * @param array<string> $scope An array of scopes to request during authentication steps.
      */
-    public function setScope(array $scope = ['openid', 'profile', 'email']): self
+    public function setScope(?array $scope = ['openid', 'profile', 'email']): self
     {
-        if ([] === $scope) {
+        if (null === $scope || [] === $scope) {
             $scope = ['openid', 'profile', 'email'];
         }
 
@@ -783,7 +783,7 @@ final class SdkConfiguration implements ConfigurableContract
      */
     public function getScope(): array
     {
-        return $this->scope;
+        return $this->scope ?? ['openid', 'profile', 'email'];
     }
 
     public function hasScope(): bool
@@ -811,7 +811,7 @@ final class SdkConfiguration implements ConfigurableContract
             return $this->scope;
         }
 
-        $this->setScope(array_merge($this->scope, $scopes));
+        $this->setScope(array_merge($this->getScope(), $scopes));
 
         return $this->scope;
     }
@@ -1321,7 +1321,7 @@ final class SdkConfiguration implements ConfigurableContract
             'audience' => fn ($value) => is_array($value) || null === $value,
             'organization' => fn ($value) => is_array($value) || null === $value,
             'usePkce' => fn ($value) => is_bool($value),
-            'scope' => fn ($value) => is_array($value),
+            'scope' => fn ($value) => is_array($value) || null === $value,
             'responseMode' => fn ($value) => is_string($value),
             'responseType' => fn ($value) => is_string($value),
             'tokenAlgorithm' => fn ($value) => is_string($value),


Test patch:
diff --git a/tests/Unit/Configuration/SdkConfigurationTest.php b/tests/Unit/Configuration/SdkConfigurationTest.php
index 8091396..dcaaa26 100644
--- a/tests/Unit/Configuration/SdkConfigurationTest.php
+++ b/tests/Unit/Configuration/SdkConfigurationTest.php
@@ -402,6 +402,32 @@ test('scope() successfully converts the array to a string', function(): void
     expect($sdk->formatScope())->toEqual('one two three');
 });
 
+test('scope() successfully reverts to the default values when an empty array is provided', function(): void
+{
+    $sdk = new SdkConfiguration([
+        'domain' => MockDomain::valid(),
+        'cookieSecret' => uniqid(),
+        'clientId' => uniqid(),
+        'redirectUri' => uniqid(),
+        'scope' => [],
+    ]);
+
+    expect($sdk->getScope())->toEqual(['openid', 'profile', 'email']);
+});
+
+test('scope() successfully reverts to the default values when a null value is provided', function(): void
+{
+    $sdk = new SdkConfiguration([
+        'domain' => MockDomain::valid(),
+        'cookieSecret' => uniqid(),
+        'clientId' => uniqid(),
+        'redirectUri' => uniqid(),
+        'scope' => null,
+    ]);
+
+    expect($sdk->getScope())->toEqual(['openid', 'profile', 'email']);
+});
+
 test('defaultOrganization() successfully returns the first organization', function(): void
 {
     $sdk = new SdkConfiguration([