rhertogh /
yii2-oauth2-server
| 1 | <?php |
||||
| 2 | |||||
| 3 | namespace rhertogh\Yii2Oauth2Server\models; |
||||
| 4 | |||||
| 5 | use rhertogh\Yii2Oauth2Server\helpers\DiHelper; |
||||
| 6 | use rhertogh\Yii2Oauth2Server\helpers\EnvironmentHelper; |
||||
| 7 | use rhertogh\Yii2Oauth2Server\helpers\exceptions\EnvironmentVariableNotAllowedException; |
||||
| 8 | use rhertogh\Yii2Oauth2Server\helpers\exceptions\EnvironmentVariableNotSetException; |
||||
| 9 | use rhertogh\Yii2Oauth2Server\interfaces\models\Oauth2ClientInterface; |
||||
| 10 | use rhertogh\Yii2Oauth2Server\interfaces\models\Oauth2ClientScopeInterface; |
||||
| 11 | use rhertogh\Yii2Oauth2Server\interfaces\models\Oauth2ScopeInterface; |
||||
| 12 | use rhertogh\Yii2Oauth2Server\models\behaviors\DateTimeBehavior; |
||||
| 13 | use rhertogh\Yii2Oauth2Server\models\queries\Oauth2ClientScopeQuery; |
||||
| 14 | use rhertogh\Yii2Oauth2Server\models\traits\Oauth2EnabledTrait; |
||||
| 15 | use rhertogh\Yii2Oauth2Server\models\traits\Oauth2EntityIdentifierTrait; |
||||
| 16 | use rhertogh\Yii2Oauth2Server\Oauth2Module; |
||||
| 17 | use Yii; |
||||
| 18 | use yii\base\Exception; |
||||
| 19 | use yii\base\InvalidArgumentException; |
||||
| 20 | use yii\base\InvalidConfigException; |
||||
| 21 | use yii\base\UnknownPropertyException; |
||||
| 22 | use yii\helpers\ArrayHelper; |
||||
| 23 | use yii\helpers\Json; |
||||
| 24 | |||||
| 25 | class Oauth2Client extends base\Oauth2Client implements Oauth2ClientInterface |
||||
| 26 | { |
||||
| 27 | use Oauth2EntityIdentifierTrait; |
||||
| 28 | use Oauth2EnabledTrait; |
||||
| 29 | |||||
| 30 | protected const ENCRYPTED_ATTRIBUTES = ['secret', 'old_secret']; |
||||
| 31 | |||||
| 32 | /** |
||||
| 33 | * @var Oauth2Module |
||||
| 34 | */ |
||||
| 35 | protected $module; |
||||
| 36 | |||||
| 37 | /** |
||||
| 38 | * Minimum length for new client secrets. |
||||
| 39 | * @var int |
||||
| 40 | */ |
||||
| 41 | protected $minimumSecretLength = 10; |
||||
| 42 | |||||
| 43 | ///////////////////////////// |
||||
| 44 | /// ActiveRecord Settings /// |
||||
| 45 | ///////////////////////////// |
||||
| 46 | |||||
| 47 | /** |
||||
| 48 | * @inheritDoc |
||||
| 49 | */ |
||||
| 50 | 65 | public function behaviors() |
|||
| 51 | { |
||||
| 52 | 65 | return ArrayHelper::merge(parent::behaviors(), [ |
|||
| 53 | 65 | 'dateTimeBehavior' => DateTimeBehavior::class |
|||
| 54 | 65 | ]); |
|||
| 55 | } |
||||
| 56 | |||||
| 57 | /** |
||||
| 58 | * @inheritDoc |
||||
| 59 | */ |
||||
| 60 | 1 | public function rules() |
|||
| 61 | { |
||||
| 62 | 1 | return ArrayHelper::merge(parent::rules(), [ |
|||
| 63 | 1 | [ |
|||
| 64 | 1 | ['secret'], |
|||
| 65 | 1 | 'required', |
|||
| 66 | 1 | 'when' => fn(self $model) => $model->isConfidential(), |
|||
| 67 | 1 | ], |
|||
| 68 | 1 | ]); |
|||
| 69 | } |
||||
| 70 | |||||
| 71 | ///////////////////////// |
||||
| 72 | /// Getters & Setters /// |
||||
| 73 | ///////////////////////// |
||||
| 74 | |||||
| 75 | /** |
||||
| 76 | * @inheritdoc |
||||
| 77 | */ |
||||
| 78 | 66 | public function __set($name, $value) |
|||
| 79 | { |
||||
| 80 | 66 | if ($name === 'secret') { // Don't allow setting the secret via magic method. |
|||
| 81 | 1 | throw new UnknownPropertyException('For security the "secret" property must be set via setSecret()'); |
|||
| 82 | } else { |
||||
| 83 | 65 | parent::__set($name, $value); |
|||
| 84 | } |
||||
| 85 | } |
||||
| 86 | |||||
| 87 | /** |
||||
| 88 | * @inheritDoc |
||||
| 89 | */ |
||||
| 90 | 8 | public function getModule(): Oauth2Module |
|||
| 91 | { |
||||
| 92 | 8 | return $this->module; |
|||
| 93 | } |
||||
| 94 | |||||
| 95 | /** |
||||
| 96 | * @inheritDoc |
||||
| 97 | */ |
||||
| 98 | 52 | public function setModule($module) |
|||
| 99 | { |
||||
| 100 | 52 | $this->module = $module; |
|||
| 101 | 52 | return $this; |
|||
| 102 | } |
||||
| 103 | |||||
| 104 | /** |
||||
| 105 | * @inheritdoc |
||||
| 106 | */ |
||||
| 107 | 4 | public function getName() |
|||
| 108 | { |
||||
| 109 | 4 | return $this->name; |
|||
| 110 | } |
||||
| 111 | |||||
| 112 | /** |
||||
| 113 | * @inheritdoc |
||||
| 114 | */ |
||||
| 115 | 1 | public function setName($name) |
|||
| 116 | { |
||||
| 117 | 1 | $this->name = $name; |
|||
| 118 | 1 | return $this; |
|||
| 119 | } |
||||
| 120 | |||||
| 121 | /** |
||||
| 122 | * @inheritdoc |
||||
| 123 | */ |
||||
| 124 | 1 | public function getType() |
|||
| 125 | { |
||||
| 126 | 1 | return $this->type; |
|||
| 127 | } |
||||
| 128 | |||||
| 129 | /** |
||||
| 130 | * @inheritdoc |
||||
| 131 | */ |
||||
| 132 | 2 | public function setType($type) |
|||
| 133 | { |
||||
| 134 | 2 | if (!in_array($type, Oauth2ClientInterface::TYPES)) { |
|||
| 135 | 1 | throw new InvalidArgumentException('Unknown type "' . $type . '".'); |
|||
| 136 | } |
||||
| 137 | |||||
| 138 | 1 | $this->type = $type; |
|||
| 139 | 1 | return $this; |
|||
| 140 | } |
||||
| 141 | |||||
| 142 | /** |
||||
| 143 | * @inheritdoc |
||||
| 144 | * @throws InvalidConfigException |
||||
| 145 | * @throws EnvironmentVariableNotSetException |
||||
| 146 | * @throws EnvironmentVariableNotAllowedException |
||||
| 147 | */ |
||||
| 148 | 15 | public function getRedirectUri() |
|||
| 149 | { |
||||
| 150 | 15 | return $this->getUrisAttribute('redirect_uris'); |
|||
| 151 | } |
||||
| 152 | |||||
| 153 | /** |
||||
| 154 | * @inheritdoc |
||||
| 155 | * @throws InvalidConfigException |
||||
| 156 | * @throws EnvironmentVariableNotSetException |
||||
| 157 | * @throws EnvironmentVariableNotAllowedException |
||||
| 158 | */ |
||||
| 159 | public function getPostLogoutRedirectUris() |
||||
| 160 | { |
||||
| 161 | return $this->getUrisAttribute('post_logout_redirect_uris'); |
||||
| 162 | } |
||||
| 163 | |||||
| 164 | /** |
||||
| 165 | * @param string $attribute |
||||
| 166 | * @return string[] |
||||
| 167 | * @throws InvalidConfigException |
||||
| 168 | * @throws EnvironmentVariableNotSetException |
||||
| 169 | * @throws EnvironmentVariableNotAllowedException |
||||
| 170 | */ |
||||
| 171 | 15 | protected function getUrisAttribute($attribute) |
|||
| 172 | { |
||||
| 173 | 15 | $uris = $this->$attribute; |
|||
| 174 | 15 | if (empty($uris)) { |
|||
| 175 | 1 | return []; |
|||
| 176 | } |
||||
| 177 | |||||
| 178 | // Compatibility with DBMSs that don't support JSON data type. |
||||
| 179 | 14 | if (is_string($uris)) { |
|||
| 180 | try { |
||||
| 181 | 11 | $uris = Json::decode($uris); |
|||
| 182 | 1 | } catch (InvalidArgumentException $e) { |
|||
| 183 | 1 | throw new InvalidConfigException('Invalid json in `' . $attribute . '` for client ' . $this->id, 0, $e); |
|||
| 184 | } |
||||
| 185 | } |
||||
| 186 | |||||
| 187 | 13 | $redirectUrisEnvVarConfig = $this->getRedirectUrisEnvVarConfig(); |
|||
| 188 | 13 | if ($redirectUrisEnvVarConfig && version_compare(PHP_VERSION, '8.1.0', '<')) { |
|||
| 189 | // PHP < 8.1 can only handle indexed array when unpacking. |
||||
| 190 | 6 | $redirectUrisEnvVarConfig = array_values($redirectUrisEnvVarConfig); |
|||
| 191 | } |
||||
| 192 | |||||
| 193 | 13 | if (is_string($uris)) { |
|||
| 194 | 4 | if ($redirectUrisEnvVarConfig && preg_match('/^\${[a-zA-Z0-9_]+}$/', $uris)) { |
|||
| 195 | 3 | $uris = EnvironmentHelper::parseEnvVars($uris, ...$redirectUrisEnvVarConfig); |
|||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||
| 196 | try { |
||||
| 197 | // Try to parse the content of the environment variable(s) as JSON. |
||||
| 198 | 3 | $uris = Json::decode($uris); |
|||
| 199 | 1 | } catch (InvalidArgumentException $e) { |
|||
| 200 | // Use as plain text if it failed. |
||||
| 201 | } |
||||
| 202 | } |
||||
| 203 | } |
||||
| 204 | |||||
| 205 | 13 | if (is_string($uris)) { |
|||
| 206 | 3 | $uris = [$uris]; |
|||
| 207 | 10 | } elseif (is_array($uris)) { |
|||
| 208 | 9 | $uris = array_values($uris); |
|||
| 209 | } else { |
||||
| 210 | 1 | throw new InvalidConfigException('`' . $attribute . '` must be a JSON encoded string or array of strings.'); |
|||
| 211 | } |
||||
| 212 | |||||
| 213 | 12 | foreach ($uris as $key => $uri) { |
|||
| 214 | 12 | if (!is_string($uri)) { |
|||
| 215 | 1 | throw new InvalidConfigException('`' . $attribute . '` must be a JSON encoded string or array of strings.'); // phpcs:ignore Generic.Files.LineLength.TooLong |
|||
| 216 | } |
||||
| 217 | 11 | if ($redirectUrisEnvVarConfig) { |
|||
| 218 | 6 | $uris[$key] = EnvironmentHelper::parseEnvVars($uri, ...$redirectUrisEnvVarConfig); |
|||
| 219 | 5 | if (!$uris[$key]) { |
|||
| 220 | 1 | unset($uris[$key]); |
|||
| 221 | } |
||||
| 222 | } |
||||
| 223 | } |
||||
| 224 | 10 | return array_values($uris); // Re-index array in case elements were removed. |
|||
| 225 | } |
||||
| 226 | |||||
| 227 | /** |
||||
| 228 | * @inheritDoc |
||||
| 229 | */ |
||||
| 230 | 4 | public function setRedirectUri($uri) |
|||
| 231 | { |
||||
| 232 | 4 | return $this->setUrisAttribute('redirect_uris', $uri); |
|||
| 233 | } |
||||
| 234 | |||||
| 235 | /** |
||||
| 236 | * @inheritDoc |
||||
| 237 | */ |
||||
| 238 | public function setPostLogoutRedirectUris($uri) |
||||
| 239 | { |
||||
| 240 | return $this->setUrisAttribute('post_logout_redirect_uris', $uri); |
||||
| 241 | } |
||||
| 242 | |||||
| 243 | /** |
||||
| 244 | * @param string $attribute |
||||
| 245 | * @param string|string[]|null $uri |
||||
| 246 | * @return $this |
||||
| 247 | */ |
||||
| 248 | 4 | protected function setUrisAttribute($attribute, $uri) |
|||
| 249 | { |
||||
| 250 | 4 | if (is_array($uri)) { |
|||
| 251 | 2 | foreach ($uri as $value) { |
|||
| 252 | 2 | if (!is_string($value)) { |
|||
| 253 | 1 | throw new InvalidArgumentException('When $uri is an array, its values must be strings.'); |
|||
| 254 | } |
||||
| 255 | } |
||||
| 256 | 1 | $uri = Json::encode($uri); |
|||
| 257 | 2 | } elseif (is_string($uri)) { |
|||
| 258 | 1 | $uri = Json::encode([$uri]); |
|||
| 259 | 1 | } elseif ($uri !== null) { |
|||
|
0 ignored issues
–
show
|
|||||
| 260 | 1 | throw new InvalidArgumentException('$uri must be a string or an array, got: ' . gettype($uri)); |
|||
| 261 | } |
||||
| 262 | |||||
| 263 | 2 | $this->$attribute = $uri; |
|||
| 264 | |||||
| 265 | 2 | return $this; |
|||
| 266 | } |
||||
| 267 | |||||
| 268 | /** |
||||
| 269 | * @inheritdoc |
||||
| 270 | */ |
||||
| 271 | 18 | public function getEnvVarConfig() |
|||
| 272 | { |
||||
| 273 | 18 | return $this->env_var_config; |
|||
| 274 | } |
||||
| 275 | |||||
| 276 | /** |
||||
| 277 | * @inheritdoc |
||||
| 278 | */ |
||||
| 279 | 9 | public function setEnvVarConfig($config) |
|||
| 280 | { |
||||
| 281 | 9 | $this->env_var_config = $config; |
|||
| 282 | 9 | return $this; |
|||
| 283 | } |
||||
| 284 | |||||
| 285 | /** |
||||
| 286 | * @inheritdoc |
||||
| 287 | */ |
||||
| 288 | 14 | public function getRedirectUrisEnvVarConfig() |
|||
| 289 | { |
||||
| 290 | 14 | $envVarConfig = $this->getEnvVarConfig(); |
|||
| 291 | 14 | return is_array($envVarConfig) && array_key_exists('redirectUris', $envVarConfig) |
|||
| 292 | 7 | ? $this->getEnvVarConfig()['redirectUris'] |
|||
| 293 | 14 | : $this->getModule()->clientRedirectUrisEnvVarConfig; |
|||
| 294 | } |
||||
| 295 | |||||
| 296 | /** |
||||
| 297 | * @inheritdoc |
||||
| 298 | */ |
||||
| 299 | 3 | public function getSecretsEnvVarConfig() |
|||
| 300 | { |
||||
| 301 | 3 | $envVarConfig = $this->getEnvVarConfig(); |
|||
| 302 | 3 | return is_array($envVarConfig) && array_key_exists('secrets', $envVarConfig) |
|||
| 303 | 1 | ? $envVarConfig['secrets'] |
|||
| 304 | 3 | : null; |
|||
| 305 | } |
||||
| 306 | |||||
| 307 | 5 | public function isVariableRedirectUriQueryAllowed() |
|||
| 308 | { |
||||
| 309 | 5 | return (bool)$this->allow_variable_redirect_uri_query; |
|||
| 310 | } |
||||
| 311 | 1 | public function setAllowVariableRedirectUriQuery($allowVariableRedirectUriQuery) |
|||
| 312 | { |
||||
| 313 | 1 | $this->allow_variable_redirect_uri_query = $allowVariableRedirectUriQuery; |
|||
| 314 | 1 | return $this; |
|||
| 315 | } |
||||
| 316 | |||||
| 317 | /** |
||||
| 318 | * @inheritDoc |
||||
| 319 | */ |
||||
| 320 | 4 | public function getUserAccountSelection() |
|||
| 321 | { |
||||
| 322 | 4 | return $this->user_account_selection; |
|||
| 323 | } |
||||
| 324 | |||||
| 325 | 4 | public function endUsersMayAuthorizeClient() |
|||
| 326 | { |
||||
| 327 | 4 | return $this->end_users_may_authorize_client; |
|||
| 328 | } |
||||
| 329 | |||||
| 330 | 1 | public function setEndUsersMayAuthorizeClient($endUsersMayAuthorizeClient) |
|||
| 331 | { |
||||
| 332 | 1 | $this->end_users_may_authorize_client = $endUsersMayAuthorizeClient; |
|||
| 333 | 1 | return $this; |
|||
| 334 | } |
||||
| 335 | |||||
| 336 | /** |
||||
| 337 | * @inheritDoc |
||||
| 338 | */ |
||||
| 339 | 1 | public function setUserAccountSelection($userAccountSelectionConfig) |
|||
| 340 | { |
||||
| 341 | 1 | $this->user_account_selection = $userAccountSelectionConfig; |
|||
| 342 | 1 | return $this; |
|||
| 343 | } |
||||
| 344 | |||||
| 345 | /** |
||||
| 346 | * @inheritDoc |
||||
| 347 | */ |
||||
| 348 | 4 | public function isAuthCodeWithoutPkceAllowed() |
|||
| 349 | { |
||||
| 350 | 4 | return (bool)$this->allow_auth_code_without_pkce; |
|||
| 351 | } |
||||
| 352 | |||||
| 353 | /** |
||||
| 354 | * @inheritDoc |
||||
| 355 | */ |
||||
| 356 | 1 | public function setAllowAuthCodeWithoutPkce($allowAuthCodeWithoutPkce) |
|||
| 357 | { |
||||
| 358 | 1 | $this->allow_auth_code_without_pkce = $allowAuthCodeWithoutPkce; |
|||
| 359 | 1 | return $this; |
|||
| 360 | } |
||||
| 361 | |||||
| 362 | /** |
||||
| 363 | * @inheritDoc |
||||
| 364 | */ |
||||
| 365 | 2 | public function skipAuthorizationIfScopeIsAllowed() |
|||
| 366 | { |
||||
| 367 | 2 | return (bool)$this->skip_authorization_if_scope_is_allowed; |
|||
| 368 | } |
||||
| 369 | |||||
| 370 | /** |
||||
| 371 | * @inheritDoc |
||||
| 372 | */ |
||||
| 373 | 1 | public function setSkipAuthorizationIfScopeIsAllowed($skipAuthIfScopeIsAllowed) |
|||
| 374 | { |
||||
| 375 | 1 | $this->skip_authorization_if_scope_is_allowed = $skipAuthIfScopeIsAllowed; |
|||
| 376 | 1 | return $this; |
|||
| 377 | } |
||||
| 378 | |||||
| 379 | /** |
||||
| 380 | * @inheritDoc |
||||
| 381 | */ |
||||
| 382 | 1 | public function getClientCredentialsGrantUserId() |
|||
| 383 | { |
||||
| 384 | 1 | return $this->client_credentials_grant_user_id; |
|||
| 385 | } |
||||
| 386 | |||||
| 387 | /** |
||||
| 388 | * @inheritDoc |
||||
| 389 | */ |
||||
| 390 | 1 | public function setClientCredentialsGrantUserId($userId) |
|||
| 391 | { |
||||
| 392 | 1 | $this->client_credentials_grant_user_id = $userId; |
|||
| 393 | 1 | return $this; |
|||
| 394 | } |
||||
| 395 | |||||
| 396 | /** |
||||
| 397 | * @inheritDoc |
||||
| 398 | */ |
||||
| 399 | 1 | public function getOpenIdConnectAllowOfflineAccessWithoutConsent() |
|||
| 400 | { |
||||
| 401 | 1 | return (bool)$this->oidc_allow_offline_access_without_consent; |
|||
| 402 | } |
||||
| 403 | |||||
| 404 | /** |
||||
| 405 | * @inheritDoc |
||||
| 406 | */ |
||||
| 407 | 1 | public function setOpenIdConnectAllowOfflineAccessWithoutConsent($allowOfflineAccessWithoutConsent) |
|||
| 408 | { |
||||
| 409 | 1 | $this->oidc_allow_offline_access_without_consent = $allowOfflineAccessWithoutConsent; |
|||
| 410 | 1 | return $this; |
|||
| 411 | } |
||||
| 412 | |||||
| 413 | /** |
||||
| 414 | * @inheritDoc |
||||
| 415 | */ |
||||
| 416 | public function getOpenIdConnectRpInitiatedLogout() |
||||
| 417 | { |
||||
| 418 | return $this->oidc_rp_initiated_logout; |
||||
| 419 | } |
||||
| 420 | |||||
| 421 | /** |
||||
| 422 | * @inheritDoc |
||||
| 423 | */ |
||||
| 424 | public function setOpenIdConnectRpInitiatedLogout($skipLogoutValidation) |
||||
| 425 | { |
||||
| 426 | $this->oidc_rp_initiated_logout = $skipLogoutValidation; |
||||
| 427 | return $this; |
||||
| 428 | } |
||||
| 429 | |||||
| 430 | /** |
||||
| 431 | * @inheritDoc |
||||
| 432 | */ |
||||
| 433 | 1 | public function getOpenIdConnectUserinfoEncryptedResponseAlg() |
|||
| 434 | { |
||||
| 435 | 1 | return $this->oidc_userinfo_encrypted_response_alg; |
|||
| 436 | } |
||||
| 437 | |||||
| 438 | /** |
||||
| 439 | * @inheritDoc |
||||
| 440 | */ |
||||
| 441 | 1 | public function setOpenIdConnectUserinfoEncryptedResponseAlg($algorithm) |
|||
| 442 | { |
||||
| 443 | 1 | $this->oidc_userinfo_encrypted_response_alg = $algorithm; |
|||
| 444 | 1 | return $this; |
|||
| 445 | } |
||||
| 446 | |||||
| 447 | /** |
||||
| 448 | * @inheritdoc |
||||
| 449 | */ |
||||
| 450 | 9 | public function isConfidential() |
|||
| 451 | { |
||||
| 452 | 9 | return (int)$this->type !== static::TYPE_PUBLIC; |
|||
| 453 | } |
||||
| 454 | |||||
| 455 | /** |
||||
| 456 | * @inheritDoc |
||||
| 457 | */ |
||||
| 458 | 17 | public function getAllowGenericScopes() |
|||
| 459 | { |
||||
| 460 | 17 | return (bool)$this->allow_generic_scopes; |
|||
| 461 | } |
||||
| 462 | |||||
| 463 | /** |
||||
| 464 | * @inheritDoc |
||||
| 465 | */ |
||||
| 466 | 1 | public function setAllowGenericScopes($allowGenericScopes) |
|||
| 467 | { |
||||
| 468 | 1 | $this->allow_generic_scopes = $allowGenericScopes; |
|||
| 469 | 1 | return $this; |
|||
| 470 | } |
||||
| 471 | |||||
| 472 | /** |
||||
| 473 | * @inheritDoc |
||||
| 474 | */ |
||||
| 475 | 1 | public function getExceptionOnInvalidScope() |
|||
| 476 | { |
||||
| 477 | 1 | return $this->exception_on_invalid_scope; |
|||
| 478 | } |
||||
| 479 | |||||
| 480 | /** |
||||
| 481 | * @inheritDoc |
||||
| 482 | */ |
||||
| 483 | 1 | public function setExceptionOnInvalidScope($exceptionOnInvalidScope) |
|||
| 484 | { |
||||
| 485 | 1 | $this->exception_on_invalid_scope = $exceptionOnInvalidScope; |
|||
| 486 | 1 | return $this; |
|||
| 487 | } |
||||
| 488 | |||||
| 489 | /** |
||||
| 490 | * @inheritDoc |
||||
| 491 | */ |
||||
| 492 | 3 | public static function getEncryptedAttributes() |
|||
| 493 | { |
||||
| 494 | 3 | return static::ENCRYPTED_ATTRIBUTES; |
|||
| 495 | } |
||||
| 496 | |||||
| 497 | /** |
||||
| 498 | * @inheritDoc |
||||
| 499 | */ |
||||
| 500 | 2 | public static function rotateStorageEncryptionKeys($cryptographer, $newKeyName = null) |
|||
| 501 | { |
||||
| 502 | 2 | $numUpdated = 0; |
|||
| 503 | 2 | $encryptedAttributes = static::getEncryptedAttributes(); |
|||
| 504 | 2 | $query = static::find()->andWhere(['NOT', array_fill_keys($encryptedAttributes, null)]); |
|||
| 505 | |||||
| 506 | 2 | $transaction = static::getDb()->beginTransaction(); |
|||
| 507 | try { |
||||
| 508 | /** @var static $client */ |
||||
| 509 | 2 | foreach ($query->each() as $client) { |
|||
| 510 | 2 | $client->rotateStorageEncryptionKey($cryptographer, $newKeyName); |
|||
| 511 | 2 | if ($client->getDirtyAttributes($encryptedAttributes)) { |
|||
| 512 | 2 | $client->persist(); |
|||
| 513 | 1 | $numUpdated++; |
|||
| 514 | } |
||||
| 515 | } |
||||
| 516 | 1 | $transaction->commit(); |
|||
| 517 | 1 | } catch (\Exception $e) { |
|||
| 518 | 1 | $transaction->rollBack(); |
|||
| 519 | 1 | throw $e; |
|||
| 520 | } |
||||
| 521 | |||||
| 522 | 1 | return $numUpdated; |
|||
| 523 | } |
||||
| 524 | |||||
| 525 | /** |
||||
| 526 | * @inheritDoc |
||||
| 527 | */ |
||||
| 528 | public static function getUsedStorageEncryptionKeys($cryptographer) |
||||
| 529 | { |
||||
| 530 | $encryptedAttributes = static::getEncryptedAttributes(); |
||||
| 531 | $query = static::find()->andWhere(['NOT', array_fill_keys($encryptedAttributes, null)]); |
||||
| 532 | |||||
| 533 | $keyUsage = []; |
||||
| 534 | foreach ($query->each() as $client) { |
||||
| 535 | foreach ($encryptedAttributes as $encryptedAttribute) { |
||||
| 536 | $data = $client->$encryptedAttribute; |
||||
| 537 | if (!empty($data)) { |
||||
| 538 | ['keyName' => $keyName] = $cryptographer->parseData($data); |
||||
| 539 | if (array_key_exists($keyName, $keyUsage)) { |
||||
| 540 | $keyUsage[$keyName][] = $client->getPrimaryKey(); |
||||
| 541 | } else { |
||||
| 542 | $keyUsage[$keyName] = [$client->getPrimaryKey()]; |
||||
| 543 | } |
||||
| 544 | } |
||||
| 545 | } |
||||
| 546 | } |
||||
| 547 | |||||
| 548 | return $keyUsage; |
||||
| 549 | } |
||||
| 550 | |||||
| 551 | /** |
||||
| 552 | * @inheritDoc |
||||
| 553 | */ |
||||
| 554 | 3 | public function rotateStorageEncryptionKey($cryptographer, $newKeyName = null) |
|||
| 555 | { |
||||
| 556 | 3 | foreach (static::getEncryptedAttributes() as $attribute) { |
|||
| 557 | 3 | $data = $this->getAttribute($attribute); |
|||
| 558 | 3 | if ($data) { |
|||
| 559 | try { |
||||
| 560 | 3 | $this->setAttribute($attribute, $cryptographer->rotateKey($data, $newKeyName)); |
|||
| 561 | } catch (\Exception $e) { |
||||
| 562 | throw new Exception('Unable to rotate key for client "' . $this->identifier |
||||
| 563 | . '", attribute "' . $attribute . '": ' . $e->getMessage(), 0, $e); |
||||
| 564 | } |
||||
| 565 | } |
||||
| 566 | } |
||||
| 567 | } |
||||
| 568 | |||||
| 569 | /** |
||||
| 570 | * @inheritDoc |
||||
| 571 | */ |
||||
| 572 | 4 | public function setSecret($secret, $cryptographer, $oldSecretValidUntil = null, $keyName = null) |
|||
| 573 | { |
||||
| 574 | 4 | if ($this->isConfidential()) { |
|||
| 575 | 4 | if (!$this->validateNewSecret($secret, $error)) { |
|||
| 576 | 1 | throw new InvalidArgumentException($error); |
|||
| 577 | } |
||||
| 578 | |||||
| 579 | // Ensure we clear out any old secret. |
||||
| 580 | 3 | $this->setAttribute('old_secret', null); |
|||
| 581 | 3 | $this->setAttribute('old_secret_valid_until', null); |
|||
| 582 | |||||
| 583 | 3 | if ($oldSecretValidUntil) { |
|||
| 584 | 1 | $oldSecretData = $this->getAttribute('secret') ?? null; |
|||
| 585 | 1 | if ($oldSecretData) { |
|||
| 586 | // Ensure correct encryption key. |
||||
| 587 | 1 | $oldSecretData = $cryptographer->encryp($cryptographer->decrypt($oldSecretData), $keyName); |
|||
| 588 | 1 | $this->setAttribute('old_secret', $oldSecretData); |
|||
| 589 | |||||
| 590 | 1 | if ($oldSecretValidUntil instanceof \DateInterval) { |
|||
| 591 | 1 | $oldSecretValidUntil = (new \DateTimeImmutable())->add($oldSecretValidUntil); |
|||
| 592 | } |
||||
| 593 | 1 | $this->setAttribute('old_secret_valid_until', $oldSecretValidUntil); |
|||
| 594 | } |
||||
| 595 | } |
||||
| 596 | |||||
| 597 | 3 | $this->setAttribute('secret', $cryptographer->encryp($secret, $keyName)); |
|||
| 598 | } else { |
||||
| 599 | 1 | if ($secret !== null) { |
|||
| 600 | 1 | throw new InvalidArgumentException( |
|||
| 601 | 1 | 'The secret for a non-confidential client can only be set to `null`.' |
|||
| 602 | 1 | ); |
|||
| 603 | } |
||||
| 604 | |||||
| 605 | 1 | $this->setAttribute('secret', null); |
|||
| 606 | } |
||||
| 607 | |||||
| 608 | 3 | return $this; |
|||
| 609 | } |
||||
| 610 | |||||
| 611 | /** |
||||
| 612 | * @inheritDoc |
||||
| 613 | */ |
||||
| 614 | 1 | public function setSecretsAsEnvVars($secretEnvVarName, $oldSecretEnvVarName = null, $oldSecretValidUntil = null) |
|||
| 615 | { |
||||
| 616 | 1 | if (empty($secretEnvVarName)) { |
|||
| 617 | throw new InvalidArgumentException( |
||||
| 618 | 'Parameter $secretEnvVarName can not be empty.' |
||||
| 619 | ); |
||||
| 620 | } |
||||
| 621 | |||||
| 622 | 1 | if ($oldSecretEnvVarName) { |
|||
| 623 | 1 | if (!$oldSecretValidUntil) { |
|||
| 624 | throw new InvalidArgumentException( |
||||
| 625 | 'Parameter $oldSecretValidUntil must be set when $oldSecretEnvVar is set.' |
||||
| 626 | ); |
||||
| 627 | } |
||||
| 628 | 1 | $this->setAttribute('old_secret', '${' . $oldSecretEnvVarName . '}'); |
|||
| 629 | |||||
| 630 | 1 | if ($oldSecretValidUntil instanceof \DateInterval) { |
|||
| 631 | 1 | $oldSecretValidUntil = (new \DateTimeImmutable())->add($oldSecretValidUntil); |
|||
| 632 | } |
||||
| 633 | 1 | $this->setAttribute('old_secret_valid_until', $oldSecretValidUntil); |
|||
| 634 | } else { |
||||
| 635 | $this->setAttribute('old_secret', null); |
||||
| 636 | $this->setAttribute('old_secret_valid_until', null); |
||||
| 637 | } |
||||
| 638 | |||||
| 639 | 1 | $this->setAttribute('secret', '${' . $secretEnvVarName . '}'); |
|||
| 640 | |||||
| 641 | 1 | return $this; |
|||
| 642 | } |
||||
| 643 | |||||
| 644 | /** |
||||
| 645 | * @inheritDoc |
||||
| 646 | */ |
||||
| 647 | 4 | public function validateNewSecret($secret, &$error) |
|||
| 648 | { |
||||
| 649 | 4 | $error = null; |
|||
| 650 | 4 | if (mb_strlen($secret) < $this->getMinimumSecretLength()) { |
|||
| 651 | 1 | $error = 'Secret should be at least ' . $this->getMinimumSecretLength() . ' characters.'; |
|||
| 652 | } |
||||
| 653 | |||||
| 654 | 4 | return $error === null; |
|||
| 655 | } |
||||
| 656 | |||||
| 657 | /** |
||||
| 658 | * @inheritDoc |
||||
| 659 | */ |
||||
| 660 | 5 | public function getMinimumSecretLength() |
|||
| 661 | { |
||||
| 662 | 5 | return $this->minimumSecretLength; |
|||
| 663 | } |
||||
| 664 | |||||
| 665 | /** |
||||
| 666 | * @inheritDoc |
||||
| 667 | */ |
||||
| 668 | 1 | public function setMinimumSecretLength($minimumSecretLength) |
|||
| 669 | { |
||||
| 670 | 1 | if (!(int)$minimumSecretLength) { |
|||
| 671 | throw new InvalidArgumentException('$minimumSecretLength can not be empty.'); |
||||
| 672 | } |
||||
| 673 | 1 | $this->minimumSecretLength = (int)$minimumSecretLength; |
|||
| 674 | 1 | return $this; |
|||
| 675 | } |
||||
| 676 | |||||
| 677 | /** |
||||
| 678 | * @inheritDoc |
||||
| 679 | */ |
||||
| 680 | 3 | public function getDecryptedSecret($cryptographer) |
|||
| 681 | { |
||||
| 682 | 3 | return $cryptographer->decrypt($this->envVarParseSecret($this->secret)); |
|||
| 683 | } |
||||
| 684 | |||||
| 685 | /** |
||||
| 686 | * @inheritDoc |
||||
| 687 | */ |
||||
| 688 | 2 | public function getDecryptedOldSecret($cryptographer) |
|||
| 689 | { |
||||
| 690 | 2 | return $cryptographer->decrypt($this->envVarParseSecret($this->old_secret)); |
|||
| 691 | } |
||||
| 692 | |||||
| 693 | /** |
||||
| 694 | * Replaces environment variables with their values in the secret |
||||
| 695 | * |
||||
| 696 | * @param string $secret |
||||
| 697 | * @return string |
||||
| 698 | * @throws EnvironmentVariableNotAllowedException |
||||
| 699 | * @throws EnvironmentVariableNotSetException |
||||
| 700 | * @throws InvalidConfigException |
||||
| 701 | */ |
||||
| 702 | 3 | protected function envVarParseSecret($secret) |
|||
| 703 | { |
||||
| 704 | 3 | $secretsEnvVarConfig = $this->getSecretsEnvVarConfig(); |
|||
| 705 | 3 | if ($secretsEnvVarConfig) { |
|||
| 706 | 1 | if (version_compare(PHP_VERSION, '8.1.0', '<')) { |
|||
| 707 | // PHP < 8.1 can only handle indexed array when unpacking. |
||||
| 708 | 1 | $secretsEnvVarConfig = array_values($secretsEnvVarConfig); |
|||
| 709 | } |
||||
| 710 | 1 | $secret = EnvironmentHelper::parseEnvVars($secret, ...$secretsEnvVarConfig); |
|||
|
0 ignored issues
–
show
$secretsEnvVarConfig is expanded, but the parameter $allowList of rhertogh\Yii2Oauth2Serve...tHelper::parseEnvVars() does not expect variable arguments.
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||
| 711 | 2 | } elseif (preg_match(EnvironmentHelper::ENV_VAR_REGEX, $secret)) { |
|||
| 712 | throw new InvalidConfigException('Environment variable used without env_var_config being set.'); |
||||
| 713 | } |
||||
| 714 | |||||
| 715 | 3 | return $secret; |
|||
| 716 | } |
||||
| 717 | |||||
| 718 | /** |
||||
| 719 | * @inheritDoc |
||||
| 720 | */ |
||||
| 721 | public function getOldSecretValidUntil() |
||||
| 722 | { |
||||
| 723 | return $this->old_secret_valid_until; |
||||
| 724 | } |
||||
| 725 | |||||
| 726 | /** |
||||
| 727 | * @inheritdoc |
||||
| 728 | */ |
||||
| 729 | 2 | public function validateSecret($secret, $cryptographer) |
|||
| 730 | { |
||||
| 731 | 2 | return is_string($secret) |
|||
| 732 | 2 | && strlen($secret) |
|||
| 733 | 2 | && ( |
|||
| 734 | 2 | Yii::$app->security->compareString($this->getDecryptedSecret($cryptographer), $secret) |
|||
| 735 | 2 | || ( |
|||
| 736 | 2 | !empty($this->old_secret) |
|||
| 737 | 2 | && !empty($this->old_secret_valid_until) |
|||
| 738 | 2 | && $this->old_secret_valid_until > (new \DateTime()) |
|||
| 739 | 2 | && Yii::$app->security->compareString($this->getDecryptedOldSecret($cryptographer), $secret) |
|||
| 740 | 2 | ) |
|||
| 741 | 2 | ); |
|||
| 742 | } |
||||
| 743 | |||||
| 744 | /** |
||||
| 745 | * @inheritdoc |
||||
| 746 | */ |
||||
| 747 | 1 | public function getLogoUri() |
|||
| 748 | { |
||||
| 749 | 1 | return $this->logo_uri; |
|||
| 750 | } |
||||
| 751 | |||||
| 752 | /** |
||||
| 753 | * @inheritdoc |
||||
| 754 | */ |
||||
| 755 | 1 | public function setLogoUri($logoUri) |
|||
| 756 | { |
||||
| 757 | 1 | $this->logo_uri = $logoUri; |
|||
| 758 | 1 | return $this; |
|||
| 759 | } |
||||
| 760 | |||||
| 761 | /** |
||||
| 762 | * @inheritdoc |
||||
| 763 | */ |
||||
| 764 | 1 | public function getTermsOfServiceUri() |
|||
| 765 | { |
||||
| 766 | 1 | return $this->tos_uri; |
|||
| 767 | } |
||||
| 768 | |||||
| 769 | /** |
||||
| 770 | * @inheritdoc |
||||
| 771 | */ |
||||
| 772 | 1 | public function setTermsOfServiceUri($tosUri) |
|||
| 773 | { |
||||
| 774 | 1 | $this->tos_uri = $tosUri; |
|||
| 775 | 1 | return $this; |
|||
| 776 | } |
||||
| 777 | |||||
| 778 | /** |
||||
| 779 | * @inheritdoc |
||||
| 780 | */ |
||||
| 781 | 1 | public function getContacts() |
|||
| 782 | { |
||||
| 783 | 1 | return $this->contacts; |
|||
| 784 | } |
||||
| 785 | |||||
| 786 | /** |
||||
| 787 | * @inheritdoc |
||||
| 788 | */ |
||||
| 789 | 1 | public function setContacts($contacts) |
|||
| 790 | { |
||||
| 791 | 1 | $this->contacts = $contacts; |
|||
| 792 | 1 | return $this; |
|||
| 793 | } |
||||
| 794 | |||||
| 795 | /** |
||||
| 796 | * @inheritDoc |
||||
| 797 | */ |
||||
| 798 | 1 | public function getGrantTypes() |
|||
| 799 | { |
||||
| 800 | 1 | return (int)$this->grant_types; |
|||
| 801 | } |
||||
| 802 | |||||
| 803 | /** |
||||
| 804 | * @inheritDoc |
||||
| 805 | */ |
||||
| 806 | 2 | public function setGrantTypes($grantTypes) |
|||
| 807 | { |
||||
| 808 | 2 | $grantTypeIds = array_flip(Oauth2Module::GRANT_TYPE_MAPPING); |
|||
| 809 | 2 | for ($i = (int)log(PHP_INT_MAX, 2); $i >= 0; $i--) { |
|||
| 810 | 2 | $grantTypeId = (int)pow(2, $i); |
|||
| 811 | 2 | if ($grantTypes & $grantTypeId) { |
|||
| 812 | 2 | if (!array_key_exists($grantTypeId, $grantTypeIds)) { |
|||
| 813 | 1 | throw new InvalidArgumentException('Unknown Grant Type ID: ' . $grantTypeId); |
|||
| 814 | } |
||||
| 815 | } |
||||
| 816 | } |
||||
| 817 | |||||
| 818 | 1 | $this->grant_types = $grantTypes; |
|||
| 819 | |||||
| 820 | 1 | return $this; |
|||
| 821 | } |
||||
| 822 | |||||
| 823 | /** |
||||
| 824 | * @inheritDoc |
||||
| 825 | */ |
||||
| 826 | 2 | public function validateGrantType($grantTypeIdentifier) |
|||
| 827 | { |
||||
| 828 | 2 | $grantTypeId = Oauth2Module::getGrantTypeId($grantTypeIdentifier); |
|||
| 829 | 2 | if (empty($grantTypeId)) { |
|||
| 830 | 1 | throw new InvalidArgumentException('Unknown grant type "' . $grantTypeIdentifier . '".'); |
|||
| 831 | } |
||||
| 832 | |||||
| 833 | 1 | return (bool)($this->getGrantTypes() & $grantTypeId); |
|||
| 834 | } |
||||
| 835 | |||||
| 836 | /** |
||||
| 837 | * @inheritDoc |
||||
| 838 | * @throws InvalidConfigException |
||||
| 839 | */ |
||||
| 840 | 10 | public function validateAuthRequestScopes($scopeIdentifiers, &$unknownScopes = [], &$unauthorizedScopes = []) |
|||
| 841 | { |
||||
| 842 | 10 | if (empty($scopeIdentifiers)) { |
|||
| 843 | 1 | $unknownScopes = []; |
|||
| 844 | 1 | $unauthorizedScopes = []; |
|||
| 845 | 1 | return true; |
|||
| 846 | } |
||||
| 847 | |||||
| 848 | /** @var Oauth2ScopeInterface $scopeClass */ |
||||
| 849 | 9 | $scopeClass = DiHelper::getValidatedClassName(Oauth2ScopeInterface::class); |
|||
| 850 | 9 | $knownScopeIdentifiers = $scopeClass::find() |
|||
| 851 | 9 | ->andWhere(['identifier' => $scopeIdentifiers]) |
|||
| 852 | 9 | ->select('identifier') |
|||
| 853 | 9 | ->column(); |
|||
| 854 | |||||
| 855 | 9 | $unknownScopes = array_diff($scopeIdentifiers, $knownScopeIdentifiers); |
|||
| 856 | |||||
| 857 | 9 | $allowedScopeIdentifiers = array_map( |
|||
| 858 | 9 | fn($scope) => $scope->getIdentifier(), |
|||
| 859 | 9 | $this->getAllowedScopes($knownScopeIdentifiers) |
|||
| 860 | 9 | ); |
|||
| 861 | |||||
| 862 | 9 | $unauthorizedScopes = array_values(array_diff($knownScopeIdentifiers, $allowedScopeIdentifiers)); |
|||
| 863 | |||||
| 864 | 9 | return empty($unknownScopes) && empty($unauthorizedScopes); |
|||
| 865 | } |
||||
| 866 | |||||
| 867 | /** |
||||
| 868 | * @inheritDoc |
||||
| 869 | * @throws InvalidConfigException |
||||
| 870 | */ |
||||
| 871 | 16 | public function getAllowedScopes($requestedScopeIdentifiers = []) |
|||
| 872 | { |
||||
| 873 | /** @var Oauth2ClientScopeInterface $clientScopeClass */ |
||||
| 874 | 16 | $clientScopeClass = DiHelper::getValidatedClassName(Oauth2ClientScopeInterface::class); |
|||
| 875 | 16 | $clientScopeTableName = $clientScopeClass::tableName(); |
|||
| 876 | /** @var Oauth2ScopeInterface $scopeClass */ |
||||
| 877 | 16 | $scopeClass = DiHelper::getValidatedClassName(Oauth2ScopeInterface::class); |
|||
| 878 | 16 | $scopeTableName = $scopeClass::tableName(); |
|||
| 879 | |||||
| 880 | 16 | if (is_array($requestedScopeIdentifiers)) { |
|||
| 881 | 14 | $possibleScopesConditions = [ |
|||
| 882 | // Requested and default scopes defined for this client. |
||||
| 883 | 14 | ['AND', |
|||
| 884 | 14 | [$clientScopeTableName . '.client_id' => $this->getPrimaryKey()], |
|||
| 885 | 14 | [$clientScopeTableName . '.enabled' => 1], |
|||
| 886 | 14 | ['OR', |
|||
| 887 | 14 | ...( |
|||
| 888 | 14 | !empty($requestedScopeIdentifiers) |
|||
| 889 | 9 | ? [[$scopeTableName . '.identifier' => $requestedScopeIdentifiers]] |
|||
| 890 | : [] |
||||
| 891 | 14 | ), |
|||
| 892 | 14 | ['NOT', [ |
|||
| 893 | 14 | $clientScopeTableName . '.applied_by_default' => Oauth2ScopeInterface::APPLIED_BY_DEFAULT_NO |
|||
| 894 | 14 | ]], |
|||
| 895 | 14 | ['AND', |
|||
| 896 | 14 | [$clientScopeTableName . '.applied_by_default' => null], |
|||
| 897 | 14 | ['NOT', [ |
|||
| 898 | 14 | $scopeTableName . '.applied_by_default' => Oauth2ScopeInterface::APPLIED_BY_DEFAULT_NO |
|||
| 899 | 14 | ]], |
|||
| 900 | 14 | ], |
|||
| 901 | 14 | ], |
|||
| 902 | 14 | ], |
|||
| 903 | 14 | ]; |
|||
| 904 | } else { |
||||
| 905 | 2 | if ($requestedScopeIdentifiers === true) { |
|||
| 906 | 2 | $possibleScopesConditions = [ |
|||
| 907 | // All scopes defined for this client. |
||||
| 908 | 2 | [$clientScopeTableName . '.enabled' => 1], |
|||
| 909 | 2 | ]; |
|||
| 910 | } else { |
||||
| 911 | throw new InvalidArgumentException('`$possibleScopesConditions` must be either an array of strings or `true`.'); // phpcs:ignore Generic.Files.LineLength.TooLong |
||||
| 912 | } |
||||
| 913 | } |
||||
| 914 | |||||
| 915 | 16 | $allowGenericScopes = $this->getAllowGenericScopes(); |
|||
| 916 | 16 | if ($allowGenericScopes) { |
|||
| 917 | 5 | if (is_array($requestedScopeIdentifiers)) { |
|||
| 918 | // Requested and default scopes defined by scope for all clients. |
||||
| 919 | 4 | $possibleScopesConditions[] = ['AND', |
|||
| 920 | 4 | [$clientScopeTableName . '.client_id' => null], |
|||
| 921 | 4 | ['OR', |
|||
| 922 | 4 | ...( |
|||
| 923 | 4 | !empty($requestedScopeIdentifiers) |
|||
| 924 | 2 | ? [[$scopeTableName . '.identifier' => $requestedScopeIdentifiers]] |
|||
| 925 | : [] |
||||
| 926 | 4 | ), |
|||
| 927 | 4 | [ |
|||
| 928 | 4 | 'NOT', |
|||
| 929 | 4 | [$scopeTableName . '.applied_by_default' => Oauth2ScopeInterface::APPLIED_BY_DEFAULT_NO] |
|||
| 930 | 4 | ], |
|||
| 931 | 4 | ], |
|||
| 932 | 4 | ]; |
|||
| 933 | 1 | } elseif ($requestedScopeIdentifiers === true) { |
|||
| 934 | // All scopes defined by scope for all clients. |
||||
| 935 | 1 | $possibleScopesConditions[] = [$clientScopeTableName . '.client_id' => null]; |
|||
| 936 | } |
||||
| 937 | } |
||||
| 938 | |||||
| 939 | 16 | return $scopeClass::find() |
|||
| 940 | 16 | ->joinWith( |
|||
| 941 | 16 | ['clientScopes' => function (Oauth2ClientScopeQuery $query) use ($clientScopeTableName) { |
|||
| 942 | 16 | $query->andOnCondition([$clientScopeTableName . '.client_id' => $this->getPrimaryKey()]); |
|||
| 943 | 16 | }], |
|||
| 944 | 16 | true |
|||
| 945 | 16 | ) |
|||
| 946 | 16 | ->enabled() |
|||
| 947 | 16 | ->andWhere(['OR', ...$possibleScopesConditions]) |
|||
| 948 | 16 | ->orderBy('id') |
|||
| 949 | 16 | ->all(); |
|||
| 950 | } |
||||
| 951 | |||||
| 952 | /** |
||||
| 953 | * @inheritdoc |
||||
| 954 | * @return array{ |
||||
|
0 ignored issues
–
show
|
|||||
| 955 | * 'unaffected': Oauth2ClientScopeInterface[], |
||||
| 956 | * 'new': Oauth2ClientScopeInterface[], |
||||
| 957 | * 'updated': Oauth2ClientScopeInterface[], |
||||
| 958 | * 'deleted': Oauth2ClientScopeInterface[], |
||||
| 959 | * } |
||||
| 960 | */ |
||||
| 961 | 11 | public function syncClientScopes($scopes, $scopeRepository) |
|||
| 962 | { |
||||
| 963 | 11 | if (is_string($scopes)) { |
|||
| 964 | 2 | $scopes = array_filter(array_map('trim', explode(' ', $scopes))); |
|||
| 965 | 9 | } elseif ($scopes === null) { |
|||
| 966 | 1 | $scopes = []; |
|||
| 967 | 8 | } elseif (!is_array($scopes)) { |
|||
| 968 | 1 | throw new InvalidArgumentException('$scopes must be a string, an array or null.'); |
|||
| 969 | } |
||||
| 970 | |||||
| 971 | /** @var class-string<Oauth2ClientScopeInterface> $clientScopeClass */ |
||||
| 972 | 10 | $clientScopeClass = DiHelper::getValidatedClassName(Oauth2ClientScopeInterface::class); |
|||
| 973 | |||||
| 974 | /** @var Oauth2ClientScopeInterface[] $origClientScopes */ |
||||
| 975 | 10 | $origClientScopes = $clientScopeClass::findAll([ |
|||
| 976 | 10 | 'client_id' => $this->getPrimaryKey(), |
|||
| 977 | 10 | ]); |
|||
| 978 | |||||
| 979 | 10 | $origClientScopes = array_combine( |
|||
| 980 | 10 | array_map( |
|||
| 981 | 10 | fn(Oauth2ClientScopeInterface $clientScope) => implode('-', $clientScope->getPrimaryKey(true)), |
|||
| 982 | 10 | $origClientScopes |
|||
| 983 | 10 | ), |
|||
| 984 | 10 | $origClientScopes |
|||
| 985 | 10 | ); |
|||
| 986 | |||||
| 987 | /** @var Oauth2ClientScopeInterface[] $clientScopes */ |
||||
| 988 | 10 | $clientScopes = []; |
|||
| 989 | |||||
| 990 | 10 | foreach ($scopes as $key => $value) { |
|||
| 991 | 9 | if ($value instanceof Oauth2ClientScopeInterface) { |
|||
| 992 | 2 | $clientScope = $value; |
|||
| 993 | 2 | $clientScope->client_id = $this->getPrimaryKey(); // Ensure PK is set. |
|||
|
0 ignored issues
–
show
|
|||||
| 994 | 2 | $pkIndex = implode('-', $clientScope->getPrimaryKey(true)); |
|||
| 995 | 2 | if (array_key_exists($pkIndex, $origClientScopes)) { |
|||
| 996 | // Overwrite orig (might still be considered "unchanged" when new ClientScope is not "dirty"). |
||||
| 997 | 2 | $origClientScopes[$pkIndex] = $clientScope; |
|||
| 998 | } |
||||
| 999 | } else { |
||||
| 1000 | |||||
| 1001 | 7 | $scopeIdentifier = null; |
|||
| 1002 | 7 | $clientScopeConfig = [ |
|||
| 1003 | 7 | 'client_id' => $this->getPrimaryKey(), |
|||
| 1004 | 7 | ]; |
|||
| 1005 | |||||
| 1006 | 7 | if (is_string($value)) { |
|||
| 1007 | 3 | $scopeIdentifier = $value; |
|||
| 1008 | 4 | } elseif ($value instanceof Oauth2ScopeInterface) { |
|||
| 1009 | 2 | $scopePk = $value->getPrimaryKey(); |
|||
| 1010 | 2 | if ($scopePk) { |
|||
| 1011 | 1 | $clientScopeConfig = ArrayHelper::merge( |
|||
| 1012 | 1 | $clientScopeConfig, |
|||
| 1013 | 1 | ['scope_id' => $scopePk] |
|||
| 1014 | 1 | ); |
|||
| 1015 | } else { |
||||
| 1016 | // New model, using identifier. |
||||
| 1017 | 2 | $scopeIdentifier = $value->getIdentifier(); |
|||
| 1018 | } |
||||
| 1019 | 2 | } elseif (is_array($value)) { |
|||
| 1020 | 1 | $clientScopeConfig = ArrayHelper::merge( |
|||
| 1021 | 1 | $clientScopeConfig, |
|||
| 1022 | 1 | $value, |
|||
| 1023 | 1 | ); |
|||
| 1024 | 1 | if (empty($clientScopeConfig['scope_id'])) { |
|||
| 1025 | 1 | $scopeIdentifier = $key; |
|||
| 1026 | } |
||||
| 1027 | } else { |
||||
| 1028 | 1 | throw new InvalidArgumentException( |
|||
| 1029 | 1 | 'If $scopes is an array, its values must be a string, array or an instance of ' |
|||
| 1030 | 1 | . Oauth2ClientScopeInterface::class . ' or ' . Oauth2ScopeInterface::class . '.' |
|||
| 1031 | 1 | ); |
|||
| 1032 | } |
||||
| 1033 | |||||
| 1034 | 6 | if (isset($scopeIdentifier)) { |
|||
| 1035 | 4 | $scope = $scopeRepository->getScopeEntityByIdentifier($scopeIdentifier); |
|||
| 1036 | 4 | if (empty($scope)) { |
|||
| 1037 | 1 | throw new InvalidArgumentException('No scope with identifier "' |
|||
| 1038 | 1 | . $scopeIdentifier . '" found.'); |
|||
| 1039 | } |
||||
| 1040 | 3 | if (!($scope instanceof Oauth2ScopeInterface)) { |
|||
| 1041 | throw new InvalidConfigException(get_class($scope) |
||||
| 1042 | . ' must implement ' . Oauth2ScopeInterface::class); |
||||
| 1043 | } |
||||
| 1044 | 3 | $clientScopeConfig['scope_id'] = $scope->getPrimaryKey(); |
|||
| 1045 | } else { |
||||
| 1046 | 3 | if (empty($clientScopeConfig['scope_id'])) { |
|||
| 1047 | 1 | throw new InvalidArgumentException('Element ' . $key |
|||
| 1048 | 1 | . ' in $scope should specify either the scope id or its identifier.'); |
|||
| 1049 | } |
||||
| 1050 | } |
||||
| 1051 | |||||
| 1052 | 4 | $pkIndex = $clientScopeConfig['client_id'] . '-' . $clientScopeConfig['scope_id']; |
|||
| 1053 | 4 | if (array_key_exists($pkIndex, $origClientScopes)) { |
|||
| 1054 | 4 | $clientScope = $origClientScopes[$pkIndex]; |
|||
| 1055 | 4 | $clientScope->setAttributes($clientScopeConfig, false); |
|||
| 1056 | } else { |
||||
| 1057 | /** @var Oauth2ClientScopeInterface $clientScope */ |
||||
| 1058 | 4 | $clientScope = Yii::createObject(ArrayHelper::merge( |
|||
| 1059 | 4 | ['class' => $clientScopeClass], |
|||
| 1060 | 4 | $clientScopeConfig |
|||
| 1061 | 4 | )); |
|||
| 1062 | } |
||||
| 1063 | } |
||||
| 1064 | |||||
| 1065 | 6 | $pkIndex = implode('-', $clientScope->getPrimaryKey(true)); |
|||
| 1066 | 6 | $clientScopes[$pkIndex] = $clientScope; |
|||
| 1067 | } |
||||
| 1068 | |||||
| 1069 | 7 | $transaction = static::getDb()->beginTransaction(); |
|||
| 1070 | try { |
||||
| 1071 | // Delete records no longer present in the provided data. |
||||
| 1072 | /** @var self[]|array[] $deleteClientScopes */ |
||||
| 1073 | 7 | $deleteClientScopes = array_diff_key($origClientScopes, $clientScopes); |
|||
| 1074 | 7 | foreach ($deleteClientScopes as $deleteClientScope) { |
|||
| 1075 | 6 | $deleteClientScope->delete(); |
|||
| 1076 | } |
||||
| 1077 | |||||
| 1078 | // Create records not present in the provided data. |
||||
| 1079 | 7 | $createClientScopes = array_diff_key($clientScopes, $origClientScopes); |
|||
| 1080 | 7 | foreach ($createClientScopes as $createClientScope) { |
|||
| 1081 | 6 | $createClientScope->persist(); |
|||
| 1082 | } |
||||
| 1083 | |||||
| 1084 | // Update existing records if needed. |
||||
| 1085 | 6 | $unaffectedClientScopes = []; |
|||
| 1086 | 6 | $updatedClientScopes = []; |
|||
| 1087 | 6 | foreach (array_intersect_key($origClientScopes, $clientScopes) as $key => $existingClientScope) { |
|||
| 1088 | 5 | if ($existingClientScope->getDirtyAttributes()) { |
|||
| 1089 | 2 | $existingClientScope->persist(); |
|||
| 1090 | 2 | $updatedClientScopes[$key] = $existingClientScope; |
|||
| 1091 | } else { |
||||
| 1092 | 5 | $unaffectedClientScopes[$key] = $existingClientScope; |
|||
| 1093 | } |
||||
| 1094 | } |
||||
| 1095 | |||||
| 1096 | 6 | $transaction->commit(); |
|||
| 1097 | 1 | } catch (\Exception $e) { |
|||
| 1098 | 1 | $transaction->rollBack(); |
|||
| 1099 | 1 | throw $e; |
|||
| 1100 | } |
||||
| 1101 | |||||
| 1102 | 6 | return [ |
|||
| 1103 | 6 | 'unaffected' => $unaffectedClientScopes, |
|||
| 1104 | 6 | 'new' => $createClientScopes, |
|||
| 1105 | 6 | 'updated' => $updatedClientScopes, |
|||
| 1106 | 6 | 'deleted' => $deleteClientScopes, |
|||
| 1107 | 6 | ]; |
|||
| 1108 | } |
||||
| 1109 | } |
||||
| 1110 |