diff --git a/lib/GaletteOAuth2/Authorization/UserHelper.php b/lib/GaletteOAuth2/Authorization/UserHelper.php index 89096bf..e5c5616 100644 --- a/lib/GaletteOAuth2/Authorization/UserHelper.php +++ b/lib/GaletteOAuth2/Authorization/UserHelper.php @@ -14,10 +14,12 @@ use DI\Attribute\Inject; use Galette\Core\Db; use Galette\Core\History; +use Galette\Core\I18n; use Galette\Core\Login; use Galette\Core\Preferences; use Galette\Entity\Adherent; use Galette\Entity\Social; +use Galette\Entity\Status; use GaletteOAuth2\Tools\Config; use GaletteOAuth2\Tools\Debug; use RKA\Session; @@ -40,6 +42,7 @@ public function __construct( private readonly Login $login, private readonly History $history, private readonly Preferences $preferences, + private readonly I18n $i18n, private readonly Messages $flash, #[Inject('oauth_session')] private readonly Session $session @@ -129,6 +132,34 @@ public function getUserData(int $id, string $acl, array $scopes, bool $legacy = throw new UserAuthorizationException(_T('User not found.', 'oauth2')); } + //data are sent to client applications, that do not share member's session + $current_lang = $this->i18n->getID(); + if ($member->language && $member->language !== $current_lang) { + $this->i18n->changeLanguage($member->language); + } + try { + return $this->buildUserData($member, $acl, $scopes, $legacy); + } finally { + if ($this->i18n->getID() !== $current_lang) { + $this->i18n->changeLanguage($current_lang); + } + } + } + + /** + * Build user data, in current language + * + * @param Adherent $member Member + * @param string $acl Requested authorization + * @param string[] $scopes Scopes + * @param bool $legacy Legacy mode for data + * + * @return array + * @throws UserAuthorizationException + * @throws \Throwable + */ + private function buildUserData(Adherent $member, string $acl, array $scopes, bool $legacy): array + { //check active member? Covers $acl === self::AUTH_ACTIVE if (!$member->isActive()) { throw new UserAuthorizationException(_T("Sorry, you can't login because you are not an active member.", "oauth2")); @@ -209,7 +240,8 @@ public function getUserData(int $id, string $acl, array $scopes, bool $legacy = //member:personal if (in_array('member:personal', $scopes)) { - $oauth_data['birthdate'] = $member->birthdate; + //raw value: formatted dates follow current language + $oauth_data['birthdate'] = $member->rbirthdate; $oauth_data['birthplace'] = $member->birth_place; $oauth_data['job'] = $member->job; $oauth_data['gender'] = $member->sgender; @@ -272,12 +304,12 @@ public function getUserData(int $id, string $acl, array $scopes, bool $legacy = //member:groups if (in_array('member:groups', $scopes)) { - $oauth_data['groups'] = self::getUserGroups($member, $legacy); + $oauth_data['groups'] = $this->getUserGroups($member, $legacy); } //member:due_date if (in_array('member:due_date', $scopes)) { - $oauth_data['due_date'] = $member->due_date; + $oauth_data['due_date'] = $member->rdue_date; } return $oauth_data; @@ -291,7 +323,7 @@ public function getUserData(int $id, string $acl, array $scopes, bool $legacy = * * @return string[] */ - protected static function getUserGroups(Adherent $member, bool $legacy = false): array + private function getUserGroups(Adherent $member, bool $legacy = false): array { $groups = array_map( function ($group) { @@ -300,7 +332,9 @@ function ($group) { array_values($member->getGroups()) ); - $groups[] = $member->sstatus; + //untranslated: groups are mapped by client applications, they must not depend on language + $status = new Status($this->zdb); + $groups[] = (string)$status->getLabel($member->status, false); if ($member->isAdmin()) { $groups[] = 'admin'; diff --git a/tests/GaletteOAuth2/Authorization/tests/units/UserHelper.php b/tests/GaletteOAuth2/Authorization/tests/units/UserHelper.php index 770d453..9cb3a7d 100644 --- a/tests/GaletteOAuth2/Authorization/tests/units/UserHelper.php +++ b/tests/GaletteOAuth2/Authorization/tests/units/UserHelper.php @@ -128,7 +128,7 @@ public function testGetUserData(): void $this->assertSame( $expected_base + [ - 'birthdate' => $user_data['birthdate'], + 'birthdate' => $this->dataAdherentOne()['ddn_adh'], 'birthplace' => 'Gonzalez-sur-Meunier', 'job' => 'Chef de fabrication', 'gender' => 'Unspecified', @@ -277,6 +277,95 @@ public function testGetUserData(): void ); } + /** + * Store a member speaking french + * + * @return \Galette\Entity\Adherent + */ + private function getFrenchMember(): \Galette\Entity\Adherent + { + $this->logSuperAdmin(); + $adh = new \Galette\Entity\Adherent($this->zdb); + $adh->setDependencies( + $this->preferences, + $this->members_fields, + $this->history + ); + + $data = $this->dataAdherentOne(); + $data['pref_lang'] = 'fr_FR'; + $data['sexe_adh'] = \Galette\Entity\Adherent::WOMAN; + $check = $adh->check($data, [], []); + if (is_array($check)) { + var_dump($check); + } + $this->assertTrue($check); + + $this->assertTrue($adh->store()); + $this->login->logout(); + + return $adh; + } + + /** + * Test user data are translated in member's language, whatever the current one is + * + * @return void + */ + public function testGetUserDataInMemberLanguage(): void + { + $member = $this->getFrenchMember(); + $data = $this->dataAdherentOne(); + $this->assertSame('en_US', $this->i18n->getID()); + + $user_data = $this->container->get(\GaletteOAuth2\Authorization\UserHelper::class)->getUserData( + $member->id, + '', + ['member', 'member:personal', 'member:groups', 'member:due_date'] + ); + + $this->assertSame('fr_FR', $user_data['locale']); + $this->assertSame('Femme', $user_data['gender']); + //dates are not localized + $this->assertSame($data['ddn_adh'], $user_data['birthdate']); + $this->assertNull($user_data['due_date']); + //status in groups is not translated: client applications rely on it + $this->assertSame(['Non-member'], $user_data['groups']); + + //current language is restored + $this->assertSame('en_US', $this->i18n->getID()); + $this->assertSame('Woman', _T('Woman')); + } + + /** + * Test refusal messages are in member's language, and current language is restored + * + * @return void + */ + public function testRefusalInMemberLanguage(): void + { + $member = $this->getFrenchMember(); + + $this->i18n->changeLanguage('fr_FR'); + $expected = _T("Sorry, you can't login because you are not a team member.", 'oauth2'); + $this->i18n->changeLanguage('en_US'); + $this->assertNotSame("Sorry, you can't login because you are not a team member.", $expected); + + $exception_thrown = false; + try { + $this->container->get(\GaletteOAuth2\Authorization\UserHelper::class)->getUserData( + $member->id, + \GaletteOAuth2\Authorization\UserHelper::AUTH_TEAMONLY, + ['member'] + ); + } catch (\GaletteOAuth2\Authorization\UserAuthorizationException $e) { + $exception_thrown = true; + $this->assertSame($expected, $e->getMessage()); + } + $this->assertTrue($exception_thrown); + $this->assertSame('en_US', $this->i18n->getID()); + } + /** * Test requireAdmin * diff --git a/tests/GaletteOAuth2/Controllers/tests/units/AuthorizationCodeFlow.php b/tests/GaletteOAuth2/Controllers/tests/units/AuthorizationCodeFlow.php index 3bc59ad..54a6b61 100644 --- a/tests/GaletteOAuth2/Controllers/tests/units/AuthorizationCodeFlow.php +++ b/tests/GaletteOAuth2/Controllers/tests/units/AuthorizationCodeFlow.php @@ -263,6 +263,46 @@ public function testUserData(): void $this->assertArrayHasKey('due_date', $user); } + /** + * Test user data are sent in member's language, not in the one of client's request + * + * @return void + */ + public function testUserDataInMemberLanguage(): void + { + $this->logSuperAdmin(); + $member = clone $this->getMemberOne(); + $check = $member->check( + [ + 'bool_admin_adh' => '1', + 'pref_lang' => 'fr_FR', + 'sexe_adh' => \Galette\Entity\Adherent::MAN, + ], + [], + [] + ); + if (is_array($check)) { + var_dump($check); + } + $this->assertTrue($check); + $this->assertTrue($member->store()); + $this->login->logout(); + + $tokens = $this->exchangeCode( + $this->getAuthorizationCode($member->id, ['member', 'member:personal', 'member:groups']) + ); + $this->assertSame('en_US', $this->i18n->getID()); + $test_response = $this->requestUser($tokens['access_token']); + $this->assertSame(200, $test_response->getStatusCode()); + $user = json_decode((string)$test_response->getBody(), true); + + $this->assertSame('fr_FR', $user['locale']); + $this->assertSame('Homme', $user['gender']); + $this->assertSame($this->dataAdherentOne()['ddn_adh'], $user['birthdate']); + $this->assertSame(['Non-member', 'admin'], $user['groups']); + $this->assertSame('en_US', $this->i18n->getID()); + } + /** * Test user data are refused when member is no longer authorized *