diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..1f5431a
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,39 @@
+{
+ "name": "uisits/laravel-shibboleth",
+ "description": "Enable basic Shibboleth support for Laravel 6.x",
+ "authors": [
+ {
+ "name": "Chinwal Prasad",
+ "email": "pchin3@uis.edu"
+ }
+ ],
+ "require": {
+ "illuminate/support": "5.* || ^6.0",
+ "mrclay/shibalike": "1.0.0",
+ "laravel/framework": "^5.4 || ^6.0",
+ "tymon/jwt-auth": "1.0.x-dev"
+ },
+ "autoload": {
+ "psr-4": {
+ "StudentAffairsUwm\\Shibboleth\\": "src/StudentAffairsUwm/Shibboleth"
+ }
+ },
+ "autoload-dev": {
+ "psr-4": {
+ "StudentAffairsUwm\\Shibboleth\\Tests\\Stubs\\": "tests/setup/Stubs",
+ "App\\": "tests/setup/app"
+ }
+ },
+ "minimum-stability": "stable",
+ "require-dev": {
+ "phpunit/phpunit": "^6.0",
+ "orchestra/testbench": "3.4.*"
+ },
+ "extra": {
+ "laravel": {
+ "providers": [
+ "StudentAffairsUwm\\Shibboleth\\ShibbolethServiceProvider"
+ ]
+ }
+ }
+}
diff --git a/src/.htaccess b/src/.htaccess
new file mode 100644
index 0000000..05994c3
--- /dev/null
+++ b/src/.htaccess
@@ -0,0 +1,31 @@
+
+
+ Options -MultiViews
+
+
+ RewriteEngine On
+
+ # Redirect Trailing Slashes...
+ RewriteRule ^(.*)/$ /$1 [L,R=301]
+
+ # Handle Front Controller...
+ RewriteCond %{REQUEST_FILENAME} !-d
+ RewriteCond %{REQUEST_FILENAME} !-f
+ RewriteRule ^ index.php [L]
+
+
+
+ AuthType shibboleth
+ Require shibboleth
+ ShibUseHeaders On
+ ShibRequireSession Off
+ ShibRequestSetting isPassive Off
+
+
+
+ AuthType shibboleth
+ Require shibboleth
+ ShibUseHeaders On
+ ShibRequireSession Off
+ ShibRequestSetting isPassive Off
+
diff --git a/src/StudentAffairsUwm/Shibboleth/Controllers/ShibbolethController.php b/src/StudentAffairsUwm/Shibboleth/Controllers/ShibbolethController.php
new file mode 100644
index 0000000..f47151e
--- /dev/null
+++ b/src/StudentAffairsUwm/Shibboleth/Controllers/ShibbolethController.php
@@ -0,0 +1,251 @@
+config = new \Shibalike\Config();
+ $this->config->idpUrl = '/emulated/idp';
+
+ $stateManager = $this->getStateManager();
+
+ $this->sp = new \Shibalike\SP($stateManager, $this->config);
+ $this->sp->initLazySession();
+
+ $this->idp = new \Shibalike\IdP($stateManager, $this->getAttrStore(), $this->config);
+ }
+
+ $this->user = $user;
+ }
+
+ /**
+ * Create the session, send the user away to the IDP
+ * for authentication.
+ */
+ public function login()
+ {
+ if (config('shibboleth.emulate_idp') === true) {
+ return Redirect::to(action('\\' . __class__ . '@emulateLogin')
+ . '?target=' . action('\\' . __class__ . '@idpAuthenticate'));
+ }
+
+ return Redirect::to('https://' . Request::server('SERVER_NAME')
+ . ':' . Request::server('SERVER_PORT') . config('shibboleth.idp_login')
+ . '?target=' . action('\\' . __class__ . '@idpAuthenticate'));
+ }
+
+ /**
+ * Setup authentication based on returned server variables
+ * from the IdP.
+ */
+ public function idpAuthenticate()
+ {
+ if (empty(config('shibboleth.user'))) {
+ throw new \Exception('No user attribute mapping for server variables.');
+ }
+
+ foreach (config('shibboleth.user') as $local => $server) {
+ $map[$local] = $this->getServerVariable($server);
+ }
+
+ if (empty($map['email'])) {
+ return abort(403, 'Unauthorized');
+ }
+
+ $userClass = config('auth.providers.users.model', 'App\User');
+
+ // Attempt to login with the email, if success, update the user model
+ // with data from the Shibboleth headers (if present)
+ if (Auth::attempt(array('email' => $map['email']), true)) {
+ $user = $userClass::where('email', '=', $map['email'])->first();
+
+ // Update the model as necessary
+ $user->update($map);
+ }
+
+ // Add user and send through auth.
+ elseif (config('shibboleth.add_new_users', true)) {
+ $map['password'] = 'shibboleth';
+ $user = $userClass::create($map);
+ Auth::login($user);
+ } else {
+ return abort(403, 'Unauthorized');
+ }
+
+ Session::regenerate();
+
+ $route = config('shibboleth.authenticated');
+
+ if (config('jwtauth') === true) {
+ $route .= $this->tokenizeRedirect($user, ['auth_type' => 'idp']);
+ }
+
+ return redirect()->intended($route);
+ }
+
+ /**
+ * Destroy the current session and log the user out, redirect them to the main route.
+ */
+ public function destroy()
+ {
+ Auth::logout();
+ Session::flush();
+
+ if (config('jwtauth')) {
+ $token = JWTAuth::parseToken();
+ $token->invalidate();
+ }
+
+ if (config('shibboleth.emulate_idp') == true) {
+ return Redirect::to(action('\\' . __class__ . '@emulateLogout'));
+ }
+
+ return Redirect::to('https://' . Request::server('SERVER_NAME') . config('shibboleth.idp_logout'));
+ }
+
+ /**
+ * Emulate a login via Shibalike
+ */
+ public function emulateLogin()
+ {
+ $from = (Request::input('target') != null) ? Request::input('target') : $this->getServerVariable('HTTP_REFERER');
+
+ $this->sp->makeAuthRequest($from);
+ $this->sp->redirect();
+ }
+
+ /**
+ * Emulate a logout via Shibalike
+ */
+ public function emulateLogout()
+ {
+ $this->sp->logout();
+
+ $referer = $this->getServerVariable('HTTP_REFERER');
+
+ die("Goodbye, fair user. Return from whence you came!");
+ }
+
+ /**
+ * Emulate the 'authentication' via Shibalike
+ */
+ public function emulateIdp()
+ {
+ $data = [];
+
+ if (Request::input('username') != null) {
+ $username = (Request::input('username') === Request::input('password')) ?
+ Request::input('username') : '';
+
+ $userAttrs = $this->idp->fetchAttrs($username);
+ if ($userAttrs) {
+ $this->idp->markAsAuthenticated($username);
+ $this->idp->redirect(route('shibboleth-authenticate'));
+ }
+
+ $data['error'] = 'Incorrect username and/or password';
+ }
+
+ return view('shibalike::IdpLogin', $data);
+ }
+
+ /**
+ * Function to get an attribute store for Shibalike
+ */
+ private function getAttrStore()
+ {
+ return new \Shibalike\Attr\Store\ArrayStore(config('shibboleth.emulate_idp_users'));
+ }
+
+ /**
+ * Gets a state manager for Shibalike
+ */
+ private function getStateManager()
+ {
+ $session = \UserlandSession\SessionBuilder::instance()
+ ->setSavePath(sys_get_temp_dir())
+ ->setName('SHIBALIKE_BASIC')
+ ->build();
+
+ return new \Shibalike\StateManager\UserlandSession($session);
+ }
+
+ /**
+ * Wrapper function for getting server variables.
+ * Since Shibalike injects $_SERVER variables Laravel
+ * doesn't pick them up. So depending on if we are
+ * using the emulated IdP or a real one, we use the
+ * appropriate function.
+ */
+ private function getServerVariable($variableName)
+ {
+ if (config('shibboleth.emulate_idp') == true) {
+ return isset($_SERVER[$variableName]) ?
+ $_SERVER[$variableName] : null;
+ }
+
+ $variable = Request::server($variableName);
+
+ return (!empty($variable)) ?
+ $variable : Request::server('REDIRECT_' . $variableName);
+ }
+
+ /*
+ * Simple function that allows configuration variables
+ * to be either names of views, or redirect routes.
+ */
+ private function viewOrRedirect($view)
+ {
+ return (View::exists($view)) ? view($view) : Redirect::to($view);
+ }
+
+ /**
+ * Uses JWTAuth to tokenize the user and returns a URL query string.
+ *
+ * @param App\User $user
+ * @param array $customClaims
+ * @return string
+ */
+ private function tokenizeRedirect($user, $customClaims)
+ {
+ // This is where we used to setup a session. Now we will setup a token.
+ $token = JWTAuth::fromUser($user, $customClaims);
+
+ // We need to pass the token... how?
+ // Let's try this.
+ return "?token=$token";
+ }
+}
diff --git a/src/StudentAffairsUwm/Shibboleth/Entitlement.php b/src/StudentAffairsUwm/Shibboleth/Entitlement.php
new file mode 100644
index 0000000..65dffd5
--- /dev/null
+++ b/src/StudentAffairsUwm/Shibboleth/Entitlement.php
@@ -0,0 +1,32 @@
+model = $model;
+ }
+
+ /**
+ * Retrieve a user by their unique identifier.
+ *
+ * @param mixed $identifier
+ * @return \Illuminate\Auth\Authenticatable | null
+ */
+ public function retrieveById($identifier)
+ {
+ $user = $this->retrieveByCredentials(['id' => $identifier]);
+ return ($user && $user->getAuthIdentifier() == $identifier) ?
+ $user : null;
+ }
+
+ /**
+ * Retrieve a user by the given credentials.
+ *
+ * @param array $credentials
+ * @return Illuminate\Auth\Authenticatable | null
+ */
+ public function retrieveByCredentials(array $credentials)
+ {
+ if (count($credentials) == 0) {
+ return null;
+ }
+
+ $class = '\\' . ltrim($this->model, '\\');
+ $user = new $class;
+
+ $query = $user->newQuery();
+ foreach ($credentials as $key => $value) {
+ if (!Str::contains($key, 'password')) {
+ $query->where($key, $value);
+ }
+ }
+
+ return $query->first();
+ }
+
+ /**
+ * Validate a user against the given credentials.
+ *
+ * @param \Illuminate\Auth\Authenticatable $user
+ * @param array $credentials
+ * @return bool
+ */
+ public function validateCredentials(Authenticatable $user, array $creds)
+ {
+ return isset($creds['password'])
+ ? Hash::check($creds['password'], $user->getAuthPassword())
+ : true;
+ }
+
+ /**
+ * Update the "remember me" token for the given user in storage.
+ *
+ * @param \Illuminate\Auth\Authenticatable $user
+ * @param string $token
+ * @return void
+ */
+ public function updateRememberToken(Authenticatable $user, $token)
+ {
+ // Not Implemented
+ }
+
+ /**
+ * Retrieve a user by by their unique identifier and "remember me" token.
+ *
+ * @param mixed $identifier
+ * @param string $token
+ * @return \Illuminate\Auth\Authenticatable | null
+ */
+ public function retrieveByToken($identifier, $token)
+ {
+ // Not Implemented
+ }
+}
diff --git a/src/StudentAffairsUwm/Shibboleth/ShibalikeServiceProvider.php b/src/StudentAffairsUwm/Shibboleth/ShibalikeServiceProvider.php
new file mode 100644
index 0000000..8d84cbe
--- /dev/null
+++ b/src/StudentAffairsUwm/Shibboleth/ShibalikeServiceProvider.php
@@ -0,0 +1,25 @@
+loadViewsFrom(__DIR__ . '/../../resources/views/shibalike', 'shibalike');
+
+ $this->publishes([
+ __DIR__ . '/../../resources/views/shibalike/' => resource_path('views/vendor/shibalike'),
+ ]);
+
+ $this->loadRoutesFrom(__DIR__ . '/../../routes/shibalike.php');
+ }
+}
diff --git a/src/StudentAffairsUwm/Shibboleth/ShibbolethServiceProvider.php b/src/StudentAffairsUwm/Shibboleth/ShibbolethServiceProvider.php
new file mode 100644
index 0000000..aae409e
--- /dev/null
+++ b/src/StudentAffairsUwm/Shibboleth/ShibbolethServiceProvider.php
@@ -0,0 +1,42 @@
+publishes([
+ __DIR__ . '/../../config/shibboleth.php' => config_path('shibboleth.php'),
+ ]);
+
+ $this->loadRoutesFrom(__DIR__ . '/../../routes/shibboleth.php');
+ }
+
+ /**
+ * Register the service provider.
+ *
+ * @return void
+ */
+ public function register()
+ {
+ if (config('jwtauth')) {
+ $this->app->register('Tymon\JWTAuth\Providers\JWTAuthServiceProvider');
+ $loader = AliasLoader::getInstance();
+ $loader->alias('JWTAuth', 'Tymon\JWTAuth\Facades\JWTAuth');
+ $loader->alias('JWTFactory', 'Tymon\JWTAuth\Facades\JWTFactory');
+ }
+
+ $this->app['auth']->provider('shibboleth', function ($app) {
+ return new Providers\ShibbolethUserProvider($app['config']['auth.providers.users.model']);
+ });
+ }
+}
diff --git a/src/config/shibboleth.php b/src/config/shibboleth.php
new file mode 100644
index 0000000..3c70493
--- /dev/null
+++ b/src/config/shibboleth.php
@@ -0,0 +1,102 @@
+ '/Shibboleth.sso/Login',
+ 'idp_logout' => '/Shibboleth.sso/Logout',
+ 'authenticated' => '/',
+
+ /*
+ |--------------------------------------------------------------------------
+ | Emulate an IdP
+ |--------------------------------------------------------------------------
+ |
+ | In case you do not have access to your Shibboleth environment on
+ | homestead or your own Vagrant box, you can emulate a Shibboleth
+ | environment with the help of Shibalike.
+ |
+ | The password is the same as the username.
+ |
+ | Do not use this in production for literally any reason.
+ |
+ */
+
+ 'emulate_idp' => env('EMULATE_IDP', false),
+ 'emulate_idp_users' => [
+ 'admin' => [
+ 'Shib-cn' => 'Admin User',
+ 'Shib-mail' => 'admin@email.arizona.edu',
+ 'Shib-givenName' => 'Admin',
+ 'Shib-sn' => 'User',
+ 'Shib-emplId' => 'admin',
+ ],
+ 'staff' => [
+ 'Shib-cn' => 'Staff User',
+ 'Shib-mail' => 'staff@email.arizona.edu',
+ 'Shib-givenName' => 'Staff',
+ 'Shib-sn' => 'User',
+ 'Shib-emplId' => 'staff',
+ ],
+ 'user' => [
+ 'Shib-cn' => 'User User',
+ 'Shib-mail' => 'user@email.arizona.edu',
+ 'Shib-givenName' => 'User',
+ 'Shib-sn' => 'User',
+ 'Shib-emplId' => 'user',
+ ],
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | Server Variable Mapping
+ |--------------------------------------------------------------------------
+ |
+ | Change these to the proper values for your IdP.
+ |
+ */
+
+ 'entitlement' => 'Shib-isMemberOf',
+
+ 'user' => [
+ // fillable user model attribute => server variable
+ 'name' => 'Shib-cn',
+ 'first_name' => 'Shib-givenName',
+ 'last_name' => 'Shib-sn',
+ 'email' => 'Shib-mail',
+ 'emplid' => 'Shib-emplId',
+ ],
+
+ /*
+ |--------------------------------------------------------------------------
+ | User Creation and Groups Settings
+ |--------------------------------------------------------------------------
+ |
+ | Allows you to change if / how new users are added
+ |
+ */
+
+ 'add_new_users' => true, // Should new users be added automatically if they do not exist?
+
+ /*
+ |--------------------------------------------------------------------------
+ | JWT Auth
+ |--------------------------------------------------------------------------
+ |
+ | JWTs are for the front end to know it's logged in
+ |
+ | https://github.com/tymondesigns/jwt-auth
+ | https://github.com/StudentAffairsUWM/Laravel-Shibboleth-Service-Provider/issues/24
+ |
+ */
+
+ 'jwtauth' => env('JWTAUTH', false),
+];
diff --git a/src/database/migrations/2014_10_12_000000_create_users_table.php b/src/database/migrations/2014_10_12_000000_create_users_table.php
new file mode 100644
index 0000000..ca3d0ed
--- /dev/null
+++ b/src/database/migrations/2014_10_12_000000_create_users_table.php
@@ -0,0 +1,37 @@
+increments('id');
+ $table->string('name');
+ $table->string('email')->unique();
+ $table->string('password');
+ $table->string('first_name')->nullable();
+ $table->string('last_name')->nullable();
+ $table->string('emplid')->nullable();
+ $table->rememberToken();
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('users');
+ }
+}
diff --git a/src/database/migrations/2014_10_12_100000_create_password_resets_table.php b/src/database/migrations/2014_10_12_100000_create_password_resets_table.php
new file mode 100644
index 0000000..d132eaa
--- /dev/null
+++ b/src/database/migrations/2014_10_12_100000_create_password_resets_table.php
@@ -0,0 +1,31 @@
+string('email')->index();
+ $table->string('token')->index();
+ $table->timestamp('created_at')->nullable();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ Schema::dropIfExists('password_resets');
+ }
+}
diff --git a/src/resources/views/shibalike/IdpLogin.blade.php b/src/resources/views/shibalike/IdpLogin.blade.php
new file mode 100644
index 0000000..b876f2f
--- /dev/null
+++ b/src/resources/views/shibalike/IdpLogin.blade.php
@@ -0,0 +1,74 @@
+
+
+
+ Emulated IdP Login
+
+
+
+
+
+
+
+
diff --git a/src/routes/shibalike.php b/src/routes/shibalike.php
new file mode 100644
index 0000000..eec8237
--- /dev/null
+++ b/src/routes/shibalike.php
@@ -0,0 +1,7 @@
+ 'web'], function () {
+ Route::get('emulated/idp', 'StudentAffairsUwm\Shibboleth\Controllers\ShibbolethController@emulateIdp');
+ Route::post('emulated/idp', 'StudentAffairsUwm\Shibboleth\Controllers\ShibbolethController@emulateIdp');
+ Route::get('emulated/login', 'StudentAffairsUwm\Shibboleth\Controllers\ShibbolethController@emulateLogin');
+ Route::get('emulated/logout', 'StudentAffairsUwm\Shibboleth\Controllers\ShibbolethController@emulateLogout');
+});
diff --git a/src/routes/shibboleth.php b/src/routes/shibboleth.php
new file mode 100644
index 0000000..056f39b
--- /dev/null
+++ b/src/routes/shibboleth.php
@@ -0,0 +1,6 @@
+ 'web'], function () {
+ Route::name('shibboleth-login')->get('/shibboleth-login', 'StudentAffairsUwm\Shibboleth\Controllers\ShibbolethController@login');
+ Route::name('shibboleth-authenticate')->get('/shibboleth-authenticate', 'StudentAffairsUwm\Shibboleth\Controllers\ShibbolethController@idpAuthenticate');
+ Route::name('shibboleth-logout')->get('/shibboleth-logout', 'StudentAffairsUwm\Shibboleth\Controllers\ShibbolethController@destroy');
+});