Loading classes/Locator.php +4 −0 Original line number Diff line number Diff line Loading @@ -98,6 +98,10 @@ class Locator { return new TokenExchanger($this); } public function getGmsClient(): GmsClient { return new GmsClient($this); } /** * Retrieve the SessionData object from the $_SESSION PHP variable. Create a * new one if it is necessary. Loading classes/UserHandler.php +52 −55 Original line number Diff line number Diff line Loading @@ -42,7 +42,7 @@ class UserHandler { * new identities to it. * @param \RAP\User $user */ public function saveUser(User $user) { public function saveUser(User $user): void { $primarySpecified = true; Loading @@ -63,6 +63,25 @@ class UserHandler { } } /** * Update user with fresh information received by IdP. Useful for keeping email address always updated. */ public function updateIdentity(User $user, Identity $identity): void { $savedIdentity = $user->getIdentityByTypedId($identity->typedId); $savedIdentity->email = $identity->email; if ($identity->name !== null) { $savedIdentity->name = $identity->name; } if ($identity->surname !== null) { $savedIdentity->surname = $identity->surname; } if ($identity->institution !== null) { $savedIdentity->institution = $identity->institution; } $this->userDAO->updateIdentity($savedIdentity); } public function joinUsers(User $user1, User $user2): User { $userId1 = $user1->id; Loading @@ -72,69 +91,47 @@ class UserHandler { return $user1; } // Call Grouper for moving groups and privileges from one user to the other if (isset($this->locator->config->gms)) { //create cURL connection $conn = curl_init($this->locator->config->gms->joinEndpoint); //set options curl_setopt($conn, CURLOPT_CONNECTTIMEOUT, 30); curl_setopt($conn, CURLOPT_RETURNTRANSFER, true); curl_setopt($conn, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($conn, CURLOPT_FOLLOWLOCATION, 1); // Setting an empty body, otherwise Content-Length will be negative and Spring will answer with 400 curl_setopt($conn, CURLOPT_POSTFIELDS, " "); curl_setopt($conn, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . $this->getJoinAccessToken($userId1, $userId2)]); //set data to be posted curl_setopt($conn, CURLOPT_POST, 1); //perform the request $response = curl_exec($conn); $info = curl_getinfo($conn); if ($info['http_code'] === 200) { curl_close($conn); } else { //show information regarding the error curl_close($conn); error_log($response); $httpCode = $info['http_code']; if ($httpCode === 0) { throw new ServerErrorException('GMS service is unreachable'); if ($userId1 === null && $userId2 === null) { throw new BadRequestException("Called join on two users that are both new"); } throw new ServerErrorException('Error: GMS response code: ' . $httpCode); if ($userId1 === null) { return $this->joinNewIdentity($user2, $user1); } if ($userId2 === null) { return $this->joinNewIdentity($user1, $user2); } // Call Grouper for moving groups and privileges from one user to the other $remainingUserId = $this->locator->getGmsClient()->joinGroups($userId1, $userId2); $remainingUser = $userId1 === $remainingUserId ? $user1 : $user2; $userToDelete = $userId1 === $remainingUserId ? $user2 : $user1; // Call DAO for performing join operation into the RAP database. $this->userDAO->joinUsers($userId1, $userId2); $this->userDAO->joinUsers($remainingUserId, $userToDelete->id); foreach ($user2->identities as $identity) { $identity->primary = false; $user1->addIdentity($identity); } $this->moveIdentities($remainingUser, $userToDelete); // merged user return $user1; return $remainingUser; } private function getJoinAccessToken(int $userId1, int $userId2): string { $gmsId = $this->locator->config->gms->id; $accessToken = new AccessTokenData(); $accessToken->clientId = $gmsId; $accessToken->userId = $userId1; // shorter expiration $accessToken->expirationTime = $accessToken->creationTime + 100; $accessToken->scope = ['openid']; /** * New identity, not yet associated with a user: simply add it to the other user. */ private function joinNewIdentity(User $user, User $newUser): User { $identity = $newUser->identities[0]; $user->addIdentity($identity); $this->saveUser($user); return $user; } return $this->locator->getTokenBuilder()->getAccessToken($accessToken, function(& $jwt) use($userId2) { $jwt['alt_sub'] = strval($userId2); }); private function moveIdentities(User $remainingUser, User $deletedUser) { foreach ($deletedUser->identities as $identity) { $identity->primary = false; $remainingUser->addIdentity($identity); } } } classes/datalayer/UserDAO.php +3 −1 Original line number Diff line number Diff line Loading @@ -80,5 +80,7 @@ interface UserDAO { function updateIdentity(Identity $identity): void; function findJoinableUsers($userId): array; function findJoinableUsersByEmail(string $email): array; function findJoinableUsersByUserId(string $userId): array; } classes/datalayer/mysql/MySQLUserDAO.php +25 −2 Original line number Diff line number Diff line Loading @@ -301,7 +301,30 @@ class MySQLUserDAO extends BaseMySQLDAO implements UserDAO { $stmt->execute(); } function findJoinableUsers($userId): array { function findJoinableUsersByEmail(string $email): array { $dbh = $this->getDBHandler(); $query = "SELECT DISTINCT(i3.user_id)\n" . "FROM user u\n" . "JOIN identity i ON i.user_id = u.id\n" . "JOIN identity i2 ON i.user_id = i2.user_id\n" . "JOIN identity i3 ON i2.email = i3.email\n" . "WHERE i.email = :email"; $stmt = $dbh->prepare($query); $stmt->bindParam(':email', $email); $stmt->execute(); $results = []; foreach ($stmt->fetchAll() as $row) { array_push($results, $row['user_id']); } return $results; } function findJoinableUsersByUserId(string $userId): array { $dbh = $this->getDBHandler(); Loading classes/login/GmsClient.php 0 → 100644 +71 −0 Original line number Diff line number Diff line <?php namespace RAP; class GmsClient { private $locator; public function __construct(Locator $locator) { $this->locator = $locator; } public function joinGroups(string $userId1, string $userId2): string { if (!isset($this->locator->config->gms)) { return $userId1; } //create cURL connection $conn = curl_init($this->locator->config->gms->joinEndpoint); //set options curl_setopt($conn, CURLOPT_CONNECTTIMEOUT, 30); curl_setopt($conn, CURLOPT_RETURNTRANSFER, true); curl_setopt($conn, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($conn, CURLOPT_FOLLOWLOCATION, 1); // Setting an empty body, otherwise Content-Length will be negative and Spring will answer with 400 curl_setopt($conn, CURLOPT_POSTFIELDS, " "); curl_setopt($conn, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . $this->getJoinAccessToken($userId1, $userId2)]); //set data to be posted curl_setopt($conn, CURLOPT_POST, 1); //perform the request $response = curl_exec($conn); $info = curl_getinfo($conn); if ($info['http_code'] === 200) { curl_close($conn); } else { //show information regarding the error curl_close($conn); error_log($response); $httpCode = $info['http_code']; if ($httpCode === 0) { throw new ServerErrorException('GMS service is unreachable'); } throw new ServerErrorException('Error: GMS response code: ' . $httpCode); } // TODO: return id extracted from GMS response return $userId1; } private function getJoinAccessToken(int $userId1, int $userId2): string { $gmsId = $this->locator->config->gms->id; $accessToken = new AccessTokenData(); $accessToken->clientId = $gmsId; $accessToken->userId = $userId1; // shorter expiration $accessToken->expirationTime = $accessToken->creationTime + 100; $accessToken->scope = ['openid']; return $this->locator->getTokenBuilder()->getAccessToken($accessToken, function(& $jwt) use($userId2) { $jwt['alt_sub'] = strval($userId2); }); } } Loading
classes/Locator.php +4 −0 Original line number Diff line number Diff line Loading @@ -98,6 +98,10 @@ class Locator { return new TokenExchanger($this); } public function getGmsClient(): GmsClient { return new GmsClient($this); } /** * Retrieve the SessionData object from the $_SESSION PHP variable. Create a * new one if it is necessary. Loading
classes/UserHandler.php +52 −55 Original line number Diff line number Diff line Loading @@ -42,7 +42,7 @@ class UserHandler { * new identities to it. * @param \RAP\User $user */ public function saveUser(User $user) { public function saveUser(User $user): void { $primarySpecified = true; Loading @@ -63,6 +63,25 @@ class UserHandler { } } /** * Update user with fresh information received by IdP. Useful for keeping email address always updated. */ public function updateIdentity(User $user, Identity $identity): void { $savedIdentity = $user->getIdentityByTypedId($identity->typedId); $savedIdentity->email = $identity->email; if ($identity->name !== null) { $savedIdentity->name = $identity->name; } if ($identity->surname !== null) { $savedIdentity->surname = $identity->surname; } if ($identity->institution !== null) { $savedIdentity->institution = $identity->institution; } $this->userDAO->updateIdentity($savedIdentity); } public function joinUsers(User $user1, User $user2): User { $userId1 = $user1->id; Loading @@ -72,69 +91,47 @@ class UserHandler { return $user1; } // Call Grouper for moving groups and privileges from one user to the other if (isset($this->locator->config->gms)) { //create cURL connection $conn = curl_init($this->locator->config->gms->joinEndpoint); //set options curl_setopt($conn, CURLOPT_CONNECTTIMEOUT, 30); curl_setopt($conn, CURLOPT_RETURNTRANSFER, true); curl_setopt($conn, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($conn, CURLOPT_FOLLOWLOCATION, 1); // Setting an empty body, otherwise Content-Length will be negative and Spring will answer with 400 curl_setopt($conn, CURLOPT_POSTFIELDS, " "); curl_setopt($conn, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . $this->getJoinAccessToken($userId1, $userId2)]); //set data to be posted curl_setopt($conn, CURLOPT_POST, 1); //perform the request $response = curl_exec($conn); $info = curl_getinfo($conn); if ($info['http_code'] === 200) { curl_close($conn); } else { //show information regarding the error curl_close($conn); error_log($response); $httpCode = $info['http_code']; if ($httpCode === 0) { throw new ServerErrorException('GMS service is unreachable'); if ($userId1 === null && $userId2 === null) { throw new BadRequestException("Called join on two users that are both new"); } throw new ServerErrorException('Error: GMS response code: ' . $httpCode); if ($userId1 === null) { return $this->joinNewIdentity($user2, $user1); } if ($userId2 === null) { return $this->joinNewIdentity($user1, $user2); } // Call Grouper for moving groups and privileges from one user to the other $remainingUserId = $this->locator->getGmsClient()->joinGroups($userId1, $userId2); $remainingUser = $userId1 === $remainingUserId ? $user1 : $user2; $userToDelete = $userId1 === $remainingUserId ? $user2 : $user1; // Call DAO for performing join operation into the RAP database. $this->userDAO->joinUsers($userId1, $userId2); $this->userDAO->joinUsers($remainingUserId, $userToDelete->id); foreach ($user2->identities as $identity) { $identity->primary = false; $user1->addIdentity($identity); } $this->moveIdentities($remainingUser, $userToDelete); // merged user return $user1; return $remainingUser; } private function getJoinAccessToken(int $userId1, int $userId2): string { $gmsId = $this->locator->config->gms->id; $accessToken = new AccessTokenData(); $accessToken->clientId = $gmsId; $accessToken->userId = $userId1; // shorter expiration $accessToken->expirationTime = $accessToken->creationTime + 100; $accessToken->scope = ['openid']; /** * New identity, not yet associated with a user: simply add it to the other user. */ private function joinNewIdentity(User $user, User $newUser): User { $identity = $newUser->identities[0]; $user->addIdentity($identity); $this->saveUser($user); return $user; } return $this->locator->getTokenBuilder()->getAccessToken($accessToken, function(& $jwt) use($userId2) { $jwt['alt_sub'] = strval($userId2); }); private function moveIdentities(User $remainingUser, User $deletedUser) { foreach ($deletedUser->identities as $identity) { $identity->primary = false; $remainingUser->addIdentity($identity); } } }
classes/datalayer/UserDAO.php +3 −1 Original line number Diff line number Diff line Loading @@ -80,5 +80,7 @@ interface UserDAO { function updateIdentity(Identity $identity): void; function findJoinableUsers($userId): array; function findJoinableUsersByEmail(string $email): array; function findJoinableUsersByUserId(string $userId): array; }
classes/datalayer/mysql/MySQLUserDAO.php +25 −2 Original line number Diff line number Diff line Loading @@ -301,7 +301,30 @@ class MySQLUserDAO extends BaseMySQLDAO implements UserDAO { $stmt->execute(); } function findJoinableUsers($userId): array { function findJoinableUsersByEmail(string $email): array { $dbh = $this->getDBHandler(); $query = "SELECT DISTINCT(i3.user_id)\n" . "FROM user u\n" . "JOIN identity i ON i.user_id = u.id\n" . "JOIN identity i2 ON i.user_id = i2.user_id\n" . "JOIN identity i3 ON i2.email = i3.email\n" . "WHERE i.email = :email"; $stmt = $dbh->prepare($query); $stmt->bindParam(':email', $email); $stmt->execute(); $results = []; foreach ($stmt->fetchAll() as $row) { array_push($results, $row['user_id']); } return $results; } function findJoinableUsersByUserId(string $userId): array { $dbh = $this->getDBHandler(); Loading
classes/login/GmsClient.php 0 → 100644 +71 −0 Original line number Diff line number Diff line <?php namespace RAP; class GmsClient { private $locator; public function __construct(Locator $locator) { $this->locator = $locator; } public function joinGroups(string $userId1, string $userId2): string { if (!isset($this->locator->config->gms)) { return $userId1; } //create cURL connection $conn = curl_init($this->locator->config->gms->joinEndpoint); //set options curl_setopt($conn, CURLOPT_CONNECTTIMEOUT, 30); curl_setopt($conn, CURLOPT_RETURNTRANSFER, true); curl_setopt($conn, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($conn, CURLOPT_FOLLOWLOCATION, 1); // Setting an empty body, otherwise Content-Length will be negative and Spring will answer with 400 curl_setopt($conn, CURLOPT_POSTFIELDS, " "); curl_setopt($conn, CURLOPT_HTTPHEADER, ['Authorization: Bearer ' . $this->getJoinAccessToken($userId1, $userId2)]); //set data to be posted curl_setopt($conn, CURLOPT_POST, 1); //perform the request $response = curl_exec($conn); $info = curl_getinfo($conn); if ($info['http_code'] === 200) { curl_close($conn); } else { //show information regarding the error curl_close($conn); error_log($response); $httpCode = $info['http_code']; if ($httpCode === 0) { throw new ServerErrorException('GMS service is unreachable'); } throw new ServerErrorException('Error: GMS response code: ' . $httpCode); } // TODO: return id extracted from GMS response return $userId1; } private function getJoinAccessToken(int $userId1, int $userId2): string { $gmsId = $this->locator->config->gms->id; $accessToken = new AccessTokenData(); $accessToken->clientId = $gmsId; $accessToken->userId = $userId1; // shorter expiration $accessToken->expirationTime = $accessToken->creationTime + 100; $accessToken->scope = ['openid']; return $this->locator->getTokenBuilder()->getAccessToken($accessToken, function(& $jwt) use($userId2) { $jwt['alt_sub'] = strval($userId2); }); } }