diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/AuthMethods/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/AuthMethods/Update.php index c55c14ef9b..4f2ea38c09 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/AuthMethods/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/AuthMethods/Update.php @@ -28,7 +28,7 @@ class Update extends Action public function __construct() { $this - ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) // Should be PUT + ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) // Behaves as PUT ->setHttpPath('/v1/project/auth-methods/:methodId') ->httpAlias('/v1/projects/:projectId/auth/:methodId') ->desc('Update project auth method status') diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Apple/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Apple/Update.php index dc9eede2b4..23aacc1eff 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Apple/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Apple/Update.php @@ -14,7 +14,6 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; use Utopia\Validator\Boolean; -use Utopia\Validator\Nullable; use Utopia\Validator\Text; class Update extends Base @@ -108,7 +107,7 @@ class Update extends Base $providerLabel = static::getProviderLabel(); $this - ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) + ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) // Behaves as PUT ->setHttpPath('/v1/project/oauth2/' . $providerId) ->desc('Update project OAuth2 ' . $providerLabel) ->groups(['api', 'project']) @@ -129,11 +128,11 @@ class Update extends Base ) ], )) - ->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true) - ->param('keyId', null, new Nullable(new Text(256, 0)), '\'Key ID\' of Apple OAuth2 app. For example: P4000000N8', optional: true) - ->param('teamId', null, new Nullable(new Text(256, 0)), '\'Team ID\' of Apple OAuth2 app. For example: D4000000R6', optional: true) - ->param('p8File', null, new Nullable(new Text(4096, 0)), 'Contents of the Apple OAuth2 app .p8 private key file. The secret key wrapped by the PEM markers is 200 characters long. For example: -----BEGIN PRIVATE KEY-----MIGTAg...jy2Xbna-----END PRIVATE KEY-----', optional: true) - ->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true) + ->param(static::getClientIdParamName(), null, new Text(256, 0), static::getClientIdDescription()) + ->param('keyId', null, new Text(256, 0), '\'Key ID\' of Apple OAuth2 app. For example: P4000000N8') + ->param('teamId', null, new Text(256, 0), '\'Team ID\' of Apple OAuth2 app. For example: D4000000R6') + ->param('p8File', null, new Text(4096, 0), 'Contents of the Apple OAuth2 app .p8 private key file. The secret key wrapped by the PEM markers is 200 characters long. For example: -----BEGIN PRIVATE KEY-----MIGTAg...jy2Xbna-----END PRIVATE KEY-----') + ->param('enabled', false, new Boolean(), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.') ->inject('response') ->inject('dbForPlatform') ->inject('project') @@ -166,11 +165,11 @@ class Update extends Base * avoid an LSP-incompatible override of Base::action(). */ public function handle( - ?string $serviceId, - ?string $keyId, - ?string $teamId, - ?string $p8File, - ?bool $enabled, + string $serviceId, + string $keyId, + string $teamId, + string $p8File, + bool $enabled, Response $response, Database $dbForPlatform, Document $project, @@ -180,23 +179,11 @@ class Update extends Base $providerId = static::getProviderId(); $queueForEvents->setParam('providerId', $providerId); - // The secret is stored as JSON `{"p8": "...", "keyID": "...", "teamID": "..."}` - // to match the shape Apple's OAuth2 adapter expects in getAppSecret(). - // Merge new values with what's already stored so that submitting only - // some of the fields leaves the rest untouched. - $encodedSecret = null; - if (!\is_null($keyId) || !\is_null($teamId) || !\is_null($p8File)) { - $storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? ''; - $existing = []; - if (!empty($storedRaw)) { - $existing = \json_decode($storedRaw, true) ?: []; - } - $encodedSecret = \json_encode([ - 'p8' => $p8File ?? ($existing['p8'] ?? ''), - 'keyID' => $keyId ?? ($existing['keyID'] ?? ''), - 'teamID' => $teamId ?? ($existing['teamID'] ?? ''), - ]); - } + $encodedSecret = \json_encode([ + 'p8' => $p8File, + 'keyID' => $keyId, + 'teamID' => $teamId, + ]); $project = $this->persistCredentials($project, $dbForPlatform, $authorization, $serviceId, $encodedSecret, $enabled); diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Auth0/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Auth0/Update.php index aa5f39b213..8b0ae5a435 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Auth0/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Auth0/Update.php @@ -14,7 +14,6 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; use Utopia\Validator\Boolean; -use Utopia\Validator\Nullable; use Utopia\Validator\Text; class Update extends Base @@ -82,7 +81,7 @@ class Update extends Base $providerLabel = static::getProviderLabel(); $this - ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) + ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) // Behaves as PUT ->setHttpPath('/v1/project/oauth2/' . $providerId) ->desc('Update project OAuth2 ' . $providerLabel) ->groups(['api', 'project']) @@ -103,10 +102,10 @@ class Update extends Base ) ], )) - ->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true) - ->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true) - ->param('endpoint', null, new Nullable(new Text(256, 0)), 'Domain of Auth0 instance. For example: example.us.auth0.com', optional: true) - ->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true) + ->param(static::getClientIdParamName(), null, new Text(256, 0), static::getClientIdDescription()) + ->param(static::getClientSecretParamName(), null, new Text(512, 0), static::getClientSecretDescription()) + ->param('endpoint', null, new Text(256, 0), 'Domain of Auth0 instance. For example: example.us.auth0.com') + ->param('enabled', false, new Boolean(), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.') ->inject('response') ->inject('dbForPlatform') ->inject('project') @@ -136,10 +135,10 @@ class Update extends Base * differently to avoid an LSP-incompatible override of Base::action(). */ public function handle( - ?string $clientId, - ?string $clientSecret, - ?string $endpoint, - ?bool $enabled, + string $clientId, + string $clientSecret, + string $endpoint, + bool $enabled, Response $response, Database $dbForPlatform, Document $project, @@ -149,22 +148,10 @@ class Update extends Base $providerId = static::getProviderId(); $queueForEvents->setParam('providerId', $providerId); - // The secret is stored as JSON `{"clientSecret": "...", "auth0Domain": "..."}` - // to match the shape Auth0's OAuth2 adapter expects (getAuth0Domain()). - // Merge new values with existing storage so that submitting only one of - // `clientSecret`/`endpoint` leaves the other untouched. - $encodedSecret = null; - if (!\is_null($clientSecret) || !\is_null($endpoint)) { - $storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? ''; - $existing = []; - if (!empty($storedRaw)) { - $existing = \json_decode($storedRaw, true) ?: []; - } - $encodedSecret = \json_encode([ - 'clientSecret' => $clientSecret ?? ($existing['clientSecret'] ?? ''), - 'auth0Domain' => $endpoint ?? ($existing['auth0Domain'] ?? ''), - ]); - } + $encodedSecret = \json_encode([ + 'clientSecret' => $clientSecret, + 'auth0Domain' => $endpoint, + ]); $project = $this->persistCredentials($project, $dbForPlatform, $authorization, $clientId, $encodedSecret, $enabled); diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Authentik/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Authentik/Update.php index af6b12618a..aaa7633f1b 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Authentik/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Authentik/Update.php @@ -14,7 +14,6 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; use Utopia\Validator\Boolean; -use Utopia\Validator\Nullable; use Utopia\Validator\Text; class Update extends Base @@ -82,7 +81,7 @@ class Update extends Base $providerLabel = static::getProviderLabel(); $this - ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) + ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) // Behaves as PUT ->setHttpPath('/v1/project/oauth2/' . $providerId) ->desc('Update project OAuth2 ' . $providerLabel) ->groups(['api', 'project']) @@ -103,10 +102,10 @@ class Update extends Base ) ], )) - ->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true) - ->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true) - ->param('endpoint', null, new Nullable(new Text(256, 0)), 'Domain of Authentik instance. For example: example.authentik.com', optional: true) - ->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true) + ->param(static::getClientIdParamName(), null, new Text(256, 0), static::getClientIdDescription()) + ->param(static::getClientSecretParamName(), null, new Text(512, 0), static::getClientSecretDescription()) + ->param('endpoint', null, new Text(256, 0), 'Domain of Authentik instance. For example: example.authentik.com') + ->param('enabled', false, new Boolean(), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.') ->inject('response') ->inject('dbForPlatform') ->inject('project') @@ -136,10 +135,10 @@ class Update extends Base * differently to avoid an LSP-incompatible override of Base::action(). */ public function handle( - ?string $clientId, - ?string $clientSecret, - ?string $endpoint, - ?bool $enabled, + string $clientId, + string $clientSecret, + string $endpoint, + bool $enabled, Response $response, Database $dbForPlatform, Document $project, @@ -149,18 +148,9 @@ class Update extends Base $providerId = static::getProviderId(); $queueForEvents->setParam('providerId', $providerId); - // The secret is stored as JSON `{"clientSecret": "...", "authentikDomain": "..."}` - // to match the shape Authentik's OAuth2 adapter expects (getAuthentikDomain()). - // The `endpoint` param is optional; if omitted, the existing stored endpoint is preserved. - // `clientSecret` is optional; if omitted, the existing stored secret is preserved. - $storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? ''; - $existing = []; - if (!empty($storedRaw)) { - $existing = \json_decode($storedRaw, true) ?: []; - } $encodedSecret = \json_encode([ - 'clientSecret' => $clientSecret ?? ($existing['clientSecret'] ?? ''), - 'authentikDomain' => $endpoint ?? ($existing['authentikDomain'] ?? ''), + 'clientSecret' => $clientSecret, + 'authentikDomain' => $endpoint, ]); $project = $this->persistCredentials($project, $dbForPlatform, $authorization, $clientId, $encodedSecret, $enabled); diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Base.php b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Base.php index b5b8cacb73..a91c3d3440 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Base.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Base.php @@ -15,7 +15,6 @@ use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; use Utopia\Platform\Scope\HTTP; use Utopia\Validator\Boolean; -use Utopia\Validator\Nullable; use Utopia\Validator\Text; abstract class Base extends Action @@ -234,7 +233,7 @@ abstract class Base extends Action $providerLabel = static::getProviderLabel(); $this - ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) + ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) // Behaves as PUT ->setHttpPath('/v1/project/oauth2/' . $providerId) ->desc('Update project OAuth2 ' . $providerLabel) ->groups(['api', 'project']) @@ -255,9 +254,9 @@ abstract class Base extends Action ) ], )) - ->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true) - ->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true) - ->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true) + ->param(static::getClientIdParamName(), null, new Text(256, 0), static::getClientIdDescription()) + ->param(static::getClientSecretParamName(), null, new Text(512, 0), static::getClientSecretDescription()) + ->param('enabled', false, new Boolean(), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.') ->inject('response') ->inject('dbForPlatform') ->inject('project') @@ -372,9 +371,9 @@ abstract class Base extends Action Document $project, Database $dbForPlatform, Authorization $authorization, - ?string $clientId, - ?string $clientSecret, - ?bool $enabled + string $clientId, + string $clientSecret, + bool $enabled ): Document { $providerId = static::getProviderId(); if (!(\in_array($providerId, \array_keys(Config::getParam('oAuthProviders'))))) { @@ -387,19 +386,11 @@ abstract class Base extends Action $appSecretKey = $providerId . 'Secret'; $enabledKey = $providerId . 'Enabled'; - if (!\is_null($clientId)) { - $oAuthProviders[$appIdKey] = $clientId; - } + $oAuthProviders[$appIdKey] = $clientId; + $oAuthProviders[$appSecretKey] = $clientSecret; + $oAuthProviders[$enabledKey] = $enabled; - if (!\is_null($clientSecret)) { - $oAuthProviders[$appSecretKey] = $clientSecret; - } - - if (!\is_null($enabled)) { - $oAuthProviders[$enabledKey] = $enabled; - } - - if ($enabled === true || \is_null($enabled)) { + if ($enabled === true) { try { if (empty($oAuthProviders[$appIdKey]) || empty($oAuthProviders[$appSecretKey])) { throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Client ID and Client Secret are required when enabling OAuth2 provider.'); @@ -412,12 +403,8 @@ abstract class Base extends Action if (\method_exists($providerInstance, 'verifyCredentials')) { $providerInstance->verifyCredentials(); } - - $oAuthProviders[$enabledKey] = true; } catch (\Throwable $err) { - if ($enabled === true) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Could not enable OAuth2 provider: ' . $err->getMessage()); - } + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Could not enable OAuth2 provider: ' . $err->getMessage()); } } @@ -429,9 +416,9 @@ abstract class Base extends Action } public function action( - ?string $clientId, - ?string $clientSecret, - ?bool $enabled, + string $clientId, + string $clientSecret, + bool $enabled, Response $response, Database $dbForPlatform, Document $project, diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/FusionAuth/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/FusionAuth/Update.php index 3cdf0eeb89..089878a312 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/FusionAuth/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/FusionAuth/Update.php @@ -14,7 +14,6 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; use Utopia\Validator\Boolean; -use Utopia\Validator\Nullable; use Utopia\Validator\Text; class Update extends Base @@ -82,7 +81,7 @@ class Update extends Base $providerLabel = static::getProviderLabel(); $this - ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) + ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) // Behaves as PUT ->setHttpPath('/v1/project/oauth2/' . $providerId) ->desc('Update project OAuth2 ' . $providerLabel) ->groups(['api', 'project']) @@ -103,10 +102,10 @@ class Update extends Base ) ], )) - ->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true) - ->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true) - ->param('endpoint', null, new Nullable(new Text(256, 0)), 'Domain of FusionAuth instance. For example: example.fusionauth.io', optional: true) - ->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true) + ->param(static::getClientIdParamName(), null, new Text(256, 0), static::getClientIdDescription()) + ->param(static::getClientSecretParamName(), null, new Text(512, 0), static::getClientSecretDescription()) + ->param('endpoint', null, new Text(256, 0), 'Domain of FusionAuth instance. For example: example.fusionauth.io') + ->param('enabled', false, new Boolean(), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.') ->inject('response') ->inject('dbForPlatform') ->inject('project') @@ -136,10 +135,10 @@ class Update extends Base * differently to avoid an LSP-incompatible override of Base::action(). */ public function handle( - ?string $clientId, - ?string $clientSecret, - ?string $endpoint, - ?bool $enabled, + string $clientId, + string $clientSecret, + string $endpoint, + bool $enabled, Response $response, Database $dbForPlatform, Document $project, @@ -149,18 +148,9 @@ class Update extends Base $providerId = static::getProviderId(); $queueForEvents->setParam('providerId', $providerId); - // The secret is stored as JSON `{"clientSecret": "...", "fusionAuthDomain": "..."}` - // to match the shape FusionAuth's OAuth2 adapter expects (getFusionAuthDomain()). - // The `endpoint` param is optional; if omitted, the existing stored endpoint is preserved. - // `clientSecret` is optional; if omitted, the existing stored secret is preserved. - $storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? ''; - $existing = []; - if (!empty($storedRaw)) { - $existing = \json_decode($storedRaw, true) ?: []; - } $encodedSecret = \json_encode([ - 'clientSecret' => $clientSecret ?? ($existing['clientSecret'] ?? ''), - 'fusionAuthDomain' => $endpoint ?? ($existing['fusionAuthDomain'] ?? ''), + 'clientSecret' => $clientSecret, + 'fusionAuthDomain' => $endpoint, ]); $project = $this->persistCredentials($project, $dbForPlatform, $authorization, $clientId, $encodedSecret, $enabled); diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Gitlab/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Gitlab/Update.php index 804f6354ae..577edfc3ac 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Gitlab/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Gitlab/Update.php @@ -14,7 +14,6 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; use Utopia\Validator\Boolean; -use Utopia\Validator\Nullable; use Utopia\Validator\Text; use Utopia\Validator\URL; @@ -93,7 +92,7 @@ class Update extends Base $providerLabel = static::getProviderLabel(); $this - ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) + ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) // Behaves as PUT ->setHttpPath('/v1/project/oauth2/' . $providerId) ->desc('Update project OAuth2 ' . $providerLabel) ->groups(['api', 'project']) @@ -114,10 +113,10 @@ class Update extends Base ) ], )) - ->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true) - ->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true) - ->param('endpoint', null, new Nullable(new URL(allowEmpty: true)), 'Endpoint URL of self-hosted GitLab instance. For example: https://gitlab.com', optional: true) - ->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true) + ->param(static::getClientIdParamName(), null, new Text(256, 0), static::getClientIdDescription()) + ->param(static::getClientSecretParamName(), null, new Text(512, 0), static::getClientSecretDescription()) + ->param('endpoint', null, new URL(allowEmpty: true), 'Endpoint URL of self-hosted GitLab instance. For example: https://gitlab.com') + ->param('enabled', false, new Boolean(), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.') ->inject('response') ->inject('dbForPlatform') ->inject('project') @@ -147,10 +146,10 @@ class Update extends Base * differently to avoid an LSP-incompatible override of Base::action(). */ public function handle( - ?string $applicationId, - ?string $secret, - ?string $endpoint, - ?bool $enabled, + string $applicationId, + string $secret, + string $endpoint, + bool $enabled, Response $response, Database $dbForPlatform, Document $project, @@ -160,22 +159,10 @@ class Update extends Base $providerId = static::getProviderId(); $queueForEvents->setParam('providerId', $providerId); - // The secret is stored as JSON `{"clientSecret": "...", "endpoint": "..."}` - // so that the Gitlab OAuth2 adapter can extract the endpoint via getEndpoint(). - // Merge the new values with what's already stored so that submitting only - // one of `secret`/`endpoint` leaves the other untouched. - $encodedSecret = null; - if (!\is_null($secret) || !\is_null($endpoint)) { - $storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? ''; - $existing = []; - if (!empty($storedRaw)) { - $existing = \json_decode($storedRaw, true) ?: []; - } - $encodedSecret = \json_encode([ - 'clientSecret' => $secret ?? ($existing['clientSecret'] ?? ''), - 'endpoint' => $endpoint ?? ($existing['endpoint'] ?? ''), - ]); - } + $encodedSecret = \json_encode([ + 'clientSecret' => $secret, + 'endpoint' => $endpoint, + ]); $project = $this->persistCredentials($project, $dbForPlatform, $authorization, $applicationId, $encodedSecret, $enabled); diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Google/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Google/Update.php index 2a061d09ce..ab9c82de20 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Google/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Google/Update.php @@ -16,7 +16,6 @@ use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; use Utopia\Validator\ArrayList; use Utopia\Validator\Boolean; -use Utopia\Validator\Nullable; use Utopia\Validator\Text; use Utopia\Validator\WhiteList; @@ -85,7 +84,7 @@ class Update extends Base $providerLabel = static::getProviderLabel(); $this - ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) + ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) // Behaves as PUT ->setHttpPath('/v1/project/oauth2/' . $providerId) ->desc('Update project OAuth2 ' . $providerLabel) ->groups(['api', 'project']) @@ -106,10 +105,10 @@ class Update extends Base ) ], )) - ->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true) - ->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true) - ->param('prompt', null, new Nullable(new ArrayList(new WhiteList(['none', 'consent', 'select_account'], true), 3)), 'Array of Google OAuth2 prompt values. If "none" is included, it must be the only element. "none" means: don\'t display any authentication or consent screens. Must not be specified with other values. "consent" means: prompt the user for consent. "select_account" means: prompt the user to select an account.', optional: true) - ->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true) + ->param(static::getClientIdParamName(), null, new Text(256, 0), static::getClientIdDescription()) + ->param(static::getClientSecretParamName(), null, new Text(512, 0), static::getClientSecretDescription()) + ->param('prompt', null, new ArrayList(new WhiteList(['none', 'consent', 'select_account'], true), 3), 'Array of Google OAuth2 prompt values. If "none" is included, it must be the only element. "none" means: don\'t display any authentication or consent screens. Must not be specified with other values. "consent" means: prompt the user for consent. "select_account" means: prompt the user to select an account.') + ->param('enabled', false, new Boolean(), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.') ->inject('response') ->inject('dbForPlatform') ->inject('project') @@ -139,10 +138,10 @@ class Update extends Base * differently to avoid an LSP-incompatible override of Base::action(). */ public function handle( - ?string $clientId, - ?string $clientSecret, - ?array $prompt, - ?bool $enabled, + string $clientId, + string $clientSecret, + array $prompt, + bool $enabled, Response $response, Database $dbForPlatform, Document $project, @@ -152,28 +151,17 @@ class Update extends Base $providerId = static::getProviderId(); $queueForEvents->setParam('providerId', $providerId); - if ($prompt !== null) { - if (empty($prompt)) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Prompt array cannot be empty.'); - } - - if (\in_array('none', $prompt) && \count($prompt) > 1) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'When "none" is used as a prompt value, it must be the only element in the array.'); - } + if (empty($prompt)) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Prompt array cannot be empty.'); } - $storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? ''; - $existing = $this->decodeStoredSecret($project); - - // Backwards compatibility: secrets stored before the prompt feature - // were saved as plain strings. Treat the raw value as clientSecret. - if (!empty($storedRaw) && empty($existing)) { - $existing = ['clientSecret' => $storedRaw]; + if (\in_array('none', $prompt) && \count($prompt) > 1) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'When "none" is used as a prompt value, it must be the only element in the array.'); } $encodedSecret = \json_encode([ - 'clientSecret' => $clientSecret ?? ($existing['clientSecret'] ?? ''), - 'prompt' => $prompt ?? ($existing['prompt'] ?? ['consent']), + 'clientSecret' => $clientSecret, + 'prompt' => $prompt, ]); $project = $this->persistCredentials($project, $dbForPlatform, $authorization, $clientId, $encodedSecret, $enabled); diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Keycloak/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Keycloak/Update.php index aa41e8a5e9..e20faacdab 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Keycloak/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Keycloak/Update.php @@ -14,7 +14,6 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; use Utopia\Validator\Boolean; -use Utopia\Validator\Nullable; use Utopia\Validator\Text; class Update extends Base @@ -88,7 +87,7 @@ class Update extends Base $providerLabel = static::getProviderLabel(); $this - ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) + ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) // Behaves as PUT ->setHttpPath('/v1/project/oauth2/' . $providerId) ->desc('Update project OAuth2 ' . $providerLabel) ->groups(['api', 'project']) @@ -109,11 +108,11 @@ class Update extends Base ) ], )) - ->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true) - ->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true) - ->param('endpoint', null, new Nullable(new Text(256, 0)), 'Domain of Keycloak instance. For example: keycloak.example.com', optional: true) - ->param('realmName', null, new Nullable(new Text(256, 0)), 'Keycloak realm name. For example: appwrite-realm', optional: true) - ->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true) + ->param(static::getClientIdParamName(), null, new Text(256, 0), static::getClientIdDescription()) + ->param(static::getClientSecretParamName(), null, new Text(512, 0), static::getClientSecretDescription()) + ->param('endpoint', null, new Text(256, 0), 'Domain of Keycloak instance. For example: keycloak.example.com') + ->param('realmName', null, new Text(256, 0), 'Keycloak realm name. For example: appwrite-realm') + ->param('enabled', false, new Boolean(), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.') ->inject('response') ->inject('dbForPlatform') ->inject('project') @@ -145,11 +144,11 @@ class Update extends Base * Base::action(). */ public function handle( - ?string $clientId, - ?string $clientSecret, - ?string $endpoint, - ?string $realmName, - ?bool $enabled, + string $clientId, + string $clientSecret, + string $endpoint, + string $realmName, + bool $enabled, Response $response, Database $dbForPlatform, Document $project, @@ -159,19 +158,10 @@ class Update extends Base $providerId = static::getProviderId(); $queueForEvents->setParam('providerId', $providerId); - // The secret is stored as JSON `{"clientSecret": "...", "keycloakDomain": "...", "keycloakRealm": "..."}` - // to match the shape Keycloak's OAuth2 adapter expects (getKeycloakDomain(), getKeycloakRealm()). - // The `endpoint` and `realmName` params are optional; if omitted, existing stored values are preserved. - // `clientSecret` is optional; if omitted, the existing stored secret is preserved. - $storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? ''; - $existing = []; - if (!empty($storedRaw)) { - $existing = \json_decode($storedRaw, true) ?: []; - } $encodedSecret = \json_encode([ - 'clientSecret' => $clientSecret ?? ($existing['clientSecret'] ?? ''), - 'keycloakDomain' => $endpoint ?? ($existing['keycloakDomain'] ?? ''), - 'keycloakRealm' => $realmName ?? ($existing['keycloakRealm'] ?? ''), + 'clientSecret' => $clientSecret, + 'keycloakDomain' => $endpoint, + 'keycloakRealm' => $realmName, ]); $project = $this->persistCredentials($project, $dbForPlatform, $authorization, $clientId, $encodedSecret, $enabled); diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Microsoft/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Microsoft/Update.php index 811819a05c..d69306fa12 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Microsoft/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Microsoft/Update.php @@ -14,7 +14,6 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; use Utopia\Validator\Boolean; -use Utopia\Validator\Nullable; use Utopia\Validator\Text; class Update extends Base @@ -92,7 +91,7 @@ class Update extends Base $providerLabel = static::getProviderLabel(); $this - ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) + ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) // Behaves as PUT ->setHttpPath('/v1/project/oauth2/' . $providerId) ->desc('Update project OAuth2 ' . $providerLabel) ->groups(['api', 'project']) @@ -113,10 +112,10 @@ class Update extends Base ) ], )) - ->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true) - ->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true) - ->param('tenant', null, new Nullable(new Text(256, 0)), 'Microsoft Entra ID tenant identifier. Use \'common\', \'organizations\', \'consumers\' or a specific tenant ID. For example: common', true) - ->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true) + ->param(static::getClientIdParamName(), null, new Text(256, 0), static::getClientIdDescription()) + ->param(static::getClientSecretParamName(), null, new Text(512, 0), static::getClientSecretDescription()) + ->param('tenant', null, new Text(256, 0), 'Microsoft Entra ID tenant identifier. Use \'common\', \'organizations\', \'consumers\' or a specific tenant ID. For example: common') + ->param('enabled', false, new Boolean(), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.') ->inject('response') ->inject('dbForPlatform') ->inject('project') @@ -146,10 +145,10 @@ class Update extends Base * differently to avoid an LSP-incompatible override of Base::action(). */ public function handle( - ?string $applicationId, - ?string $applicationSecret, - ?string $tenant, - ?bool $enabled, + string $applicationId, + string $applicationSecret, + string $tenant, + bool $enabled, Response $response, Database $dbForPlatform, Document $project, @@ -159,18 +158,9 @@ class Update extends Base $providerId = static::getProviderId(); $queueForEvents->setParam('providerId', $providerId); - // The secret is stored as JSON `{"clientSecret": "...", "tenantID": "..."}` - // to match the shape Microsoft's OAuth2 adapter expects (getTenantID()). - // The `tenant` param is optional; if omitted, the existing stored tenant is preserved. - // `applicationSecret` is optional; if omitted, the existing stored secret is preserved. - $storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? ''; - $existing = []; - if (!empty($storedRaw)) { - $existing = \json_decode($storedRaw, true) ?: []; - } $encodedSecret = \json_encode([ - 'clientSecret' => $applicationSecret ?? ($existing['clientSecret'] ?? ''), - 'tenantID' => $tenant ?? ($existing['tenantID'] ?? ''), + 'clientSecret' => $applicationSecret, + 'tenantID' => $tenant, ]); $project = $this->persistCredentials($project, $dbForPlatform, $authorization, $applicationId, $encodedSecret, $enabled); diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Oidc/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Oidc/Update.php index 697b306be8..4fee327455 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Oidc/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Oidc/Update.php @@ -15,7 +15,6 @@ use Utopia\Database\Database; use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; use Utopia\Validator\Boolean; -use Utopia\Validator\Nullable; use Utopia\Validator\Text; use Utopia\Validator\URL; @@ -102,7 +101,7 @@ class Update extends Base $providerLabel = static::getProviderLabel(); $this - ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) + ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) // Behaves as PUT ->setHttpPath('/v1/project/oauth2/' . $providerId) ->desc('Update project OAuth2 ' . $providerLabel) ->groups(['api', 'project']) @@ -123,13 +122,13 @@ class Update extends Base ) ], )) - ->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true) - ->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true) - ->param('wellKnownURL', null, new Nullable(new URL(allowEmpty: true)), 'OpenID Connect well-known configuration URL. When provided, authorization, token, and user info endpoints can be discovered automatically. For example: https://myoauth.com/.well-known/openid-configuration', optional: true) - ->param('authorizationURL', null, new Nullable(new URL(allowEmpty: true)), 'OpenID Connect authorization endpoint URL. Required when wellKnownURL is not provided. For example: https://myoauth.com/oauth2/authorize', optional: true) - ->param('tokenURL', null, new Nullable(new URL(allowEmpty: true)), 'OpenID Connect token endpoint URL. Required when wellKnownURL is not provided. For example: https://myoauth.com/oauth2/token', optional: true, aliases: ['tokenUrl']) - ->param('userInfoURL', null, new Nullable(new URL(allowEmpty: true)), 'OpenID Connect user info endpoint URL. Required when wellKnownURL is not provided. For example: https://myoauth.com/oauth2/userinfo', optional: true, aliases: ['userInfoUrl']) - ->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true) + ->param(static::getClientIdParamName(), null, new Text(256, 0), static::getClientIdDescription()) + ->param(static::getClientSecretParamName(), null, new Text(512, 0), static::getClientSecretDescription()) + ->param('wellKnownURL', null, new URL(allowEmpty: true), 'OpenID Connect well-known configuration URL. When provided, authorization, token, and user info endpoints can be discovered automatically. For example: https://myoauth.com/.well-known/openid-configuration') + ->param('authorizationURL', null, new URL(allowEmpty: true), 'OpenID Connect authorization endpoint URL. Required when wellKnownURL is not provided. For example: https://myoauth.com/oauth2/authorize') + ->param('tokenURL', null, new URL(allowEmpty: true), 'OpenID Connect token endpoint URL. Required when wellKnownURL is not provided. For example: https://myoauth.com/oauth2/token', aliases: ['tokenUrl']) + ->param('userInfoURL', null, new URL(allowEmpty: true), 'OpenID Connect user info endpoint URL. Required when wellKnownURL is not provided. For example: https://myoauth.com/oauth2/userinfo', aliases: ['userInfoUrl']) + ->param('enabled', false, new Boolean(), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.') ->inject('response') ->inject('dbForPlatform') ->inject('project') @@ -170,13 +169,13 @@ class Update extends Base * that were configured previously. */ public function handle( - ?string $clientId, - ?string $clientSecret, - ?string $wellKnownURL, - ?string $authorizationURL, - ?string $tokenURL, - ?string $userInfoURL, - ?bool $enabled, + string $clientId, + string $clientSecret, + string $wellKnownURL, + string $authorizationURL, + string $tokenURL, + string $userInfoURL, + bool $enabled, Response $response, Database $dbForPlatform, Document $project, @@ -186,41 +185,25 @@ class Update extends Base $providerId = static::getProviderId(); $queueForEvents->setParam('providerId', $providerId); - // The secret is stored as JSON - // `{"clientSecret": "...", "wellKnownEndpoint": "...", "authorizationEndpoint": "...", "tokenEndpoint": "...", "userInfoEndpoint": "..."}` - // so that the OIDC OAuth2 adapter can extract each endpoint individually. - // Merge new values with what's already stored so that submitting only a - // subset of fields leaves the others untouched. - $storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? ''; - $existing = []; - if (!empty($storedRaw)) { - $existing = \json_decode($storedRaw, true) ?: []; - } + $encodedSecret = \json_encode([ + 'clientSecret' => $clientSecret, + 'wellKnownEndpoint' => $wellKnownURL, + 'authorizationEndpoint' => $authorizationURL, + 'tokenEndpoint' => $tokenURL, + 'userInfoEndpoint' => $userInfoURL, + ]); - $merged = [ - 'clientSecret' => $clientSecret ?? ($existing['clientSecret'] ?? ''), - 'wellKnownEndpoint' => $wellKnownURL ?? ($existing['wellKnownEndpoint'] ?? ''), - 'authorizationEndpoint' => $authorizationURL ?? ($existing['authorizationEndpoint'] ?? ''), - 'tokenEndpoint' => $tokenURL ?? ($existing['tokenEndpoint'] ?? ''), - 'userInfoEndpoint' => $userInfoURL ?? ($existing['userInfoEndpoint'] ?? ''), - ]; - - // When enabling, require either wellKnownEndpoint alone, or all three - // discovery URLs (authorization, token, user info). Skip this check - // when disabling or when leaving the enabled flag unchanged. if ($enabled === true) { - $hasWellKnown = !empty($merged['wellKnownEndpoint']); - $hasAllDiscovery = !empty($merged['authorizationEndpoint']) - && !empty($merged['tokenEndpoint']) - && !empty($merged['userInfoEndpoint']); + $hasWellKnown = !empty($wellKnownURL); + $hasAllDiscovery = !empty($authorizationURL) + && !empty($tokenURL) + && !empty($userInfoURL); if (!$hasWellKnown && !$hasAllDiscovery) { throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Enabling OpenID Connect requires either wellKnownURL, or all of authorizationURL, tokenURL, and userInfoURL.'); } } - $encodedSecret = \json_encode($merged); - $project = $this->persistCredentials($project, $dbForPlatform, $authorization, $clientId, $encodedSecret, $enabled); // Reuse buildReadResponse to keep PATCH/GET shapes identical and diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Okta/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Okta/Update.php index 0344b6a14a..4d2f1fd6b1 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Okta/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/OAuth2/Okta/Update.php @@ -16,7 +16,6 @@ use Utopia\Database\Document; use Utopia\Database\Validator\Authorization; use Utopia\Validator\Boolean; use Utopia\Validator\Domain as ValidatorDomain; -use Utopia\Validator\Nullable; use Utopia\Validator\Text; class Update extends Base @@ -90,7 +89,7 @@ class Update extends Base $providerLabel = static::getProviderLabel(); $this - ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) + ->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) // Behaves as PUT ->setHttpPath('/v1/project/oauth2/' . $providerId) ->desc('Update project OAuth2 ' . $providerLabel) ->groups(['api', 'project']) @@ -111,11 +110,11 @@ class Update extends Base ) ], )) - ->param(static::getClientIdParamName(), null, new Nullable(new Text(256, 0)), static::getClientIdDescription(), optional: true) - ->param(static::getClientSecretParamName(), null, new Nullable(new Text(512, 0)), static::getClientSecretDescription(), optional: true) - ->param('domain', null, new Nullable(new ValidatorDomain(allowEmpty: true)), 'Okta company domain. Required when enabling the provider. For example: trial-6400025.okta.com. Example of wrong value: trial-6400025-admin.okta.com, or https://trial-6400025.okta.com/', optional: true) - ->param('authorizationServerId', null, new Nullable(new Text(256, 0)), 'Custom Authorization Servers. Optional, can be left empty or unconfigured. For example: aus000000000000000h7z', optional: true) - ->param('enabled', null, new Nullable(new Boolean()), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.', true) + ->param(static::getClientIdParamName(), null, new Text(256, 0), static::getClientIdDescription()) + ->param(static::getClientSecretParamName(), null, new Text(512, 0), static::getClientSecretDescription()) + ->param('domain', null, new ValidatorDomain(allowEmpty: true), 'Okta company domain. Required when enabling the provider. For example: trial-6400025.okta.com. Example of wrong value: trial-6400025-admin.okta.com, or https://trial-6400025.okta.com/') + ->param('authorizationServerId', null, new Text(256, 0), 'Custom Authorization Servers. Optional, can be left empty or unconfigured. For example: aus000000000000000h7z') + ->param('enabled', false, new Boolean(), 'OAuth2 sign-in method status. Set to true to enable new session creation. Setting to true will trigger end-to-end credentials validation, and will throw if the credentials are invalid.') ->inject('response') ->inject('dbForPlatform') ->inject('project') @@ -147,11 +146,11 @@ class Update extends Base * Base::action(). */ public function handle( - ?string $clientId, - ?string $clientSecret, - ?string $domain, - ?string $authorizationServerId, - ?bool $enabled, + string $clientId, + string $clientSecret, + string $domain, + string $authorizationServerId, + bool $enabled, Response $response, Database $dbForPlatform, Document $project, @@ -161,32 +160,14 @@ class Update extends Base $providerId = static::getProviderId(); $queueForEvents->setParam('providerId', $providerId); - // The secret is stored as JSON `{"clientSecret": "...", "oktaDomain": "...", "authorizationServerId": "..."}` - // to match the shape Okta's OAuth2 adapter expects. - // Merge new values with existing storage so that submitting only some of - // the parameters leaves the others untouched. - $storedRaw = $project->getAttribute('oAuthProviders', [])[$providerId . 'Secret'] ?? ''; - $existing = []; - if (!empty($storedRaw)) { - $existing = \json_decode($storedRaw, true) ?: []; - } + $encodedSecret = \json_encode([ + 'clientSecret' => $clientSecret, + 'oktaDomain' => $domain, + 'authorizationServerId' => $authorizationServerId, + ]); - $encodedSecret = null; - if (!\is_null($clientSecret) || !\is_null($domain) || !\is_null($authorizationServerId)) { - $encodedSecret = \json_encode([ - 'clientSecret' => $clientSecret ?? ($existing['clientSecret'] ?? ''), - 'oktaDomain' => $domain ?? ($existing['oktaDomain'] ?? ''), - 'authorizationServerId' => $authorizationServerId ?? ($existing['authorizationServerId'] ?? ''), - ]); - } - - // Domain is required when enabling the provider, since Okta builds its - // authorization, token and userinfo URLs from it. - if ($enabled === true) { - $effectiveDomain = $domain ?? ($existing['oktaDomain'] ?? ''); - if (empty($effectiveDomain)) { - throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain is required when enabling Okta OAuth2 provider.'); - } + if ($enabled === true && empty($domain)) { + throw new Exception(Exception::GENERAL_ARGUMENT_INVALID, 'Domain is required when enabling Okta OAuth2 provider.'); } $project = $this->persistCredentials($project, $dbForPlatform, $authorization, $clientId, $encodedSecret, $enabled); diff --git a/src/Appwrite/Platform/Modules/Project/Http/Project/Templates/Email/Update.php b/src/Appwrite/Platform/Modules/Project/Http/Project/Templates/Email/Update.php index f1451c5413..914bfc4c56 100644 --- a/src/Appwrite/Platform/Modules/Project/Http/Project/Templates/Email/Update.php +++ b/src/Appwrite/Platform/Modules/Project/Http/Project/Templates/Email/Update.php @@ -30,7 +30,7 @@ class Update extends Action public function __construct() { - $this->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) // Should be PUT + $this->setHttpMethod(Action::HTTP_REQUEST_METHOD_PATCH) // Behaves as PUT ->setHttpPath('/v1/project/templates/email') ->httpAlias('/v1/projects/:projectId/templates/email') ->httpAlias('/v1/projects/:projectId/templates/email/:templateId/:locale')