Commit 6f493c2f authored by Sonia Zorba's avatar Sonia Zorba
Browse files

Refactoring of login logic preparatory to autojoin

parent e2a85279
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -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.
+52 −55
Original line number Diff line number Diff line
@@ -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;

@@ -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;
@@ -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);
        }
    }

}
+3 −1
Original line number Diff line number Diff line
@@ -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;
}
+25 −2
Original line number Diff line number Diff line
@@ -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();
        
+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