diff --git a/db/migrations/2020_12_25_000000_oauth_add_tokens.php b/db/migrations/2020_12_25_000000_oauth_add_tokens.php new file mode 100644 index 00000000..0bbfa5df --- /dev/null +++ b/db/migrations/2020_12_25_000000_oauth_add_tokens.php @@ -0,0 +1,41 @@ +schema->table( + 'oauth', + function (Blueprint $table) { + $table->string('access_token')->nullable()->default(null)->after('identifier'); + $table->string('refresh_token')->nullable()->default(null)->after('access_token'); + $table->dateTime('expires_at')->nullable()->default(null)->after('refresh_token'); + } + ); + } + + /** + * Reverse the migration + */ + public function down() + { + $this->schema->table( + 'oauth', + function (Blueprint $table) { + $table->dropColumn('access_token'); + $table->dropColumn('refresh_token'); + $table->dropColumn('expires_at'); + } + ); + } +} diff --git a/includes/pages/guest_login.php b/includes/pages/guest_login.php index b4a8d0d4..0c1676e0 100644 --- a/includes/pages/guest_login.php +++ b/includes/pages/guest_login.php @@ -227,8 +227,11 @@ function guest_register() if ($session->has('oauth2_connect_provider') && $session->has('oauth2_user_id')) { $oauth = new OAuth([ - 'provider' => $session->get('oauth2_connect_provider'), - 'identifier' => $session->get('oauth2_user_id'), + 'provider' => $session->get('oauth2_connect_provider'), + 'identifier' => $session->get('oauth2_user_id'), + 'access_token' => $session->get('oauth2_access_token'), + 'refresh_token' => $session->get('oauth2_refresh_token'), + 'expires_at' => $session->get('oauth2_expires_at'), ]); $oauth->user() ->associate($user) @@ -236,6 +239,9 @@ function guest_register() $session->remove('oauth2_connect_provider'); $session->remove('oauth2_user_id'); + $session->remove('oauth2_access_token'); + $session->remove('oauth2_refresh_token'); + $session->remove('oauth2_expires_at'); } // Assign user-group and set password diff --git a/src/Controllers/OAuthController.php b/src/Controllers/OAuthController.php index 5a0db7c4..059c50bb 100644 --- a/src/Controllers/OAuthController.php +++ b/src/Controllers/OAuthController.php @@ -16,6 +16,7 @@ use League\OAuth2\Client\Provider\AbstractProvider; use League\OAuth2\Client\Provider\Exception\IdentityProviderException; use League\OAuth2\Client\Provider\GenericProvider; use League\OAuth2\Client\Provider\ResourceOwnerInterface as ResourceOwner; +use League\OAuth2\Client\Token\AccessTokenInterface; use Psr\Log\LoggerInterface; use Symfony\Component\HttpFoundation\Session\Session as Session; @@ -136,6 +137,16 @@ class OAuthController extends BaseController ->where('identifier', $resourceOwner->getId()) ->first(); + $expirationTime = $accessToken->getExpires(); + $expirationTime = $expirationTime ? Carbon::createFromTimestamp($expirationTime) : null; + if ($oauth) { + $oauth->access_token = $accessToken->getToken(); + $oauth->refresh_token = $accessToken->getRefreshToken(); + $oauth->expires_at = $expirationTime; + + $oauth->save(); + } + $user = $this->auth->user(); if ($oauth && $user && $user->id != $oauth->user_id) { throw new HttpNotFound('oauth.already-connected'); @@ -144,7 +155,13 @@ class OAuthController extends BaseController $connectProvider = $this->session->get('oauth2_connect_provider'); $this->session->remove('oauth2_connect_provider'); if (!$oauth && $user && $connectProvider && $connectProvider == $providerName) { - $oauth = new OAuth(['provider' => $providerName, 'identifier' => $resourceOwner->getId()]); + $oauth = new OAuth([ + 'provider' => $providerName, + 'identifier' => $resourceOwner->getId(), + 'access_token' => $accessToken->getToken(), + 'refresh_token' => $accessToken->getRefreshToken(), + 'expires_at' => $expirationTime, + ]); $oauth->user() ->associate($user) ->save(); @@ -156,10 +173,16 @@ class OAuthController extends BaseController $this->addNotification('oauth.connected'); } - $config = ($this->config->get('oauth')[$providerName]); + $config = $this->config->get('oauth')[$providerName]; $userdata = new Collection($resourceOwner->toArray()); if (!$oauth) { - return $this->redirectRegisterOrThrowNotFound($providerName, $resourceOwner->getId(), $config, $userdata); + return $this->redirectRegisterOrThrowNotFound( + $providerName, + $resourceOwner->getId(), + $accessToken, + $config, + $userdata + ); } if (isset($config['mark_arrived']) && $config['mark_arrived']) { @@ -282,16 +305,18 @@ class OAuthController extends BaseController } /** - * @param string $providerName - * @param string $providerUserIdentifier - * @param array $config - * @param Collection $userdata + * @param string $providerName + * @param string $providerUserIdentifier + * @param AccessTokenInterface $accessToken + * @param array $config + * @param Collection $userdata * * @return Response */ protected function redirectRegisterOrThrowNotFound( string $providerName, string $providerUserIdentifier, + AccessTokenInterface $accessToken, array $config, Collection $userdata ): Response { @@ -315,6 +340,12 @@ class OAuthController extends BaseController $this->session->set('oauth2_connect_provider', $providerName); $this->session->set('oauth2_user_id', $providerUserIdentifier); + $expirationTime = $accessToken->getExpires(); + $expirationTime = $expirationTime ? Carbon::createFromTimestamp($expirationTime) : null; + $this->session->set('oauth2_access_token', $accessToken->getToken()); + $this->session->set('oauth2_refresh_token', $accessToken->getRefreshToken()); + $this->session->set('oauth2_expires_at', $expirationTime); + return $this->redirector->to('/register'); } } diff --git a/src/Models/OAuth.php b/src/Models/OAuth.php index 8fdd0caa..ad1e9fc8 100644 --- a/src/Models/OAuth.php +++ b/src/Models/OAuth.php @@ -12,12 +12,17 @@ use Illuminate\Database\Query\Builder as QueryBuilder; * @property int $id * @property string $provider * @property string $identifier + * @property string $access_token + * @property string $refresh_token + * @property Carbon|null $expires_at * @property Carbon|null $created_at * @property Carbon|null $updated_at * * @method static QueryBuilder|OAuth[] whereId($value) * @method static QueryBuilder|OAuth[] whereProvider($value) * @method static QueryBuilder|OAuth[] whereIdentifier($value) + * @method static QueryBuilder|OAuth[] whereAccessToken($value) + * @method static QueryBuilder|OAuth[] whereRefreshToken($value) */ class OAuth extends BaseModel { @@ -29,9 +34,17 @@ class OAuth extends BaseModel /** @var bool Enable timestamps */ public $timestamps = true; + /** @var string[] */ + protected $dates = [ + 'expires_at', + ]; + /** @var array */ protected $fillable = [ 'provider', 'identifier', + 'access_token', + 'refresh_token', + 'expires_at', ]; } diff --git a/tests/Unit/Controllers/OAuthControllerTest.php b/tests/Unit/Controllers/OAuthControllerTest.php index 000c4d2a..15960f99 100644 --- a/tests/Unit/Controllers/OAuthControllerTest.php +++ b/tests/Unit/Controllers/OAuthControllerTest.php @@ -94,6 +94,9 @@ class OAuthControllerTest extends TestCase $this->session->set('oauth2_connect_provider', 'testprovider'); $accessToken = $this->createMock(AccessToken::class); + $this->setExpects($accessToken, 'getToken', null, 'test-token', $this->atLeastOnce()); + $this->setExpects($accessToken, 'getRefreshToken', null, 'test-refresh-token', $this->atLeastOnce()); + $this->setExpects($accessToken, 'getExpires', null, 4242424242, $this->atLeastOnce()); /** @var ResourceOwnerInterface|MockObject $resourceOwner */ $resourceOwner = $this->createMock(ResourceOwnerInterface::class); @@ -153,7 +156,13 @@ class OAuthControllerTest extends TestCase // Login using provider $controller->index($request); $this->assertFalse($this->session->has('oauth2_connect_provider')); - $this->assertFalse((bool)User::find(1)->state->arrived); + $this->assertFalse((bool)$this->otherUser->state->arrived); + + // Tokens updated + $oauth = $this->otherUser->oauth[0]; + $this->assertEquals('test-token', $oauth->access_token); + $this->assertEquals('test-refresh-token', $oauth->refresh_token); + $this->assertEquals(4242424242, $oauth->expires_at->unix()); // Mark as arrived $oauthConfig = $this->config->get('oauth'); @@ -321,6 +330,9 @@ class OAuthControllerTest extends TestCase public function testIndexRedirectRegister() { $accessToken = $this->createMock(AccessToken::class); + $this->setExpects($accessToken, 'getToken', null, 'test-token', $this->atLeastOnce()); + $this->setExpects($accessToken, 'getRefreshToken', null, 'test-refresh-token', $this->atLeastOnce()); + $this->setExpects($accessToken, 'getExpires', null, 4242424242, $this->atLeastOnce()); /** @var ResourceOwnerInterface|MockObject $resourceOwner */ $resourceOwner = $this->createMock(ResourceOwnerInterface::class); @@ -374,6 +386,9 @@ class OAuthControllerTest extends TestCase $controller->index($request); $this->assertEquals('testprovider', $this->session->get('oauth2_connect_provider')); $this->assertEquals('provider-not-connected-identifier', $this->session->get('oauth2_user_id')); + $this->assertEquals('test-token', $this->session->get('oauth2_access_token')); + $this->assertEquals('test-refresh-token', $this->session->get('oauth2_refresh_token')); + $this->assertEquals(4242424242, $this->session->get('oauth2_expires_at')->unix()); $this->assertEquals( [ 'name' => 'username',