Completed
Push — master ( ff5cf9...ecc5e7 )
by Tharanga
08:06
created

PersistingAuthenticator::authenticate()   A

Complexity

Conditions 6
Paths 24

Size

Total Lines 36
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 14
nc 24
nop 1
dl 0
loc 36
rs 9.2222
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Tharanga Kothalawala <[email protected]>
4
 * @date 30-12-2018
5
 *
6
 * This is similar to the Default version. But this also stores the vendor token user details in a given storage by mapping the client application user record.
7
 * use this if you want to connect & use multiple vendor login such as Facebook and/or Google.
8
 */
9
10
namespace TSK\SSO\Auth;
11
12
use TSK\SSO\AppUser\AppUser;
13
use TSK\SSO\AppUser\AppUserRepository;
14
use TSK\SSO\AppUser\ExistingAppUser;
15
use TSK\SSO\Auth\Exception\AuthenticationFailedException;
16
use TSK\SSO\Storage\Exception\DataCannotBeStoredException;
17
use TSK\SSO\Storage\FileSystemThirdPartyStorageRepository;
18
use TSK\SSO\Storage\ThirdPartyStorageRepository;
19
use TSK\SSO\ThirdParty\Exception\NoThirdPartyEmailFoundException;
20
use TSK\SSO\ThirdParty\Exception\ThirdPartyConnectionFailedException;
21
use TSK\SSO\ThirdParty\VendorConnection;
22
23
/**
24
 * @package TSK\SSO\Auth
25
 * @see AppUserAwarePersistingAuthenticator
26
 *
27
 * Use this to do a signup/signin via a third party vendor connection by persisting vendor data.
28
 * It is recommended to use this if you are planning to have more than one sso integration.
29
 */
30
class PersistingAuthenticator implements Authenticator
31
{
32
    /**
33
     * @var AppUserRepository
34
     */
35
    private $appUserRepository;
36
37
    /**
38
     * @var ThirdPartyStorageRepository
39
     */
40
    private $storageRepository;
41
42
    /**
43
     * @param AppUserRepository $appUserRepository client application specific user repository implementation to use
44
     *        to provision or validate users.
45
     * @param ThirdPartyStorageRepository $storageRepository a storage implementation to store the third party auth
46
     *        data. By default uses file system as the storage.
47
     */
48
    public function __construct(
49
        AppUserRepository $appUserRepository,
50
        ThirdPartyStorageRepository $storageRepository = null
51
    ) {
52
        $this->appUserRepository = $appUserRepository;
53
        $this->storageRepository = is_null($storageRepository)
54
            ? new FileSystemThirdPartyStorageRepository()
55
            : $storageRepository;
56
    }
57
58
    /**
59
     * This will try to authenticate a user using any given vendor connection.
60
     * Upon a successful attempt, returns the authenticated user.
61
     *
62
     * @param VendorConnection $thirdPartyConnection vendor connection to use to perform an auth
63
     * @return AppUser
64
     *
65
     * @throws AuthenticationFailedException
66
     * @throws DataCannotBeStoredException
67
     * @throws NoThirdPartyEmailFoundException
68
     * @throws ThirdPartyConnectionFailedException
69
     */
70
    public function authenticate(VendorConnection $thirdPartyConnection)
71
    {
72
        $accessToken = $thirdPartyConnection->grantNewAccessToken();
73
74
        $thirdPartyUser = $thirdPartyConnection->getSelf($accessToken);
75
76
        // a SIGN-IN attempt
77
        // check if this is a signin attempt with an existing user account
78
        $existingAppUser = $this->appUserRepository->getUser($thirdPartyUser->email());
79
80
        // if no user account found with the same vendor email as the app email,
81
        // do a mapping lookup in the storage across all vendors
82
        if (is_null($existingAppUser)) {
83
            $mappedUser = $this->storageRepository->getUser($thirdPartyUser->email());
84
            if (!is_null($mappedUser)) {
85
                $existingAppUser = new ExistingAppUser($mappedUser->appUserId(), $mappedUser->vendorEmail());
86
            }
87
        }
88
89
        // a SIGN-UP attempt
90
        // if no user found previously, let's create a new user as this seems like a signup attempt
91
        if (is_null($existingAppUser)) {
92
            $existingAppUser = $this->appUserRepository->create($thirdPartyUser);
93
        }
94
95
        // let's add/update the mapping of the newly created or the existing user's access token before we acknowledge
96
        if (!is_null($existingAppUser)) {
97
            $this->storageRepository->save($existingAppUser, $thirdPartyUser, $accessToken);
98
        }
99
100
        // throw error, if still the an app user cannot be resolved.
101
        if (is_null($existingAppUser)) {
102
            throw new AuthenticationFailedException('This user cannot be authenticated at this moment');
103
        }
104
105
        return $existingAppUser;
106
    }
107
}
108