Page MenuHomeDevCentral

D445.id1103.diff
No OneTemporary

D445.id1103.diff

diff --git a/.env.example b/.env.example
--- a/.env.example
+++ b/.env.example
@@ -16,3 +16,7 @@
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
+
+GITHUB_ENABLE=0
+GITHUB_CLIENT_ID=your-app-client-id
+GITHUB_CLIENT_SECRET=your-app-client-secret
\ No newline at end of file
diff --git a/app/Events/ExternalUserAuthorizeEvent.php b/app/Events/ExternalUserAuthorizeEvent.php
new file mode 100644
--- /dev/null
+++ b/app/Events/ExternalUserAuthorizeEvent.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace AuthGrove\Events;
+
+use Illuminate\Queue\SerializesModels;
+
+use AuthGrove\Events\Event as BaseEvent;
+
+class ExternalUserAuthorizeEvent extends BaseEvent {
+
+ use SerializesModels;
+
+ ///
+ /// Properties
+ ///
+
+ /**
+ * The external authorization source, e.g. github
+ *
+ * @var string
+ */
+ public $externalSource;
+
+ /**
+ * The external user information returned by the API
+ *
+ * @var Laravel\Socialite\Contracts\User
+ */
+ public $externalUser;
+
+}
diff --git a/app/Events/NewExternalUserAuthorizeEvent.php b/app/Events/NewExternalUserAuthorizeEvent.php
new file mode 100644
--- /dev/null
+++ b/app/Events/NewExternalUserAuthorizeEvent.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace AuthGrove\Events;
+
+class NewExternalUserAuthorizeEvent extends ExternalUserAuthorizeEvent {
+
+ ///
+ /// Properties specific for new authorize events
+ ///
+
+ /**
+ * If set, the external user matches this local user, identified by user ID.
+ *
+ * If null, there isn't any constraint.
+ *
+ * @var int|null
+ */
+ public $constraintByUserId = null;
+
+}
diff --git a/app/Http/Controllers/Auth/AuthController.php b/app/Http/Controllers/Auth/AuthController.php
--- a/app/Http/Controllers/Auth/AuthController.php
+++ b/app/Http/Controllers/Auth/AuthController.php
@@ -2,13 +2,15 @@
namespace AuthGrove\Http\Controllers\Auth;
+use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\Registrar as RegistrarContract;
use Illuminate\Foundation\Auth\AuthenticatesAndRegistersUsers;
use Illuminate\Foundation\Auth\ThrottlesLogins;
+use Illuminate\Http\Request;
use AuthGrove\Http\Controllers\Controller;
+use AuthGrove\Services\AuthenticatesExternalUsers;
use AuthGrove\Services\Registrar;
-use AuthGrove\Models\User;
use Config;
use Route;
@@ -21,12 +23,16 @@
|--------------------------------------------------------------------------
|
| This controller handles the registration of new users, as well as the
- | authentication of existing users. By default, this controller uses
- | a simple trait to add these behaviors. Why don't you explore it?
+ | authentication of existing users.
|
*/
- use AuthenticatesAndRegistersUsers, ThrottlesLogins, Registrar;
+ use AuthenticatesAndRegistersUsers, ThrottlesLogins, Registrar,
+ AuthenticatesExternalUsers;
+
+ ///
+ /// Properties
+ ///
/**
* Where to redirect users after login / registration.
@@ -43,13 +49,29 @@
protected $username = 'username';
/**
+ * @var Illuminate\Http\Request
+ */
+ private $request;
+
+ /**
+ * @var Symfony\Component\HttpFoundation\Session\SessionInterface
+ */
+ private $session;
+
+ ///
+ /// Constructor
+ ///
+
+ /**
* Create a new authentication controller instance.
*
+ * @param Illuminate\Http\Request $request The HTTP request
* @return void
*/
- public function __construct()
- {
+ public function __construct (Request $request) {
$this->middleware($this->guestMiddleware(), ['except' => 'logout']);
+ $this->request = $request;
+ $this->session = $request->session();
}
///
@@ -106,6 +128,25 @@
// Reset password (with a token received by mail)
Route::get($auth . '/reset/{token?}', ['as' => 'auth.password.reset', 'uses' => 'Auth\PasswordController@getReset']);
Route::post($auth . '/reset', ['as' => 'auth.password.reset', 'uses' => 'Auth\PasswordController@reset']);
+
+ //External providers
+ static::registerExternalProviderRoutes();
}
+ /**
+ * Registers routes to redirectToProvider and handleProviderCallback.
+ */
+ public static function registerExternalProviderRoutes () {
+ $auth = static::getRoutePrefix();
+
+ Route::get(
+ $auth . 'external/{driver?}',
+ 'Auth\AuthController@redirectToProvider'
+ );
+ Route::get(
+ $auth . 'external/{driver?}/authorize',
+ 'Auth\AuthController@handleProviderCallback'
+ );
+ }
+
}
diff --git a/app/Jobs/LinkExternalUserAccount.php b/app/Jobs/LinkExternalUserAccount.php
new file mode 100644
--- /dev/null
+++ b/app/Jobs/LinkExternalUserAccount.php
@@ -0,0 +1,85 @@
+<?php
+
+namespace AuthGrove\Jobs;
+
+use Illuminate\Queue\SerializesModels;
+use Illuminate\Queue\InteractsWithQueue;
+use Illuminate\Contracts\Queue\ShouldQueue;
+
+use AuthGrove\Events\NewExternalUserAuthorizeEvent;
+use AuthGrove\Jobs\Job;
+use AuthGrove\Models\User;
+use AuthGrove\Models\UserExternalSource;
+
+use Auth;
+
+class LinkExternalUserAccount extends Job implements ShouldQueue {
+
+ use InteractsWithQueue, SerializesModels;
+
+ ///
+ /// Properties, constructor, job handler
+ ///
+
+ /**
+ * @var NewExternalUserAuthorizeEvent;
+ */
+ private $event;
+
+ /**
+ * @var User
+ */
+ private $user;
+
+ /**
+ * Initializes a new instance of LinkExternalUserAccount.
+ *
+ * @param AuthGrove\Events\NewExternalUserAuthorizeEvent $event The event containing link information
+ * @param AuthGrove\Models\User $user The local user to link the external account to
+ */
+ public function __construct (NewExternalUserAuthorizeEvent $event, User $user) {
+ $this->event = $event;
+ $this->user = $user;
+ }
+
+ /**
+ * Executes the job.
+ *
+ * @return void
+ */
+ public function handle () {
+ if ($this->canLinkAccounts()) {
+ $this->linkAccounts();
+ }
+ }
+
+ ///
+ /// Tasks methods
+ ///
+
+ /**
+ * Determines if the requirements to link accounts are met.
+ *
+ * @return bool
+ */
+ public function canLinkAccounts () {
+ $userId = $this->event->constraintByUserId;
+
+ // We allow to link account when:
+ // - there is no constraint (external scenario 3 puts the constraint)
+ // - the user id matches the constraint
+ return $userId === null || $userId === $this->user->id;
+ }
+
+ /**
+ * Links external and local accounts.
+ */
+ public function linkAccounts () {
+ UserExternalSource::create([
+ 'source_name' => $this->event->externalSource,
+ 'source_user_id' => $this->event->externalUser->id,
+ 'user_id' => $this->user->id,
+ ]);
+ }
+
+}
diff --git a/app/Jobs/LogExternalUserAuthorize.php b/app/Jobs/LogExternalUserAuthorize.php
new file mode 100644
--- /dev/null
+++ b/app/Jobs/LogExternalUserAuthorize.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace AuthGrove\Jobs;
+
+use AuthGrove\Jobs\Job;
+use Illuminate\Queue\SerializesModels;
+use Illuminate\Queue\InteractsWithQueue;
+use Illuminate\Contracts\Queue\ShouldQueue;
+
+class LogExternalUserAuthorize extends Job implements ShouldQueue {
+
+ use InteractsWithQueue, SerializesModels;
+
+ /**
+ * Initializes a new instance of LogExternalUserAuthorize.
+ *
+ * @return void
+ */
+ public function __construct() {
+ }
+
+ /**
+ * Executes the job.
+ *
+ * @return void
+ */
+ public function handle () {
+ }
+
+}
diff --git a/app/Jobs/LogLogin.php b/app/Jobs/LogLogin.php
new file mode 100644
--- /dev/null
+++ b/app/Jobs/LogLogin.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace AuthGrove\Jobs;
+
+use AuthGrove\Jobs\Job;
+use Illuminate\Queue\SerializesModels;
+use Illuminate\Queue\InteractsWithQueue;
+use Illuminate\Contracts\Queue\ShouldQueue;
+
+class LogLogin extends Job implements ShouldQueue {
+
+ use InteractsWithQueue, SerializesModels;
+
+ /**
+ * Initializes a new instance of LogExternalUserAuthorize.
+ *
+ * @return void
+ */
+ public function __construct() {
+ }
+
+ /**
+ * Executes the job.
+ *
+ * @return void
+ */
+ public function handle () {
+ }
+
+}
diff --git a/app/Listeners/ExternalUserListener.php b/app/Listeners/ExternalUserListener.php
new file mode 100644
--- /dev/null
+++ b/app/Listeners/ExternalUserListener.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace AuthGrove\Listeners;
+
+use Illuminate\Events\Dispatcher;
+
+use AuthGrove\Events\ExternalUserAuthorizeEvent;
+use AuthGrove\Events\NewExternalUserAuthorizeEvent;
+use AuthGrove\Jobs\LinkExternalUserAccount;
+use AuthGrove\Jobs\LogExternalUserAuthorize;
+
+class ExternalUserListener {
+
+ ///
+ /// Events handlers
+ ///
+
+ public function onNewExternalUserAuthorize (NewExternalUserAuthorizeEvent $event) {
+ $this->linkAccounts($event);
+ $this->logExternalUserAuthorize($event);
+ }
+
+ public function onExternalUserAuthorize (ExternalUserAuthorizeEvent $event) {
+ $this->logExternalUserAuthorize($event);
+ }
+
+ ///
+ /// Tasks methods
+ ///
+
+ public function logExternalUserAuthorize (ExternalUserAuthorizeEvent $event) {
+
+ }
+
+ public function linkAccounts (NewExternalUserAuthorizeEvent $event) {
+
+ }
+
+
+ ///
+ /// Events listener
+ ///
+
+ /**
+ * Registers the listeners for the subscriber.
+ *
+ * @param Illuminate\Events\Dispatcher $events The events dispatcher
+ */
+ public function subscribe (Dispatcher $events) {
+ $class = get_class($this);
+ $events->listen(
+ NewExternalUserAuthorizeEvent::class,
+ "$class@onNewExternalUserAuthorize"
+ );
+ $events->listen(
+ ExternalUserAuthorizeEvent::class,
+ "$class@onExternalUserAuthorize"
+ );
+ }
+
+}
diff --git a/app/Listeners/PostLoginActionsListener.php b/app/Listeners/PostLoginActionsListener.php
new file mode 100644
--- /dev/null
+++ b/app/Listeners/PostLoginActionsListener.php
@@ -0,0 +1,101 @@
+<?php
+
+namespace AuthGrove\Listeners;
+
+use Illuminate\Auth\Events\Login as LoginEvent;
+use Illuminate\Contracts\Auth\Authenticatable;
+use Illuminate\Events\Dispatcher;
+
+use Event;
+use Session;
+
+class PostLoginActionsListener {
+
+ ///
+ /// Event handler
+ ///
+
+ /**
+ * Handles an user login event.
+ *
+ * @param Illuminate\Auth\Events\Login $event
+ */
+ public function onLogin (LoginEvent $event) {
+ $actions = $this->getPostLoginActions();
+ foreach ($actions as $action) {
+ $this->firePostLoginActionEvent($action->event, $action->parameters);
+ }
+ }
+
+ ///
+ /// Helper methods
+ ///
+
+ /**
+ * Gets from the session the post login actions.
+ *
+ * @return array An array with the actions, duplicates removed.
+ */
+ protected function getPostLoginActions () {
+ $actions = Session::get('actions.postlogin', []);
+ return array_unique($actions, SORT_REGULAR);
+ }
+
+ /**
+ * Fires the event to trigger the post login action.
+ *
+ * @param string $eventClass The fully qualified name of the class to use to build the event object
+ * @param array $parameters The properties of the evnet object
+ */
+ protected function firePostLoginActionEvent ($eventClass, $parameters) {
+ $event = $this->buildPostLoginActionEvent($eventClass, $parameters);
+ Event::fire($event);
+ }
+
+ /**
+ * Builds the event from data passed as array.
+ *
+ * @param string $eventClass The fully qualified name of the class to use to build the event object
+ * @param array $parameters The properties of the evnet object
+ * @return AuthGrove\Events\Event
+ */
+ protected function buildPostLoginActionEvent ($eventClass, $parameters) {
+ // 1. Initialize a new instance of $eventClass
+
+ if (!class_exists($eventClass)) {
+ throw new \LogicException("Event class doesn't exist: $eventClass");
+ }
+
+ $event = new $eventClass;
+
+ // 2. Map $parameters to object's properties
+
+ foreach ($parameters as $property => $value) {
+ if (!property_exists($event, $property)) {
+ throw new \LogicException("Property doesn't exist in $eventClass: $property");
+ }
+ $event->$property = $value;
+ }
+
+
+ return $event;
+ }
+
+ ///
+ /// Events listener
+ ///
+
+ /**
+ * Registers the listeners for the subscriber.
+ *
+ * @param Illuminate\Events\Dispatcher $events The events dispatcher
+ */
+ public function subscribe (Dispatcher $events) {
+ $class = get_class($this);
+ $events->listen(
+ 'Illuminate\Auth\Events\Login',
+ "$class@onLogin"
+ );
+ }
+
+}
diff --git a/app/Services/AuthenticatesExternalUsers.php b/app/Services/AuthenticatesExternalUsers.php
new file mode 100644
--- /dev/null
+++ b/app/Services/AuthenticatesExternalUsers.php
@@ -0,0 +1,330 @@
+<?php
+
+namespace AuthGrove\Services;
+
+use Laravel\Socialite\Contracts\User as ExternalUser;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+
+use AuthGrove\Events\NewExternalUserAuthorizeEvent;
+use AuthGrove\Models\User;
+
+use Auth;
+use Config;
+use Redirect;
+use Socialite;
+
+trait AuthenticatesExternalUsers {
+
+ /*
+ |--------------------------------------------------------------------------
+ | External login trait for registration and login controller
+ |--------------------------------------------------------------------------
+ |
+ | This trait allows a registration & login controller to interact with
+ | external sources like GitHub to authorize externally the user.
+ |
+ | We provide routes to request authorization and to handle reply payload.
+ |
+ | Basically, four scenarii could occur and are handled:
+ |
+ | 1. An exception occurs during remote authorization process.
+ | We can retry, as that's sometimes expected (e.g. go back to an old URL)
+ | but it fails again and again and again, we have to fail loudly.
+ |
+ | 2. The external user payload matches directly by a known ID a local user.
+ | We log in.
+ |
+ | 3. The external user payload matches a local user by e-mail.
+ | We record the information in the session sate, so if the user succeeds
+ | to log in by another way, we'll link local and external accounts.
+ | We redirect the user to login form.
+ |
+ | 4. The external user payload doesn't match a local user (or only by non
+ | reliable field like username).
+ | We record the information in the session sate, so if the user succeeds
+ | to log in or register, we'll link too.
+ | We redirect the user to register form.
+ |
+ */
+
+ ///
+ /// Properties
+ ///
+
+ /**
+ * @var AuthGrove\Models\User
+ */
+ private $localUser = null;
+
+ /**
+ * @var Laravel\Socialite\Contracts\User
+ */
+ private $externalUser;
+
+ ///
+ /// Controller entry points
+ ///
+
+ /**
+ * Redirects the user to the provider authentication page.
+ *
+ * This is typically an entry point for routes like /auth/external/github.
+ *
+ * @param string $driver The provider
+ * @return \Illuminate\Http\Response
+ */
+ public function redirectToProvider($driver) {
+ $this->initializeDriverFeatures($driver);
+
+ return Socialite::driver($driver)->redirect();
+ }
+
+ /**
+ * Handles external source payload.
+ *
+ * This is typically an entry point reserved for the external service,
+ * for routes like /auth/external/github/authorize.
+ *
+ * @param string $driver The provider
+ * @return \Illuminate\Http\Response
+ */
+ public function handleProviderCallback($driver) {
+ $this->initializeDriverFeatures($driver);
+
+ try {
+ $this->externalUser = Socialite::driver($driver)->user();
+ $this->session->put('auth.redirects', 0); // End of redirect cycle
+ } catch (\Exception $ex) {
+ // CASE 1 — An exception during remote authorization process.
+ return $this->handleExternalSourceException($ex);
+ }
+
+ // CASE 2 — The external user id is known
+ if ($this->tryGetLocalUserFromExternalSource()) {
+ return $this->handleKnownExternalUser();
+ }
+
+ // CASE 3 — E-mail matches: offer to link
+ if ($this->tryFindLocalUserByMail()) {
+ return $this->handleMatchingExternalUser();
+ }
+
+ // CASE 4 — Unknown user: offer to register
+ return $this->handleUnknownExternalUser();
+ }
+
+ ///
+ /// Helper methods to return responses depending of scenarii
+ ///
+
+ /**
+ * Handles an exception thrown by an external source.
+ *
+ * Scenario 1.
+ *
+ * @param \Exception $ex The exception thrown
+ * @return \Illuminate\Http\Response
+ */
+ public function handleExternalSourceException (\Exception $ex) {
+ // When an exception occurs, one of the most frequent case is user
+ // refreshed the page or reached an old URL. We can reinitiate the
+ // request safely. But we need to track this behavior to limit the
+ // number of redirects below the browser value, so when external
+ // source really throws an error, we can notify the user loudly.
+
+ $redirectsCount = $this->session->increment('auth.redirects');
+ if ($redirectsCount < 3) {
+ // Let's try again
+ $url = static::getRoutePrefix() . 'external/' . $this->driver;
+ return Redirect::to($url);
+ }
+
+ $this->session->put('auth.redirects', 0); // End of redirect cycle
+ return view('auth.fatal-error')->withErrors([
+ 'context' => trans('auth.external-source-exception'),
+ 'exception' => $e->getMessage(),
+ ]);
+ }
+
+ /**
+ * Handles a known external user authorize payload.
+ * Logs in, redirects to target after-login page (dashboard, another site).
+ *
+ * Scenario 2.
+ *
+ * @return \Illuminate\Http\Response
+ */
+ public function handleKnownExternalUser () {
+ Auth::login($this->localUser, true);
+ return Redirect::to($this->redirectTo);
+ }
+
+ /**
+ * Handles a matching external user authorize payload.
+ * Offers to login to confirm identity.
+ *
+ * Scenario 3.
+ *
+ * @return \Illuminate\Http\Response
+ */
+ public function handleMatchingExternalUser () {
+ // We can't directly authenticate the user but we've a probable
+ // match between local data and external source data.
+ // We need to confirm that, with another way to authenticate.
+ $this->saveExternalUserAuthorize();
+
+ return view('auth.login')->withErrors([
+ 'context' => trans('auth.external-source-email-match'),
+ ]);
+ }
+
+ /**
+ * Handles an unknown external user authorize payload.
+ * Offers to register, with a prefilled form.
+ *
+ * Scenario 4.
+ *
+ * @return \Illuminate\Http\Response
+ */
+ public function handleUnknownExternalUser () {
+ $this->saveExternalUserAuthorize();
+
+ $registerUrl = static::getRoutePrefix() . 'register';
+ return redirect($registerUrl)
+ ->withErrors([
+ 'context' => trans('auth.external-source-no-match'),
+ ])
+ ->withInput([
+ 'username' => $this->externalUser->nickname,
+ 'fullname' => $this->externalUser->name,
+ 'email' => $this->externalUser->email,
+ ]);
+ }
+
+ ///
+ /// Helper methods to manage driver
+ ///
+
+ /**
+ * Ensures driver is enabled and configures callback redirect.
+ */
+ public function initializeDriverFeatures ($driver) {
+ $this->driver = $driver;
+
+ if (!$this->isDriverEnabled()) {
+ throw new NotFoundHttpException;
+ }
+
+ Config::set(
+ 'services.' . $driver . '.redirect',
+ $this->getProviderCallbackUrl()
+ );
+ }
+
+ /**
+ * Checks if the specified driver exists and is enabled.
+ *
+ * @param string $driverToCheck The driver to check
+ * @return bool true if the driver exists AND is enabled; otherwise, false.
+ */
+ public function isDriverEnabled () {
+ $sources = Config::get('auth.sources.external');
+ foreach ($sources as $source => $enabled) {
+ if ($source === $this->driver) {
+ return $enabled;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Helper methods to manage session state
+ ///
+
+ /**
+ * Saves to the session state the external source authorize payload.
+ *
+ * This will allow to save later in database the link between local user
+ * and external user.
+ */
+ public function saveExternalUserAuthorize () {
+ $action = $this->getExternalUserAuthorizePostLoginAction();
+ $this->session->push('actions.postlogin', $action);
+ }
+
+ /**
+ * Gets an array to record external user authorize state in the session.
+ *
+ * @param User|null if know, the local user matching the external user data [facultative]
+ * return array
+ */
+ public function getExternalUserAuthorizePostLoginAction () {
+ $action = [
+ 'event' => NewExternalUserAuthorizeEvent::class,
+ 'parameters' => [
+ 'externalSource' => $this->driver,
+ 'externalUser' => $this->externalUser,
+ ]
+ ];
+
+ // If a local user is known, only them will be able to link the accounts.
+ // That avoids users to register an extraneous account.
+ if ($this->localUser !== null) {
+ $action['parameters']['constraintByUserId'] = $this->localUser->id;
+ }
+
+ return $action;
+ }
+
+ ///
+ /// Helper methods to match local and external users
+ ///
+
+ /**
+ * Finds a local user matching an external user by external user ID.
+ *
+ * If an user is found, the object property localUser is set.
+ *
+ * @return bool
+ */
+ public function tryGetLocalUserFromExternalSource () {
+ return User::tryGetFromExternalSource(
+ $this->driver,
+ $this->externalUser->id,
+ $this->localUser
+ );
+ }
+
+ /**
+ * Finds a local user matching an external user by mail.
+ *
+ * If an user is found, the object property localUser is set.
+ *
+ * @return bool
+ */
+ public function tryFindLocalUserByMail () {
+ $this->localUser = User::where(
+ ['email' => $this->externalUser->email]
+ )->first();
+
+ return $this->localUser !== null;
+ }
+
+ ///
+ /// Routing
+ ///
+
+ /**
+ * The callback URL the external service should use to redirect back to
+ * our application.
+ *
+ * @return string
+ */
+ public function getProviderCallbackUrl () {
+ $url = static::getRoutePrefix()
+ . "external/" . $this->driver . "/authorize";
+ return url($url);
+ }
+
+}
diff --git a/config/app.php b/config/app.php
--- a/config/app.php
+++ b/config/app.php
@@ -195,6 +195,8 @@
*/
'listeners' => [
+ AuthGrove\Listeners\PostLoginActionsListener::class,
+ AuthGrove\Listeners\ExternalUserListener::class,
],
/*
diff --git a/config/auth.php b/config/auth.php
--- a/config/auth.php
+++ b/config/auth.php
@@ -106,6 +106,23 @@
/*
|--------------------------------------------------------------------------
+ | Authentication sources
+ |--------------------------------------------------------------------------
+ |
+ | This option lists all the authentication sources: it could be a local
+ | source like username/password or by e-mail, or external authentication
+ | providers we allow to login to through OAuth.
+ |
+ */
+
+ 'sources' => [
+ 'external' => [
+ 'github' => (bool)env('GITHUB_ENABLE'),
+ ]
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
| Routes
|--------------------------------------------------------------------------
|
diff --git a/config/services.php b/config/services.php
--- a/config/services.php
+++ b/config/services.php
@@ -4,6 +4,23 @@
/*
|--------------------------------------------------------------------------
+ | Authentication providers
+ |--------------------------------------------------------------------------
+ |
+ | This file is for storing the credentials for third party services such
+ | as Stripe, Mailgun, Mandrill, and others. This file provides a sane
+ | default location for this type of information, allowing packages
+ | to have a conventional place to find your various credentials.
+ |
+ */
+
+ 'github' => [
+ 'client_id' => env('GITHUB_CLIENT_ID', null),
+ 'client_secret' => env('GITHUB_CLIENT_SECRET', null),
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
| Third Party Services
|--------------------------------------------------------------------------
|
diff --git a/resources/lang/en/auth.php b/resources/lang/en/auth.php
--- a/resources/lang/en/auth.php
+++ b/resources/lang/en/auth.php
@@ -16,4 +16,10 @@
'failed' => 'These credentials do not match our records.',
'throttle' => 'Too many login attempts. Please try again in :seconds seconds.',
+ 'fatal-error' => 'A fatal error occured during the authentication process.',
+
+ 'external-source-exception' => 'This error occured when the authentication process tried to reach an external provider to get an authorization:',
+ 'external-source-email-match' => "You've been successfully authorized by an external provider. We can link this account with an account here, as the email address matches. Please first log in with another method to securely authenticate yourself.",
+ 'external-source-no-match' => "You've been successfully authorized by an external provider. It seems you don't already have an account here. You can register a new account filling this form. If you already have an account, you can go back to the login screen and try another method to log in. In both cases, we'll link your external account to your local account.",
+
];
diff --git a/resources/lang/en/login.php b/resources/lang/en/login.php
--- a/resources/lang/en/login.php
+++ b/resources/lang/en/login.php
@@ -41,4 +41,7 @@
"resetPassword" => "New password",
"resetButton" => "Reset password",
+ //Back to homepage
+ "goto-login" => "Go back to login screen",
+
];
diff --git a/tests/Jobs/LinkExternalUserAccountTest.php b/tests/Jobs/LinkExternalUserAccountTest.php
new file mode 100644
--- /dev/null
+++ b/tests/Jobs/LinkExternalUserAccountTest.php
@@ -0,0 +1,128 @@
+<?php
+
+use Illuminate\Foundation\Testing\DatabaseTransactions;
+
+use AuthGrove\Events\NewExternalUserAuthorizeEvent;
+use AuthGrove\Jobs\LinkExternalUserAccount;
+use AuthGrove\Models\UserExternalSource;
+
+
+/**
+ * Test LinkExternalUserAccountTest job.
+ */
+class LinkExternalUserAccountTest extends TestCase {
+
+ use DatabaseTransactions;
+
+ /**
+ * @varAuthGrove\Events\NewExternalUserAuthorizeEvent
+ */
+ private $event;
+
+ /**
+ * @varAuthGrove\Events\NewExternalUserAuthorizeEvent
+ */
+ private $user;
+
+ ///
+ /// Sets up, tears down
+ ///
+
+ public function setUp () {
+ parent::setUp();
+
+ $this->cleanDatabase();
+ $this->initializeEvent();
+
+ $this->user = $this->mockUser();
+ }
+
+ public function tearDown () {
+ $this->cleanDatabase();
+
+ parent::tearDown();
+ }
+
+ ///
+ /// Tests
+ ///
+
+ public function testCanLinkAccounts () {
+ $job = new LinkExternalUserAccount($this->event, $this->user);
+ $this->assertTrue($job->canLinkAccounts());
+
+ $this->event->constraintByUserId = 500;
+ $this->assertFalse($job->canLinkAccounts());
+ }
+
+ public function testJob () {
+ // Ensures the link doesn't exist before the job
+ $externalUser = UserExternalSource::where(['user_id' => 1])->first();
+ $this->assertNull($externalUser, "Test can occurs, as there is an unexpected record in the database. Test setUp should have removed it.");
+
+ // Runs job
+ $job = new LinkExternalUserAccount($this->event, $this->user);
+ $job->handle();
+
+ // Test a link now exists
+ $externalUser = UserExternalSource::where(['user_id' => 1])->first();
+ $this->assertNotNull($externalUser);
+ $this->assertSame($externalUser->user_id, 1);
+ $this->assertSame($externalUser->source_user_id, '666');
+ $this->assertSame($externalUser->source_name, 'quux');
+ }
+
+ public function testJobWhenConstraintAllowsLink () {
+ // Ensures the link doesn't exist before the job
+ $externalUser = UserExternalSource::where(['user_id' => 1])->first();
+ $this->assertNull($externalUser, "Test can occurs, as there is an unexpected record in the database. Test setUp should have removed it.");
+
+ // Runs job
+ $this->event->constraintByUserId = 1;
+ $job = new LinkExternalUserAccount($this->event, $this->user);
+ $job->handle();
+
+ // Test a link now exists
+ $externalUser = UserExternalSource::where(['user_id' => 1])->first();
+ $this->assertNotNull($externalUser);
+ $this->assertSame($externalUser->user_id, 1);
+ $this->assertSame($externalUser->source_user_id, '666');
+ $this->assertSame($externalUser->source_name, 'quux');
+ }
+
+ public function testJobWhenConstraintForbidsLink () {
+ // Ensures the link doesn't exist before the job
+ $externalUser = UserExternalSource::where(['user_id' => 1])->first();
+ $this->assertNull($externalUser, "Test can occurs, as there is an unexpected record in the database. Test setUp should have removed it.");
+
+ // Runs job
+ $this->event->constraintByUserId = 500;
+ $job = new LinkExternalUserAccount($this->event, $this->user);
+ $job->handle();
+
+ // Test a link now exists
+ $externalUser = UserExternalSource::where(['user_id' => 1])->first();
+ $this->assertNull($externalUser);
+ }
+
+ ///
+ /// Helper methods to prepare the test environment
+ ///
+
+ protected function cleanDatabase () {
+ UserExternalSource::where(['user_id' => 1])->delete();
+ }
+
+ protected function initializeEvent () {
+ $this->event = new NewExternalUserAuthorizeEvent();
+ $this->event->externalSource = "quux";
+ $this->event->externalUser = Mockery::mock('Laravel\Socialite\Contracts\User');
+ $this->event->externalUser->id = 666;
+ }
+
+ protected function mockUser () {
+ $user = Mockery::mock('AuthGrove\Models\User');
+ $user->shouldReceive('getAttribute')->andReturn(1);
+ return $user;
+ }
+}

File Metadata

Mime Type
text/plain
Expires
Mon, Jan 20, 16:08 (10 h, 39 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2362741
Default Alt Text
D445.id1103.diff (31 KB)

Event Timeline