From 6adb1179c707e43406cc5dcbf5d72e40a5d1b7b7 Mon Sep 17 00:00:00 2001 From: Jamie0 Date: Fri, 13 Sep 2019 20:41:23 +0100 Subject: [PATCH 1/2] Add basic OpenID Connect support --- app/Factories/LinkFactory.php | 2 +- app/Http/Controllers/SetupController.php | 11 ++ app/Http/Controllers/UserController.php | 68 ++++++++++++ app/Http/routes.php | 3 + bootstrap/app.php | 1 + composer.json | 3 +- composer.lock | 130 ++++++++++++++++++++++- resources/views/env.blade.php | 5 + resources/views/setup.blade.php | 28 +++++ 9 files changed, 247 insertions(+), 4 deletions(-) diff --git a/app/Factories/LinkFactory.php b/app/Factories/LinkFactory.php index 7ff2bda5d..9f3e504e3 100644 --- a/app/Factories/LinkFactory.php +++ b/app/Factories/LinkFactory.php @@ -9,7 +9,7 @@ class LinkFactory { const MAXIMUM_LINK_LENGTH = 65535; - private static function formatLink($link_ending, $secret_ending=false) { + public static function formatLink($link_ending, $secret_ending=false) { /** * Given a link ending and a boolean indicating whether a secret ending is needed, * return a link formatted with app protocol, app address, and link ending. diff --git a/app/Http/Controllers/SetupController.php b/app/Http/Controllers/SetupController.php index d29006548..3ba51d504 100644 --- a/app/Http/Controllers/SetupController.php +++ b/app/Http/Controllers/SetupController.php @@ -133,6 +133,11 @@ public static function performSetup(Request $request) { $mail_from = $request->input('app:smtp_from'); $mail_from_name = $request->input('app:smtp_from_name'); + $openid_connect_url = $request->input('settings:openid_connect_url'); + $openid_connect_configuration = $request->input('settings:openid_connect_configuration'); + $openid_connect_client_id = $request->input('settings:openid_connect_client_id'); + $openid_connect_client_secret = $request->input('settings:openid_connect_client_secret'); + if ($mail_host) { $mail_enabled = true; } @@ -168,6 +173,11 @@ public static function performSetup(Request $request) { 'POLR_RECAPTCHA_SITE_KEY' => $polr_recaptcha_site_key, 'POLR_RECAPTCHA_SECRET' => $polr_recaptcha_secret_key, + 'OPENID_CONNECT_URL' => $openid_connect_url, + 'OPENID_CONNECT_CONFIGURATION' => $openid_connect_configuration, + 'OPENID_CONNECT_CLIENT_ID' => $openid_connect_client_id, + 'OPENID_CONNECT_CLIENT_SECRET' => $openid_connect_client_secret, + 'MAIL_ENABLED' => $mail_enabled, 'MAIL_HOST' => $mail_host, 'MAIL_PORT' => $mail_port, @@ -226,6 +236,7 @@ public static function finishSetup(Request $request) { // unset cookie setcookie('setup_arguments', '', time()-3600); + $transaction_authorised = env('TMP_SETUP_AUTH_KEY') == $setup_finish_args->setup_auth_key; if ($transaction_authorised != true) { diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index 62e8c27d5..dc08936ff 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -34,6 +34,74 @@ public function performLogoutUser(Request $request) { return redirect()->route('index'); } + public function performOpenIDConnect(Request $request) { + $client = app()->make('openidconnect'); + + $client->authenticate(); + + $info = $client->requestUserInfo(); + + $username = $info->preferred_username ?: $info->email ?: $info->sub; + $email = $info->email; + + if (!$username || !$email) { + abort(503, 'OpenID Connect provider did not return the requested mandatory fields username and email.'); + } + + if (UserHelper::emailExists($email)) { + $user = UserHelper::getUserByEmail($email); + } else if (UserHelper::userExists($username)) { + // user account already exists + $user = UserHelper::getUserByUsername($username); + } else { + // create the new user! + + if (env('SETTING_RESTRICT_EMAIL_DOMAIN')) { + $email_domain = explode('@', $email)[1]; + $permitted_email_domains = explode(',', env('SETTING_ALLOWED_EMAIL_DOMAINS')); + + if (!in_array($email_domain, $permitted_email_domains)) { + return redirect(route('signup'))->with('error', 'Sorry, your email\'s domain is not permitted to create new accounts.'); + } + } + + $ip = $request->ip(); + + $api_active = false; + $api_key = null; + + if (env('SETTING_AUTO_API')) { + // if automatic API key assignment is on + $api_active = 1; + $api_key = CryptoHelper::generateRandomHex(env('_API_KEY_LENGTH')); + } + + $user = UserFactory::createUser($username, $email, CryptoHelper::generateRandomHex(48), 1, $ip, $api_key, $api_active); + } + + + // great, we have a user! + + if (isset($info->group) && env('OPENID_CONNECT_ADMIN_GROUP')) { + $admin_groups = explode(',', env('OPENID_CONNECT_ADMIN_GROUP')); + + if (in_array($info->group, $admin_groups)) { + // make user admin! + $user->role = UserHelper::$USER_ROLES['admin']; + $user->save(); + } else { + $user->role = UserHelper::$USER_ROLES['default']; + $user->save(); + } + } + + // we're logged in! + $request->session()->put('username', $user->username); + $request->session()->put('role', $user->role); + + return redirect()->route('index'); + } + public function performLogin(Request $request) { $username = $request->input('username'); $password = $request->input('password'); diff --git a/app/Http/routes.php b/app/Http/routes.php index e936cb10f..9eea8b1ac 100644 --- a/app/Http/routes.php +++ b/app/Http/routes.php @@ -17,6 +17,9 @@ $app->get('/', ['as' => 'index', 'uses' => 'IndexController@showIndexPage']); $app->get('/logout', ['as' => 'logout', 'uses' => 'UserController@performLogoutUser']); $app->get('/login', ['as' => 'login', 'uses' => 'UserController@displayLoginPage']); + +$app->get('/login/openid', ['as' => 'login', 'uses' => 'UserController@performOpenIDConnect']); + $app->get('/about-polr', ['as' => 'about', 'uses' => 'StaticPageController@displayAbout']); $app->get('/lost_password', ['as' => 'lost_password', 'uses' => 'UserController@displayLostPasswordPage']); diff --git a/bootstrap/app.php b/bootstrap/app.php index 65581199b..ad8794b28 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -82,6 +82,7 @@ $app->register(App\Providers\AppServiceProvider::class); $app->register(\Yajra\Datatables\DatatablesServiceProvider::class); $app->register(\Torann\GeoIP\GeoIPServiceProvider::class); +$app->register(App\Providers\OpenIDConnectProvider::class); // $app->register(App\Providers\EventServiceProvider::class); /* diff --git a/composer.json b/composer.json index 4f883ee9a..f62745900 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,8 @@ "geoip2/geoip2": "^2.4", "nesbot/carbon": "^1.22", "doctrine/dbal": "^2.5", - "google/recaptcha": "~1.1" + "google/recaptcha": "~1.1", + "jumbojett/openid-connect-php": "^0.8.0" }, "require-dev": { "fzaninotto/faker": "~1.0", diff --git a/composer.lock b/composer.lock index 14d90c676..3750cc029 100644 --- a/composer.lock +++ b/composer.lock @@ -1,10 +1,10 @@ { "_readme": [ "This file locks the dependencies of your project to a known state", - "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1b7ae24ee886aba13a99bf0207be0cdd", + "content-hash": "28a6aaebd5593bc9e06b660c8387b836", "packages": [ { "name": "composer/ca-bundle", @@ -1938,6 +1938,39 @@ "homepage": "http://laravel.com", "time": "2015-11-29T16:58:05+00:00" }, + { + "name": "jumbojett/openid-connect-php", + "version": "v0.8.0", + "source": { + "type": "git", + "url": "https://github.com/jumbojett/OpenID-Connect-PHP.git", + "reference": "08c29b063803538f345183b66ac05cff7ed6eb9b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jumbojett/OpenID-Connect-PHP/zipball/08c29b063803538f345183b66ac05cff7ed6eb9b", + "reference": "08c29b063803538f345183b66ac05cff7ed6eb9b", + "shasum": "" + }, + "require": { + "ext-curl": "*", + "ext-json": "*", + "php": ">=5.4", + "phpseclib/phpseclib": "~2.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "description": "Bare-bones OpenID Connect client", + "time": "2019-01-02T19:05:13+00:00" + }, { "name": "laravel/lumen-framework", "version": "v5.1.6", @@ -2657,8 +2690,101 @@ "xls", "xlsx" ], + "abandoned": "phpoffice/phpspreadsheet", "time": "2015-05-01T07:00:55+00:00" }, + { + "name": "phpseclib/phpseclib", + "version": "2.0.21", + "source": { + "type": "git", + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "9f1287e68b3f283339a9f98f67515dd619e5bf9d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/9f1287e68b3f283339a9f98f67515dd619e5bf9d", + "reference": "9f1287e68b3f283339a9f98f67515dd619e5bf9d", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "phing/phing": "~2.7", + "phpunit/phpunit": "^4.8.35|^5.7|^6.0", + "sami/sami": "~2.0", + "squizlabs/php_codesniffer": "~2.0" + }, + "suggest": { + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." + }, + "type": "library", + "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], + "psr-4": { + "phpseclib\\": "phpseclib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", + "keywords": [ + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" + ], + "time": "2019-07-12T12:53:49+00:00" + }, { "name": "psr/log", "version": "1.0.0", diff --git a/resources/views/env.blade.php b/resources/views/env.blade.php index f36b23dbc..94e92a996 100644 --- a/resources/views/env.blade.php +++ b/resources/views/env.blade.php @@ -97,6 +97,11 @@ # reCAPTCHA secret key POLR_RECAPTCHA_SECRET_KEY="{{$POLR_RECAPTCHA_SECRET}}" +OPENID_CONNECT_URL="{{$OPENID_CONNECT_URL}}" +OPENID_CONNECT_CONFIGURATION="{{$OPENID_CONNECT_CONFIGURATION}}" +OPENID_CONNECT_CLIENT_ID="{{$OPENID_CONNECT_CLIENT_ID}}" +OPENID_CONNECT_CLIENT_SECRET="{{$OPENID_CONNECT_CLIENT_SECRET}}" + # Set each to blank to disable mail @if($MAIL_ENABLED) MAIL_DRIVER=smtp diff --git a/resources/views/setup.blade.php b/resources/views/setup.blade.php index 39fbd6c4c..08609c592 100644 --- a/resources/views/setup.blade.php +++ b/resources/views/setup.blade.php @@ -207,6 +207,34 @@ Please ensure SMTP is properly set up before enabling password recovery.

+

+ OpenID Connect URL: + +

+ + +

+ OpenID Connect Client ID: + +

+ + +

+ OpenID Connect Client Secret: + +

+ + +

+ OpenID Connect Configuration: + +

+ +

Require reCAPTCHA for Registrations From 6d49b278683ef0b6e9f180c3352bf4188faad0ba Mon Sep 17 00:00:00 2001 From: Jamie0 Date: Sun, 15 Sep 2019 12:13:30 +0100 Subject: [PATCH 2/2] Add UI template entries to reflect OpenID options. Add OPENID_CONNECT_LOGIN_CAPTION env to allow customisation of login button text --- app/Http/Controllers/UserController.php | 8 ++++++ app/Providers/OpenIDConnectProvider.php | 34 +++++++++++++++++++++++ resources/views/login.blade.php | 5 ++++ resources/views/snippets/navbar.blade.php | 26 ++++++++++++----- 4 files changed, 66 insertions(+), 7 deletions(-) create mode 100644 app/Providers/OpenIDConnectProvider.php diff --git a/app/Http/Controllers/UserController.php b/app/Http/Controllers/UserController.php index dc08936ff..f4f90cee6 100644 --- a/app/Http/Controllers/UserController.php +++ b/app/Http/Controllers/UserController.php @@ -17,6 +17,9 @@ class UserController extends Controller { * @return Response */ public function displayLoginPage(Request $request) { + if (env('OPENID_CONNECT_CONFIGURATION') == 'always') { + return $this->performOpenIDConnect($request); + } return view('login'); } @@ -35,6 +38,11 @@ public function performLogoutUser(Request $request) { } public function performOpenIDConnect(Request $request) { + if (!env('OPENID_CONNECT_CONFIGURATION') || env('OPENID_CONNECT_CONFIGURATION') == 'none') { + abort(403, 'OpenID Connect is not enabled on this Polr installation.'); + return; + } + $client = app()->make('openidconnect'); $client->authenticate(); diff --git a/app/Providers/OpenIDConnectProvider.php b/app/Providers/OpenIDConnectProvider.php new file mode 100644 index 000000000..d99e06ea2 --- /dev/null +++ b/app/Providers/OpenIDConnectProvider.php @@ -0,0 +1,34 @@ +enabled = false; + return; + } + + $this->app->singleton('openidconnect', function ($app) { + $client = new OpenIDConnectClient( + env('OPENID_CONNECT_URL'), + env('OPENID_CONNECT_CLIENT_ID'), + env('OPENID_CONNECT_CLIENT_SECRET') + ); + + $client->redirectURL = LinkFactory::formatLink('login/openid'); + + return $client; + }); + } + +} diff --git a/resources/views/login.blade.php b/resources/views/login.blade.php index 511aaacc6..f62c05f94 100644 --- a/resources/views/login.blade.php +++ b/resources/views/login.blade.php @@ -10,6 +10,11 @@

+ @if (env('OPENID_CONNECT_CONFIGURATION') && env('OPENID_CONNECT_CONFIGURATION') != 'none') + {{ env('OPENID_CONNECT_LOGIN_CAPTION') ?: 'Authenticate with OpenID Connect' }} +
+ @endif + diff --git a/resources/views/snippets/navbar.blade.php b/resources/views/snippets/navbar.blade.php index 2329bc435..6d32eb1f4 100644 --- a/resources/views/snippets/navbar.blade.php +++ b/resources/views/snippets/navbar.blade.php @@ -38,13 +38,25 @@
@else