fixing up the authenticator to allow identiy chaining

This commit is contained in:
2018-07-10 21:55:47 +02:00
parent 598f2f8024
commit d74b76da3e
41 changed files with 513 additions and 346 deletions
@@ -1,77 +1,92 @@
<?php namespace AdamWathan\EloquentOAuth;
use SocialNorm\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Database\Eloquent\Model;
use SocialNorm\ProviderUser;
class Authenticator
{
protected $auth;
/** @var \Illuminate\Auth\SessionGuard */
protected $guard;
/** @var UserStore */
protected $users;
/** @var IdentityStore */
protected $identities;
public function __construct($auth, $users, $identities)
public function __construct(\Illuminate\Auth\SessionGuard $auth, UserStore $users, IdentityStore $identities)
{
$this->auth = $auth;
$this->guard = $auth;
$this->users = $users;
$this->identities = $identities;
}
public function login($providerAlias, $userDetails, $callback = null)
public function login($providerAlias, ProviderUser $userDetails, $callback = null)
{
/** @var Model|Authenticatable $user */
$user = $this->getUser($providerAlias, $userDetails);
$user = $this->runCallback($callback, $user, $userDetails);
$this->updateUser($user, $providerAlias, $userDetails);
$this->auth->login($user);
$this->guard->login($user);
}
protected function getUser($providerAlias, \SocialNorm\User $details)
protected function getUser($providerAlias, ProviderUser $details) // returns User
{
if ($this->identities->userExists($providerAlias, $details)) {
if ($this->identities->userExists($providerAlias, $details, false)) {
return $this->getExistingUser($providerAlias, $details);
}
/** @var \App\Models\User $newuser */
$newuser = $this->users->create();
$newuser->name = $details->nickname ?: ($details->full_name ?: $details->email);
$newuser->email = $details->email;
return $newuser;
// this is potentially unsafe - allows user to attach a social identity to an
// existing account just my matching the e-mail.
// we have to assume the providers are trustworthy.
if ($details->email && $this->identities->userExists($providerAlias, $details, true)) {
return $this->users->findByEmail($details->email);
}
return $this->users->create();
}
protected function runCallback($callback, $user, $userDetails)
protected function runCallback($callback, Model $user, ProviderUser $userDetails)
{
$callback = $callback ?: function () {};
$callbackUser = $callback($user, $userDetails);
return $callbackUser ?: $user;
}
protected function updateUser($user, $providerAlias, $details)
protected function updateUser(Model $user, $providerAlias, ProviderUser $details)
{
$this->users->store($user);
$this->storeProviderIdentity($user, $providerAlias, $details);
}
protected function getExistingUser($providerAlias, $details)
protected function getExistingUser($providerAlias, ProviderUser $details)
{
$identity = $this->identities->getByProvider($providerAlias, $details);
return $this->users->findByIdentity($identity);
}
protected function storeProviderIdentity($user, $providerAlias, $details)
protected function storeProviderIdentity(Model $user, string $providerAlias, ProviderUser $details)
{
if ($this->identities->userExists($providerAlias, $details)) {
if ($this->identities->userExists($providerAlias, $details, false)) {
$this->updateProviderIdentity($providerAlias, $details);
} else {
$this->addProviderIdentity($user, $providerAlias, $details);
}
}
protected function updateProviderIdentity($providerAlias, $details)
protected function updateProviderIdentity(string $providerAlias, ProviderUser $details)
{
$identity = $this->identities->getByProvider($providerAlias, $details);
$identity->access_token = $details->access_token;
$this->identities->store($identity);
}
protected function addProviderIdentity($user, $providerAlias, $details)
/**
* @param Model $user
* @param $providerAlias
* @param $details
*/
protected function addProviderIdentity(Model $user, string $providerAlias, ProviderUser $details)
{
$identity = new OAuthIdentity;
$identity->user_id = $user->getKey();
@@ -1,28 +1,58 @@
<?php namespace AdamWathan\EloquentOAuth;
<?php
namespace AdamWathan\EloquentOAuth;
use App\Models\User;
use Illuminate\Database\Eloquent\Model;
use SocialNorm\ProviderUser;
class EloquentIdentityStore implements IdentityStore
{
public function getByProvider($provider, $providerUser)
public function getByProvider(string $provider, ProviderUser $providerUser)
{
return OAuthIdentity::where('provider', $provider)
->where('provider_user_id', $providerUser->id)
->first();
}
public function flush($user, $provider)
/**
* Try to retrieve user by their e-mail
* (!!! this is coupled to the application code !!!)
*
* @param ProviderUser $providerUser
* @return User|Model|null|object
*/
public function getByEmail(ProviderUser $providerUser)
{
$first = User::where('email', $providerUser->email)->first();
if($first->email) return $first;
return null;
}
public function flush(Model $user, string $provider)
{
OAuthIdentity::where('user_id', $user->getKey())
->where('provider', $provider)
->delete();
}
public function store($identity)
public function store(OAuthIdentity $identity)
{
$identity->save();
}
public function userExists($provider, $providerUser)
public function userExists(string $provider, ProviderUser $providerUser, bool $allowByEmail)
{
return (bool) $this->getByProvider($provider, $providerUser);
$byProvider = (bool) $this->getByProvider($provider, $providerUser);
if ($byProvider) return true;
if (!$allowByEmail) return false;
if ($providerUser->email) {
$byEmail = $this->getByEmail($providerUser);
if ($byEmail) return true;
}
return false;
}
}
@@ -1,9 +1,13 @@
<?php namespace AdamWathan\EloquentOAuth;
use Illuminate\Database\Eloquent\Model;
use SocialNorm\ProviderUser;
interface IdentityStore
{
public function getByProvider($provider, $providerUser);
public function flush($user, $provider);
public function store($identity);
public function userExists($provider, $providerUser);
public function getByProvider(string $provider, ProviderUser $providerUser);
public function flush(Model $user, string $provider);
public function getByEmail(ProviderUser $providerUser);
public function store(OAuthIdentity $identity);
public function userExists(string $provider, ProviderUser $providerUser, bool $allowByEmail);
}
@@ -1,7 +1,6 @@
<?php namespace AdamWathan\EloquentOAuth;
use Illuminate\Database\Eloquent\Model as Eloquent;
use Config;
use Illuminate\Database\Eloquent\Model;
/**
* @property $id
@@ -10,14 +9,14 @@ use Config;
* @property $provider_user_id
* @property $access_token
*/
class OAuthIdentity extends Eloquent
class OAuthIdentity extends Model
{
protected static $configuredTable = 'oauth_identities';
// public static function configureTable($table)
// {
// static::$configuredTable = $table;
// }
public static function configureTable($table)
{
static::$configuredTable = $table;
}
public function getTable()
{
@@ -1,10 +1,11 @@
<?php namespace AdamWathan\EloquentOAuth;
use SocialNorm\Provider;
use SocialNorm\SocialNorm;
class OAuthManager
{
/** @var string */
/** @var \Illuminate\Routing\Redirector */
protected $redirect;
/** @var Authenticator */
@@ -13,25 +14,32 @@ class OAuthManager
/** @var SocialNorm */
protected $socialnorm;
public function __construct($redirect, $authenticator, $socialnorm)
public function __construct(
\Illuminate\Routing\Redirector $redirect,
Authenticator $authenticator,
SocialNorm $socialnorm)
{
$this->redirect = $redirect;
$this->authenticator = $authenticator;
$this->socialnorm = $socialnorm;
}
public function authorize($providerAlias)
public function authorize(string $providerAlias)
{
return $this->redirect->to($this->socialnorm->authorize($providerAlias));
}
public function login($providerAlias, $callback = null)
/**
* @param $providerAlias
* @param \Closure|null $callback
*/
public function login(string $providerAlias, $callback = null)
{
$details = $this->socialnorm->getUser($providerAlias);
return $this->authenticator->login($providerAlias, $details, $callback);
}
public function registerProvider($alias, $provider)
public function registerProvider(string $alias, Provider $provider)
{
$this->socialnorm->registerProvider($alias, $provider);
}
@@ -4,20 +4,29 @@ use SocialNorm\Session as SocialNormSession;
class Session implements SocialNormSession
{
/** @var \Illuminate\Session\SessionManager */
private $store;
public function __construct($store)
public function __construct(\Illuminate\Session\SessionManager $store)
{
$this->store = $store;
}
/**
* @param string $key
* @return mixed
*/
public function get($key)
{
return $this->store->get($key);
}
/**
* @param string $key
* @param mixed|null $value
*/
public function put($key, $value)
{
return $this->store->put($key, $value);
$this->store->put($key, $value);
}
}
@@ -1,27 +1,35 @@
<?php namespace AdamWathan\EloquentOAuth;
use Illuminate\Database\Eloquent\Model;
class UserStore
{
protected $model;
/** @var string */
protected $modelClass;
public function __construct($model)
public function __construct(string $modelClass)
{
$this->model = $model;
$this->modelClass = $modelClass;
}
public function create()
{
$user = new $this->model;
$user = new $this->modelClass;
return $user;
}
public function store($user)
public function store(Model $user)
{
return $user->save();
}
public function findByIdentity($identity)
public function findByIdentity(OAuthIdentity $identity)
{
return $identity->belongsTo($this->model, 'user_id')->firstOrFail();
return $identity->belongsTo($this->modelClass, 'user_id')->firstOrFail();
}
public function findByEmail(string $email)
{
return $this->modelClass::where('email', $email)->firstOrFail();
}
}