Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic OpenID Connect support #533

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/Factories/LinkFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 11 additions & 0 deletions app/Http/Controllers/SetupController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down
76 changes: 76 additions & 0 deletions app/Http/Controllers/UserController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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');
}

Expand All @@ -34,6 +37,79 @@ public function performLogoutUser(Request $request) {
return redirect()->route('index');
}

public function performOpenIDConnect(Request $request) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Jamie0 I would suggest a more agnostic approach here. For example, I would add a factory and strategy pattern here to determine the authentication system based on the configuration.

I would suggest adding an additional env var to determine which Authentication system to use. Ex, default, openid, oauth2.0. This way, you can check which authentication system is configured and then instantiate the respective strategy for authenticating.

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();

$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');
Expand Down
3 changes: 3 additions & 0 deletions app/Http/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -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']);
Expand Down
34 changes: 34 additions & 0 deletions app/Providers/OpenIDConnectProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php
namespace App\Providers;

use App\Factories\LinkFactory;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Jumbojett\OpenIDConnectClient;

class OpenIDConnectProvider extends ServiceProvider {

/**
* Connect to the OpenID Connect server, if the configuration exists.
*
* @return null
*/
public function register() {
if (!env('OPENID_CONNECT_URL')) {
$this->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;
});
}

}
1 change: 1 addition & 0 deletions bootstrap/app.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);

/*
Expand Down
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
130 changes: 128 additions & 2 deletions composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions resources/views/env.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions resources/views/login.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@
<div class="col-md-3"></div>
<div class="col-md-6">
<form action="login" method="POST">
@if (env('OPENID_CONNECT_CONFIGURATION') && env('OPENID_CONNECT_CONFIGURATION') != 'none')
<a href="/login/openid" class="btn btn-primary">{{ env('OPENID_CONNECT_LOGIN_CAPTION') ?: 'Authenticate with OpenID Connect' }}</a>
<hr />
@endif

<input type="text" placeholder="username" name="username" class="form-control login-field" />
<input type="password" placeholder="password" name="password" class="form-control login-field" />
<input type="hidden" name='_token' value='{{csrf_token()}}' />
Expand Down
Loading