Implement OAuth and SAML2 support (#13764)
* Implement OAuth and SAML2 support via Socialite * Add socialite docs * fixes * Additional information added * wip * 22.3.0 targeted version * Allow mysql auth as long as there is a password saved Co-authored-by: laf <gh+n@laf.io> Co-authored-by: Tony Murray <murraytony@gmail.com>
@@ -18,7 +18,7 @@ class MysqlAuthorizer extends AuthorizerBase
|
|||||||
$username = $credentials['username'] ?? null;
|
$username = $credentials['username'] ?? null;
|
||||||
$password = $credentials['password'] ?? null;
|
$password = $credentials['password'] ?? null;
|
||||||
|
|
||||||
$user_data = User::thisAuth()->firstWhere(['username' => $username]);
|
$user_data = User::whereNotNull('password')->firstWhere(['username' => $username]);
|
||||||
$hash = $user_data->password;
|
$hash = $user_data->password;
|
||||||
$enabled = $user_data->enabled;
|
$enabled = $user_data->enabled;
|
||||||
|
|
||||||
@@ -76,7 +76,7 @@ class MysqlAuthorizer extends AuthorizerBase
|
|||||||
$user_id = $new_user->user_id;
|
$user_id = $new_user->user_id;
|
||||||
|
|
||||||
// set auth_id
|
// set auth_id
|
||||||
$new_user->auth_id = $this->getUserid($username);
|
$new_user->auth_id = (string) $this->getUserid($username);
|
||||||
$new_user->save();
|
$new_user->save();
|
||||||
|
|
||||||
if ($user_id) {
|
if ($user_id) {
|
||||||
|
@@ -83,6 +83,18 @@ class DynamicConfigItem implements \ArrayAccess
|
|||||||
return filter_var($value, FILTER_VALIDATE_EMAIL);
|
return filter_var($value, FILTER_VALIDATE_EMAIL);
|
||||||
} elseif ($this->type == 'array') {
|
} elseif ($this->type == 'array') {
|
||||||
return is_array($value); // this should probably have more complex validation via validator rules
|
return is_array($value); // this should probably have more complex validation via validator rules
|
||||||
|
} elseif ($this->type == 'array-sub-keyed') {
|
||||||
|
if (! is_array($value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($value as $v) {
|
||||||
|
if (! is_array($v)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
} elseif ($this->type == 'color') {
|
} elseif ($this->type == 'color') {
|
||||||
return (bool) preg_match('/^#?[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/', $value);
|
return (bool) preg_match('/^#?[0-9a-fA-F]{6}([0-9a-fA-F]{2})?$/', $value);
|
||||||
} elseif (in_array($this->type, ['text', 'password'])) {
|
} elseif (in_array($this->type, ['text', 'password'])) {
|
||||||
|
@@ -97,7 +97,7 @@ class AddUserCommand extends LnmsCommand
|
|||||||
$user->setPassword($password);
|
$user->setPassword($password);
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
$user->auth_id = LegacyAuth::get()->getUserid($user->username) ?: $user->user_id;
|
$user->auth_id = (string) LegacyAuth::get()->getUserid($user->username) ?: $user->user_id;
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
$this->info(__('commands.user:add.success', ['username' => $user->username]));
|
$this->info(__('commands.user:add.success', ['username' => $user->username]));
|
||||||
|
@@ -41,13 +41,21 @@ class LoginController extends Controller
|
|||||||
$this->middleware('guest')->except('logout');
|
$this->middleware('guest')->except('logout');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function username()
|
public function username(): string
|
||||||
{
|
{
|
||||||
return 'username';
|
return 'username';
|
||||||
}
|
}
|
||||||
|
|
||||||
public function showLoginForm()
|
/**
|
||||||
|
* @return \Illuminate\View\View|\Illuminate\Http\RedirectResponse|\Symfony\Component\HttpFoundation\Response
|
||||||
|
*/
|
||||||
|
public function showLoginForm(Request $request)
|
||||||
{
|
{
|
||||||
|
// Check if we want to redirect users to the socialite provider directly
|
||||||
|
if (! $request->has('redirect') && Config::get('auth.socialite.redirect') && array_key_first(Config::get('auth.socialite.configs', []))) {
|
||||||
|
return (new SocialiteController)->redirect($request, array_key_first(Config::get('auth.socialite.configs', [])));
|
||||||
|
}
|
||||||
|
|
||||||
if (Config::get('public_status')) {
|
if (Config::get('public_status')) {
|
||||||
$devices = Device::isActive()->with('location')->get();
|
$devices = Device::isActive()->with('location')->get();
|
||||||
|
|
||||||
@@ -57,7 +65,7 @@ class LoginController extends Controller
|
|||||||
return view('auth.login');
|
return view('auth.login');
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function loggedOut(Request $request)
|
protected function loggedOut(Request $request): \Illuminate\Http\RedirectResponse
|
||||||
{
|
{
|
||||||
return redirect(Config::get('auth_logout_handler', $this->redirectTo));
|
return redirect(Config::get('auth_logout_handler', $this->redirectTo));
|
||||||
}
|
}
|
||||||
|
223
app/Http/Controllers/Auth/SocialiteController.php
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* SocialiateController.php
|
||||||
|
*
|
||||||
|
* -Description-
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
* @link https://www.librenms.org
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Auth;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\User;
|
||||||
|
use Config;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Auth;
|
||||||
|
use Illuminate\Support\Facades\Event;
|
||||||
|
use Laravel\Socialite\Contracts\User as SocialiteUser;
|
||||||
|
use Laravel\Socialite\Facades\Socialite;
|
||||||
|
use LibreNMS\Config as LibreNMSConfig;
|
||||||
|
use LibreNMS\Exceptions\AuthenticationException;
|
||||||
|
use Log;
|
||||||
|
|
||||||
|
class SocialiteController extends Controller
|
||||||
|
{
|
||||||
|
/** @var SocialiteUser */
|
||||||
|
private $socialite_user;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
$this->injectConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function registerEventListeners(): void
|
||||||
|
{
|
||||||
|
foreach (LibreNMSConfig::get('auth.socialite.configs', []) as $provider => $config) {
|
||||||
|
// Treat not set as "disabled"
|
||||||
|
if (! isset($config['listener'])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$listener = $config['listener'];
|
||||||
|
|
||||||
|
if (class_exists($listener)) {
|
||||||
|
Event::listen(\SocialiteProviders\Manager\SocialiteWasCalled::class, "$listener@handle");
|
||||||
|
} else {
|
||||||
|
Log::error("Wrong value for auth.socialite.configs.$provider.listener set, class: '$listener' does not exist!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return RedirectResponse|\Symfony\Component\HttpFoundation\Response
|
||||||
|
*/
|
||||||
|
public function redirect(Request $request, string $provider)
|
||||||
|
{
|
||||||
|
// Re-store target url since it will be forgotten after the redirect
|
||||||
|
$request->session()->put('url.intended', redirect()->intended()->getTargetUrl());
|
||||||
|
|
||||||
|
return Socialite::driver($provider)->redirect();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function callback(Request $request, string $provider): RedirectResponse
|
||||||
|
{
|
||||||
|
$this->socialite_user = Socialite::driver($provider)->user();
|
||||||
|
|
||||||
|
// If we already have a valid session, user is trying to pair their account
|
||||||
|
if (Auth::user()) {
|
||||||
|
return $this->pairUser($provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->register($provider);
|
||||||
|
|
||||||
|
return $this->login($provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata endpoint used in SAML
|
||||||
|
*/
|
||||||
|
public function metadata(Request $request, string $provider): \Illuminate\Http\Response
|
||||||
|
{
|
||||||
|
$socialite = Socialite::driver($provider);
|
||||||
|
|
||||||
|
if (method_exists($socialite, 'getServiceProviderMetadata')) {
|
||||||
|
return $socialite->getServiceProviderMetadata();
|
||||||
|
}
|
||||||
|
|
||||||
|
return abort(404);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function login(string $provider): RedirectResponse
|
||||||
|
{
|
||||||
|
$user = User::where('auth_type', "socialite_$provider")
|
||||||
|
->where('auth_id', $this->socialite_user->getId())
|
||||||
|
->first();
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (! $user) {
|
||||||
|
throw new AuthenticationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
Auth::login($user);
|
||||||
|
|
||||||
|
return redirect()->intended();
|
||||||
|
} catch (AuthenticationException $e) {
|
||||||
|
flash()->addError($e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
return redirect()->route('login');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function register(string $provider): void
|
||||||
|
{
|
||||||
|
if (! LibreNMSConfig::get('auth.socialite.register', false)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = User::firstOrNew([
|
||||||
|
'auth_type' => "socialite_$provider",
|
||||||
|
'auth_id' => $this->socialite_user->getId(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($user->user_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->username = $this->buildUsername();
|
||||||
|
$user->email = $this->socialite_user->getEmail();
|
||||||
|
$user->realname = $this->buildRealName();
|
||||||
|
|
||||||
|
$user->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function pairUser(string $provider): RedirectResponse
|
||||||
|
{
|
||||||
|
$user = Auth::user();
|
||||||
|
$user->auth_type = "socialite_$provider";
|
||||||
|
$user->auth_id = $this->socialite_user->getId();
|
||||||
|
|
||||||
|
$user->save();
|
||||||
|
|
||||||
|
return redirect()->route('preferences.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildUsername(): string
|
||||||
|
{
|
||||||
|
return $this->socialite_user->getNickname()
|
||||||
|
?: $this->socialite_user->getEmail()
|
||||||
|
?: $this->buildRealName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function buildRealName(): string
|
||||||
|
{
|
||||||
|
$name = '';
|
||||||
|
|
||||||
|
// These methods only exist for a few providers
|
||||||
|
if (method_exists($this->socialite_user, 'getFirstName')) {
|
||||||
|
$name = $this->socialite_user->getFirstName();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method_exists($this->socialite_user, 'getLastName')) {
|
||||||
|
$name = trim($name . ' ' . $this->socialite_user->getLastName());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($name)) {
|
||||||
|
$name = $this->socialite_user->getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ! empty($name) ? $name : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Take the config from Librenms Config, and insert it into Laravel Config
|
||||||
|
*/
|
||||||
|
private function injectConfig(): void
|
||||||
|
{
|
||||||
|
foreach (LibreNMSConfig::get('auth.socialite.configs', []) as $provider => $config) {
|
||||||
|
Config::set("services.$provider", $config);
|
||||||
|
|
||||||
|
// Inject redirect URL automatically if not set
|
||||||
|
if (! Config::has("services.$provider.redirect")) {
|
||||||
|
Config::set("services.$provider.redirect",
|
||||||
|
route('socialite.callback', [$provider])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject SAML redirect url automatically
|
||||||
|
$this->injectSAML2Config($provider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function injectSAML2Config(string $provider): void
|
||||||
|
{
|
||||||
|
if ($provider !== 'saml2') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! Config::has("services.$provider.sp_acs")) {
|
||||||
|
Config::set("services.$provider.sp_acs", route('socialite.callback', [$provider]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! Config::has("services.$provider.client_id")) {
|
||||||
|
Config::set("services.$provider.client_id", '');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! Config::has("services.$provider.client_secret")) {
|
||||||
|
Config::set("services.$provider.client_secret", '');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -98,7 +98,7 @@ class UserController extends Controller
|
|||||||
$user = User::create($user);
|
$user = User::create($user);
|
||||||
|
|
||||||
$user->setPassword($request->new_password);
|
$user->setPassword($request->new_password);
|
||||||
$user->auth_id = LegacyAuth::get()->getUserid($user->username) ?: $user->user_id;
|
$user->auth_id = (string) LegacyAuth::get()->getUserid($user->username) ?: $user->user_id;
|
||||||
$this->updateDashboard($user, $request->get('dashboard'));
|
$this->updateDashboard($user, $request->get('dashboard'));
|
||||||
|
|
||||||
if ($user->save()) {
|
if ($user->save()) {
|
||||||
|
@@ -12,6 +12,6 @@ class VerifyCsrfToken extends Middleware
|
|||||||
* @var array
|
* @var array
|
||||||
*/
|
*/
|
||||||
protected $except = [
|
protected $except = [
|
||||||
// '*', // FIXME: CSRF completely disabled!
|
'/auth/*/callback',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@@ -53,6 +53,7 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
|
|
||||||
$this->app->booted('\LibreNMS\DB\Eloquent::initLegacyListeners');
|
$this->app->booted('\LibreNMS\DB\Eloquent::initLegacyListeners');
|
||||||
$this->app->booted('\LibreNMS\Config::load');
|
$this->app->booted('\LibreNMS\Config::load');
|
||||||
|
$this->app->booted('\App\Http\Controllers\Auth\SocialiteController::registerEventListeners');
|
||||||
|
|
||||||
$this->bootCustomBladeDirectives();
|
$this->bootCustomBladeDirectives();
|
||||||
$this->bootCustomValidators();
|
$this->bootCustomValidators();
|
||||||
@@ -174,5 +175,23 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
|
|
||||||
return $validator->passes();
|
return $validator->passes();
|
||||||
}, trans('validation.exists'));
|
}, trans('validation.exists'));
|
||||||
|
|
||||||
|
Validator::extend('url_or_xml', function ($attribute, $value): bool {
|
||||||
|
if (! is_string($value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filter_var($value, FILTER_VALIDATE_URL) !== false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
libxml_use_internal_errors(true);
|
||||||
|
$xml = simplexml_load_string($value);
|
||||||
|
if ($xml !== false) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -205,7 +205,7 @@ class LegacyUserProvider implements UserProvider
|
|||||||
/** @var User $user */
|
/** @var User $user */
|
||||||
$user->fill($new_user); // fill all attributes
|
$user->fill($new_user); // fill all attributes
|
||||||
$user->auth_type = $type; // doing this here in case it was null (legacy)
|
$user->auth_type = $type; // doing this here in case it was null (legacy)
|
||||||
$user->auth_id = $auth_id;
|
$user->auth_id = (string) $auth_id;
|
||||||
$user->save();
|
$user->save();
|
||||||
|
|
||||||
return $user;
|
return $user;
|
||||||
|
@@ -49,6 +49,7 @@
|
|||||||
"php-flasher/flasher-laravel": "^0.9",
|
"php-flasher/flasher-laravel": "^0.9",
|
||||||
"phpmailer/phpmailer": "~6.0",
|
"phpmailer/phpmailer": "~6.0",
|
||||||
"predis/predis": "^1.1",
|
"predis/predis": "^1.1",
|
||||||
|
"socialiteproviders/manager": "^4.1",
|
||||||
"symfony/yaml": "^4.0",
|
"symfony/yaml": "^4.0",
|
||||||
"tecnickcom/tcpdf": "^6.4",
|
"tecnickcom/tcpdf": "^6.4",
|
||||||
"tightenco/ziggy": "^0.9"
|
"tightenco/ziggy": "^0.9"
|
||||||
|
221
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "17d76cfe55a8adb13cb6df7c52662b86",
|
"content-hash": "b4fa65afc5e0f75e6f171f432932cb75",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "amenadiel/jpgraph",
|
"name": "amenadiel/jpgraph",
|
||||||
@@ -2188,6 +2188,75 @@
|
|||||||
},
|
},
|
||||||
"time": "2021-11-30T15:53:04+00:00"
|
"time": "2021-11-30T15:53:04+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "laravel/socialite",
|
||||||
|
"version": "v5.5.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/laravel/socialite.git",
|
||||||
|
"reference": "cb5b5538c207efa19aa5d7f46cd76acb03ec3055"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/laravel/socialite/zipball/cb5b5538c207efa19aa5d7f46cd76acb03ec3055",
|
||||||
|
"reference": "cb5b5538c207efa19aa5d7f46cd76acb03ec3055",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"guzzlehttp/guzzle": "^6.0|^7.0",
|
||||||
|
"illuminate/http": "^6.0|^7.0|^8.0|^9.0",
|
||||||
|
"illuminate/support": "^6.0|^7.0|^8.0|^9.0",
|
||||||
|
"league/oauth1-client": "^1.0",
|
||||||
|
"php": "^7.2|^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"illuminate/contracts": "^6.0|^7.0|^8.0|^9.0",
|
||||||
|
"mockery/mockery": "^1.0",
|
||||||
|
"orchestra/testbench": "^4.0|^5.0|^6.0|^7.0",
|
||||||
|
"phpunit/phpunit": "^8.0|^9.3"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "5.x-dev"
|
||||||
|
},
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Laravel\\Socialite\\SocialiteServiceProvider"
|
||||||
|
],
|
||||||
|
"aliases": {
|
||||||
|
"Socialite": "Laravel\\Socialite\\Facades\\Socialite"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Laravel\\Socialite\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Taylor Otwell",
|
||||||
|
"email": "taylor@laravel.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Laravel wrapper around OAuth 1 & OAuth 2 libraries.",
|
||||||
|
"homepage": "https://laravel.com",
|
||||||
|
"keywords": [
|
||||||
|
"laravel",
|
||||||
|
"oauth"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/laravel/socialite/issues",
|
||||||
|
"source": "https://github.com/laravel/socialite"
|
||||||
|
},
|
||||||
|
"time": "2022-02-01T16:31:36+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/tinker",
|
"name": "laravel/tinker",
|
||||||
"version": "v2.7.0",
|
"version": "v2.7.0",
|
||||||
@@ -2560,6 +2629,82 @@
|
|||||||
],
|
],
|
||||||
"time": "2021-11-21T11:48:40+00:00"
|
"time": "2021-11-21T11:48:40+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "league/oauth1-client",
|
||||||
|
"version": "v1.10.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/thephpleague/oauth1-client.git",
|
||||||
|
"reference": "88dd16b0cff68eb9167bfc849707d2c40ad91ddc"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/88dd16b0cff68eb9167bfc849707d2c40ad91ddc",
|
||||||
|
"reference": "88dd16b0cff68eb9167bfc849707d2c40ad91ddc",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"ext-openssl": "*",
|
||||||
|
"guzzlehttp/guzzle": "^6.0|^7.0",
|
||||||
|
"guzzlehttp/psr7": "^1.7|^2.0",
|
||||||
|
"php": ">=7.1||>=8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"ext-simplexml": "*",
|
||||||
|
"friendsofphp/php-cs-fixer": "^2.17",
|
||||||
|
"mockery/mockery": "^1.3.3",
|
||||||
|
"phpstan/phpstan": "^0.12.42",
|
||||||
|
"phpunit/phpunit": "^7.5||9.5"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-simplexml": "For decoding XML-based responses."
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.0-dev",
|
||||||
|
"dev-develop": "2.0-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"League\\OAuth1\\Client\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Ben Corlett",
|
||||||
|
"email": "bencorlett@me.com",
|
||||||
|
"homepage": "http://www.webcomm.com.au",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "OAuth 1.0 Client Library",
|
||||||
|
"keywords": [
|
||||||
|
"Authentication",
|
||||||
|
"SSO",
|
||||||
|
"authorization",
|
||||||
|
"bitbucket",
|
||||||
|
"identity",
|
||||||
|
"idp",
|
||||||
|
"oauth",
|
||||||
|
"oauth1",
|
||||||
|
"single sign on",
|
||||||
|
"trello",
|
||||||
|
"tumblr",
|
||||||
|
"twitter"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/thephpleague/oauth1-client/issues",
|
||||||
|
"source": "https://github.com/thephpleague/oauth1-client/tree/v1.10.0"
|
||||||
|
},
|
||||||
|
"time": "2021-08-15T23:05:49+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "librenms/laravel-vue-i18n-generator",
|
"name": "librenms/laravel-vue-i18n-generator",
|
||||||
"version": "0.1.47",
|
"version": "0.1.47",
|
||||||
@@ -4657,6 +4802,80 @@
|
|||||||
],
|
],
|
||||||
"time": "2021-09-25T23:10:38+00:00"
|
"time": "2021-09-25T23:10:38+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "socialiteproviders/manager",
|
||||||
|
"version": "v4.1.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/SocialiteProviders/Manager.git",
|
||||||
|
"reference": "4e63afbd26dc45ff263591de2a0970436a6a0bf9"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/4e63afbd26dc45ff263591de2a0970436a6a0bf9",
|
||||||
|
"reference": "4e63afbd26dc45ff263591de2a0970436a6a0bf9",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"illuminate/support": "^6.0 || ^7.0 || ^8.0 || ^9.0",
|
||||||
|
"laravel/socialite": "~4.0 || ~5.0",
|
||||||
|
"php": "^7.2 || ^8.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"mockery/mockery": "^1.2",
|
||||||
|
"phpunit/phpunit": "^6.0 || ^9.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"SocialiteProviders\\Manager\\ServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"SocialiteProviders\\Manager\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Andy Wendt",
|
||||||
|
"email": "andy@awendt.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Anton Komarev",
|
||||||
|
"email": "a.komarev@cybercog.su"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Miguel Piedrafita",
|
||||||
|
"email": "soy@miguelpiedrafita.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "atymic",
|
||||||
|
"email": "atymicq@gmail.com",
|
||||||
|
"homepage": "https://atymic.dev"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Easily add new or override built-in providers in Laravel Socialite.",
|
||||||
|
"homepage": "https://socialiteproviders.com",
|
||||||
|
"keywords": [
|
||||||
|
"laravel",
|
||||||
|
"manager",
|
||||||
|
"oauth",
|
||||||
|
"providers",
|
||||||
|
"socialite"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/socialiteproviders/manager/issues",
|
||||||
|
"source": "https://github.com/socialiteproviders/manager"
|
||||||
|
},
|
||||||
|
"time": "2022-01-23T22:40:23+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "spomky-labs/base64url",
|
"name": "spomky-labs/base64url",
|
||||||
"version": "v2.0.4",
|
"version": "v2.0.4",
|
||||||
|
@@ -170,6 +170,11 @@ return [
|
|||||||
Illuminate\Validation\ValidationServiceProvider::class,
|
Illuminate\Validation\ValidationServiceProvider::class,
|
||||||
Illuminate\View\ViewServiceProvider::class,
|
Illuminate\View\ViewServiceProvider::class,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Package Service Providers...
|
||||||
|
*/
|
||||||
|
\SocialiteProviders\Manager\ServiceProvider::class,
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Application Service Providers...
|
* Application Service Providers...
|
||||||
*/
|
*/
|
||||||
|
@@ -204,6 +204,6 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'same_site' => 'lax',
|
'same_site' => env('SESSION_SAME_SITE_COOKIE', 'lax'),
|
||||||
|
|
||||||
];
|
];
|
||||||
|
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
class IncreaseAuthIdLength extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->string('auth_id')->nullable()->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->integer('auth_id')->nullable()->change();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
352
doc/Extensions/OAuth-SAML.md
Normal file
@@ -0,0 +1,352 @@
|
|||||||
|
# OAuth and SAML Support
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
LibreNMS has support for [Laravel Socialite](https://github.com/laravel/socialite) to try and simplify the use of OAuth 1 or 2 providers such as using GitHub, Microsoft, Twitter + many more and SAML.
|
||||||
|
|
||||||
|
[Socialite Providers](https://socialiteproviders.com) supports more than 100+ 3rd parties so you will most likely find support for the SAML or OAuth provider you need without too much trouble.
|
||||||
|
|
||||||
|
Please do note however, these providers are not maintained by LibreNMS so we cannot add support for new ones and we can only provide you basic help with general configuration.
|
||||||
|
See the Socialite Providers website for more information on adding a new OAuth provider.
|
||||||
|
|
||||||
|
Below we will guide you on how to install SAML or some of these OAth providers, you should be able to use these as a guide on how to install any others you may need but **please, please, ensure you read the Socialite Providers documentation carefully**.
|
||||||
|
|
||||||
|
[GitHub Provider](https://socialiteproviders.com/GitHub/)
|
||||||
|
[Microsoft Provider](https://socialiteproviders.com/Microsoft/)
|
||||||
|
[SAML2](https://socialiteproviders.com/Saml2/)
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
LibreNMS version 22.3.0 or later.
|
||||||
|
|
||||||
|
Please ensure you set `APP_URL` within your `.env` file so that callback URLs work correctly with the identify provider.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
Once you have configured your OAuth or SAML2 provider, please ensure you check the [Post configuration settings](#post-configration-settings) section at the end.
|
||||||
|
|
||||||
|
## GitHub and Microsoft Examples
|
||||||
|
|
||||||
|
### Install plugin
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
First we need to install the plugin itself. The plugin name can be slightly different so be sure to check the Socialite Providers documentation and look for this line, `composer require socialiteproviders/github` which will give you the name you need for the command, i.e: `socialiteproviders/github`.
|
||||||
|
|
||||||
|
=== "GitHub"
|
||||||
|
|
||||||
|
`lnms plugin:add socialiteproviders/github`
|
||||||
|
|
||||||
|
=== "Microsoft"
|
||||||
|
|
||||||
|
`lnms plugin:add socialiteproviders/microsoft`
|
||||||
|
|
||||||
|
### Find the provider name
|
||||||
|
|
||||||
|
Next we need to find the provider name and writing it down
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
It's almost always the name of the provider in lowercase but can be different so check the Socialite Providers documentation and look for this line, `github => [` which will give you the name you need for the above command: `github`.
|
||||||
|
|
||||||
|
=== "GitHub"
|
||||||
|
|
||||||
|
For GitHub we can find the line:
|
||||||
|
```php
|
||||||
|
'github' => [
|
||||||
|
'client_id' => env('GITHUB_CLIENT_ID'),
|
||||||
|
'client_secret' => env('GITHUB_CLIENT_SECRET'),
|
||||||
|
'redirect' => env('GITHUB_REDIRECT_URI')
|
||||||
|
],
|
||||||
|
```
|
||||||
|
So our provider name is `github`, write this down.
|
||||||
|
|
||||||
|
|
||||||
|
=== "Microsoft"
|
||||||
|
|
||||||
|
For Microsoft we can find the line:
|
||||||
|
```php
|
||||||
|
'microsoft' => [
|
||||||
|
'client_id' => env('MICROSOFT_CLIENT_ID'),
|
||||||
|
'client_secret' => env('MICROSOFT_CLIENT_SECRET'),
|
||||||
|
'redirect' => env('MICROSOFT_REDIRECT_URI')
|
||||||
|
],
|
||||||
|
```
|
||||||
|
So our provider name is `microsoft`, write this down.
|
||||||
|
|
||||||
|
|
||||||
|
### Register OAuth application
|
||||||
|
|
||||||
|
#### Register a new application
|
||||||
|
|
||||||
|
Now we need some values from the OAuth provider itself, in most cases you need to register a new "OAuth application" at the providers site. This will vary from provider to provider but the process itself should be similar to the examples below.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
The callback URL is always: https://*your-librenms-url*/auth/*provider*/callback
|
||||||
|
It doesn't need to be a public available site, but it almost always needs to support TLS (https)!
|
||||||
|
|
||||||
|
=== "GitHub"
|
||||||
|
For our example with GitHub we go to [GitHub Developer Settings](https://github.com/settings/developers) and press "Register a new application":
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Fill out the form accordingly (with your own values):
|
||||||
|

|
||||||
|
|
||||||
|
=== "Microsoft"
|
||||||
|
For our example with Microsoft we go to ["Azure Active Directory" > "App registrations"](https://aad.portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps) and press "New registration"
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Fill out the form accordingly using your own values):
|
||||||
|

|
||||||
|
|
||||||
|
Copy the value of the **Application (client) ID** and **Directory (tenant) ID** and save them, you will need them in the next step.
|
||||||
|

|
||||||
|
|
||||||
|
#### Generate a new client secret
|
||||||
|
|
||||||
|
=== "GitHub"
|
||||||
|
|
||||||
|
Press 'Generate a new client secret' to get a new client secret.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Copy the **Client ID** and **Client secret**
|
||||||
|
|
||||||
|
In the example above it is:
|
||||||
|
|
||||||
|
**Client ID**: 7a41f1d8215640ca6b00
|
||||||
|
**Client secret**: ea03957288edd0e590be202b239e4f0ff26b8047
|
||||||
|
|
||||||
|
=== "Microsoft"
|
||||||
|
|
||||||
|
Select Certificates & secrets under Manage.
|
||||||
|
Select the 'New client secret' button.
|
||||||
|
Enter a value in Description and select one of the options for Expires and select 'Add'.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Copy the client secret **Value** (not Secret ID!) before you leave this page. You will need it in the next step.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
### Saving configuration
|
||||||
|
|
||||||
|
Now we need to set the configuration options for your provider within LibreNMS itself. Please replace the values in the examples below with the values you collected earlier:
|
||||||
|
|
||||||
|
The format of the configuration string is `auth.socialite.configs.*provider name*.*value*`
|
||||||
|
|
||||||
|
=== "GitHub"
|
||||||
|
|
||||||
|
!!! setting "auth/socialite"
|
||||||
|
```bash
|
||||||
|
lnms config:set auth.socialite.configs.github.client_id 7a41f1d8215640ca6b00
|
||||||
|
lnms config:set auth.socialite.configs.github.client_secret ea03957288edd0e590be202b239e4f0ff26b8047
|
||||||
|
```
|
||||||
|
|
||||||
|
=== "Microsoft"
|
||||||
|
|
||||||
|
!!! setting "auth/socialite"
|
||||||
|
```bash
|
||||||
|
lnms config:set auth.socialite.configs.microsoft.client_id 7983ac13-c955-40e9-9b85-5ba27be52a52
|
||||||
|
lnms config:set auth.socialite.configs.microsoft.client_secret J9P7Q~K2F5C.L243sqzbGj.cOOcjTBgAPak_l
|
||||||
|
lnms config:set auth.socialite.configs.microsoft.tenant a15edc05-152d-4eb4-973c-14f1fdc57d8b
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add provider event listener
|
||||||
|
|
||||||
|
The final step is to now add an event listener.
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
It's important to copy exactly the right value here,
|
||||||
|
It should begin with a `\` and end before the `::class.'@handle'`
|
||||||
|
|
||||||
|
=== "GitHub"
|
||||||
|
|
||||||
|
Find the section looking like:
|
||||||
|
```php
|
||||||
|
protected $listen = [
|
||||||
|
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
|
||||||
|
// ... other providers
|
||||||
|
\SocialiteProviders\GitHub\GitHubExtendSocialite::class.'@handle',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
Copy the part: `\SocialiteProviders\GitHub\GitHubExtendSocialite` and run;
|
||||||
|
!!! setting "auth/socialite"
|
||||||
|
```bash
|
||||||
|
lnms config:set auth.socialite.configs.github.listener "\SocialiteProviders\GitHub\GitHubExtendSocialite"
|
||||||
|
```
|
||||||
|
Don't forget the initial backslash (\\) !
|
||||||
|
|
||||||
|
=== "Microsoft"
|
||||||
|
|
||||||
|
Find the section looking like:
|
||||||
|
```php
|
||||||
|
protected $listen = [
|
||||||
|
\SocialiteProviders\Manager\SocialiteWasCalled::class => [
|
||||||
|
// ... other providers
|
||||||
|
\SocialiteProviders\Microsoft\MicrosoftExtendSocialite::class.'@handle',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
Copy the part: `\SocialiteProviders\Microsoft\MicrosoftExtendSocialite` and run;
|
||||||
|
!!! setting "auth/socialite"
|
||||||
|
```bash
|
||||||
|
lnms config:set auth.socialite.configs.microsoft.listener "\SocialiteProviders\Microsoft\MicrosoftExtendSocialite"
|
||||||
|
```
|
||||||
|
Don't forget the initial backslash (\\) !
|
||||||
|
|
||||||
|
Now you are done with setting up the OAuth provider!
|
||||||
|
If it doesn't work, please double check your configuration values by using the `config:get` command below.
|
||||||
|
|
||||||
|
!!! setting "auth/socialite"
|
||||||
|
```bash
|
||||||
|
lnms config:get auth.socialite
|
||||||
|
```
|
||||||
|
|
||||||
|
## SAML2 Example
|
||||||
|
|
||||||
|
### Install plugin
|
||||||
|
|
||||||
|
The first step is to install the plugin itself.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lnms plugin:add socialiteproviders/saml2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add configuration
|
||||||
|
|
||||||
|
Depending on what your identity provider (Google, Azure, ...) supports, the configuration could look different from what you see next so please use this as a rough guide.
|
||||||
|
It is up the IdP to provide the relevant details that you will need for configuration.
|
||||||
|
|
||||||
|
=== "Google"
|
||||||
|
|
||||||
|
Go to [https://admin.google.com/ac/apps/unified](https://admin.google.com/ac/apps/unified)
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
Press "DOWNLOAD METADATA" and save the file somewhere accessible by your LibreNMS server
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
ACS URL = https://*your-librenms-url*/auth/saml2/callback
|
||||||
|
Entity ID = https://*your-librenms-url*/auth/saml2
|
||||||
|
Name ID format = PERSISTANT
|
||||||
|
Name ID = Basic Information > Primary email
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
First name = http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname
|
||||||
|
Last name = http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname
|
||||||
|
Primary email = http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress
|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
!!! setting "auth/socialite"
|
||||||
|
```bash
|
||||||
|
lnms config:set auth.socialite.configs.saml2.metadata "$(cat /tmp/GoogleIDPMetadata.xml)"
|
||||||
|
```
|
||||||
|
|
||||||
|
Alternatively, you can copy the content of the file and run it like so, this will result in the exact same result as above.
|
||||||
|
!!! setting "auth/socialite"
|
||||||
|
```bash
|
||||||
|
lnms config:set auth.socialite.configs.saml2.metadata '''<?xml version="1.0" encoding
|
||||||
|
...
|
||||||
|
...
|
||||||
|
</md:EntityDescriptor>'''
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Using an Identity Provider metadata URL
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
This is the prefered and easiest way, if your IdP supports it!
|
||||||
|
|
||||||
|
!!! setting "auth/socialite"
|
||||||
|
```bash
|
||||||
|
lnms config:set auth.socialite.configs.saml2.metadata https://idp.co/metadata/xml
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using an Identity Provider metadata XML file
|
||||||
|
|
||||||
|
!!! setting "auth/socialite"
|
||||||
|
```bash
|
||||||
|
lnms config:set auth.socialite.configs.saml2.metadata "$(cat GoogleIDPMetadata.xml)"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Manually configuring the Identity Provider with a certificate string
|
||||||
|
|
||||||
|
!!! setting "auth/socialite"
|
||||||
|
```bash
|
||||||
|
lnms config:set auth.socialite.configs.saml2.acs https://idp.co/auth/acs
|
||||||
|
lnms config:set auth.socialite.configs.saml2.entityid http://saml.to/trust
|
||||||
|
lnms config:set auth.socialite.configs.saml2.certificate MIIC4jCCAcqgAwIBAgIQbDO5YO....
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Manually configuring the Identity Provider with a certificate file
|
||||||
|
|
||||||
|
!!! setting "auth/socialite"
|
||||||
|
```bash
|
||||||
|
lnms config:set auth.socialite.configs.saml2.acs https://idp.co/auth/acs
|
||||||
|
lnms config:set auth.socialite.configs.saml2.entityid http://saml.to/trust
|
||||||
|
lnms config:set auth.socialite.configs.saml2.certificate "$(cat /path/to/certificate.pem)"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add provider event listener
|
||||||
|
|
||||||
|
Now we just need to define the listener service within LibreNMS:
|
||||||
|
|
||||||
|
!!! setting "auth/socialite"
|
||||||
|
```bash
|
||||||
|
lnms config:set auth.socialite.configs.saml2.listener "\SocialiteProviders\Saml2\Saml2ExtendSocialite"
|
||||||
|
```
|
||||||
|
|
||||||
|
### SESSION_SAME_SITE_COOKIE
|
||||||
|
|
||||||
|
You most likely will need to set `SESSION_SAME_SITE_COOKIE=none` in `.env` if you use SAML2!
|
||||||
|
|
||||||
|
!!! note
|
||||||
|
Don't forget to run `lnms config:clear` after you modify `.env` to flush the config cache
|
||||||
|
|
||||||
|
### Service provider metadata
|
||||||
|
|
||||||
|
Your identify provider might ask you for your Service Provider (SP) metadata.
|
||||||
|
LibreNMS exposes all of this information from your [LibreNMS install](https://*your-librenms-url*/auth/saml2/metadata)
|
||||||
|
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
If it doesn't work, please double check your configuration values by using the `config:get` command below.
|
||||||
|
|
||||||
|
!!! setting "auth/socialite"
|
||||||
|
```bash
|
||||||
|
lnms config:get auth.socialite
|
||||||
|
```
|
||||||
|
|
||||||
|
### Redirect URL
|
||||||
|
If you have a need to, then you can override redirect url with the following commands:
|
||||||
|
|
||||||
|
=== "OAuth"
|
||||||
|
Replace `github` and the relevant URL below with your identity provider details.
|
||||||
|
`lnms config:set auth.socialite.configs.github.redirect https://demo.librenms.org/auth/github/callback`
|
||||||
|
|
||||||
|
=== "SAML2"
|
||||||
|
`lnms config:set auth.socialite.configs.saml2.sp_acs auth/saml2/callback`
|
||||||
|
|
||||||
|
## Post configuration settings
|
||||||
|
|
||||||
|
!!! setting "auth/socialite"
|
||||||
|
From here you can configure the settings for any identity providers you have configured along with some bespoke options.
|
||||||
|
|
||||||
|
Redirect Login page: This setting will skip your LibreNMS login and take the end user straight to the first idP you configured.
|
||||||
|
|
||||||
|
Allow registration via provider: If this setting is disabled, new users signing in via the idP will not be authenticated. This setting allows a local user to be automatically created which permits their login.
|
BIN
doc/img/socialite-github-1.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
doc/img/socialite-github-2.png
Normal file
After Width: | Height: | Size: 37 KiB |
BIN
doc/img/socialite-github-3.png
Normal file
After Width: | Height: | Size: 25 KiB |
BIN
doc/img/socialite-microsoft-1.png
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
doc/img/socialite-microsoft-2.png
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
doc/img/socialite-microsoft-3.png
Normal file
After Width: | Height: | Size: 32 KiB |
BIN
doc/img/socialite-microsoft-4.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
doc/img/socialite-microsoft-5.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
doc/img/socialite-microsoft-6.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
doc/img/socialite-saml-google-1.png
Normal file
After Width: | Height: | Size: 34 KiB |
BIN
doc/img/socialite-saml-google-2.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
doc/img/socialite-saml-google-3.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
doc/img/socialite-saml-google-4.png
Normal file
After Width: | Height: | Size: 17 KiB |
BIN
doc/img/socialite-saml-google-5.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
doc/img/socialite-saml-google-6.png
Normal file
After Width: | Height: | Size: 6.1 KiB |
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"/js/app.js": "/js/app.js?id=50f82aa2aac679191fac",
|
"/js/app.js": "/js/app.js?id=dba3e37f44ce826e96d0",
|
||||||
"/js/manifest.js": "/js/manifest.js?id=2951ae529be231f05a93",
|
"/js/manifest.js": "/js/manifest.js?id=2951ae529be231f05a93",
|
||||||
"/css/vendor.css": "/css/vendor.css?id=2568831af31dbfc3128a",
|
"/css/vendor.css": "/css/vendor.css?id=2568831af31dbfc3128a",
|
||||||
"/css/app.css": "/css/app.css?id=936fe619dcf1bac0a33f",
|
"/css/app.css": "/css/app.css?id=936fe619dcf1bac0a33f",
|
||||||
"/js/vendor.js": "/js/vendor.js?id=c5fd3d75a63757080dbb",
|
"/js/vendor.js": "/js/vendor.js?id=c5fd3d75a63757080dbb",
|
||||||
"/js/lang/de.js": "/js/lang/de.js?id=1aedfce25e3daad3046a",
|
"/js/lang/de.js": "/js/lang/de.js?id=1aedfce25e3daad3046a",
|
||||||
"/js/lang/en.js": "/js/lang/en.js?id=3617cad4c8bcf97221a6",
|
"/js/lang/en.js": "/js/lang/en.js?id=f16225e77f5dbe2541ac",
|
||||||
"/js/lang/fr.js": "/js/lang/fr.js?id=a20c4c78eb5f9f4a374b",
|
"/js/lang/fr.js": "/js/lang/fr.js?id=a20c4c78eb5f9f4a374b",
|
||||||
"/js/lang/it.js": "/js/lang/it.js?id=6b0bdf3be6dc3bf0a167",
|
"/js/lang/it.js": "/js/lang/it.js?id=6b0bdf3be6dc3bf0a167",
|
||||||
"/js/lang/ru.js": "/js/lang/ru.js?id=f6b7c078755312a0907c",
|
"/js/lang/ru.js": "/js/lang/ru.js?id=f6b7c078755312a0907c",
|
||||||
|
@@ -372,6 +372,38 @@
|
|||||||
"order": 1,
|
"order": 1,
|
||||||
"type": "text"
|
"type": "text"
|
||||||
},
|
},
|
||||||
|
"auth.socialite.redirect": {
|
||||||
|
"group": "auth",
|
||||||
|
"section": "socialite",
|
||||||
|
"order": 1,
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
"auth.socialite.register": {
|
||||||
|
"group": "auth",
|
||||||
|
"section": "socialite",
|
||||||
|
"order": 2,
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
|
||||||
|
"auth.socialite.configs": {
|
||||||
|
"group": "auth",
|
||||||
|
"section": "socialite",
|
||||||
|
"order": 3,
|
||||||
|
"type": "array-sub-keyed",
|
||||||
|
"validate": {
|
||||||
|
"value": "array",
|
||||||
|
"value.*": "array",
|
||||||
|
"value.*.listener": ["not_regex:/[:|@]/"],
|
||||||
|
"value.*.listener": ["regex:/^\\\\SocialiteProviders\\\\[^\\\\]+\\\\[^\\\\]+ExtendSocialite$/"],
|
||||||
|
"value.*.redirect": "url",
|
||||||
|
"value.saml.metadata": "url_or_xml",
|
||||||
|
"value.saml.acs": "url",
|
||||||
|
"value.saml.entityid": "url"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
"auth_ad_check_certificates": {
|
"auth_ad_check_certificates": {
|
||||||
"default": false,
|
"default": false,
|
||||||
"group": "auth",
|
"group": "auth",
|
||||||
|
@@ -105,6 +105,7 @@
|
|||||||
"text",
|
"text",
|
||||||
"boolean",
|
"boolean",
|
||||||
"array",
|
"array",
|
||||||
|
"array-sub-keyed",
|
||||||
"password",
|
"password",
|
||||||
"email",
|
"email",
|
||||||
"color",
|
"color",
|
||||||
|
@@ -2039,7 +2039,7 @@ users:
|
|||||||
Columns:
|
Columns:
|
||||||
- { Field: user_id, Type: 'int unsigned', 'Null': false, Extra: auto_increment }
|
- { Field: user_id, Type: 'int unsigned', 'Null': false, Extra: auto_increment }
|
||||||
- { Field: auth_type, Type: varchar(32), 'Null': true, Extra: '' }
|
- { Field: auth_type, Type: varchar(32), 'Null': true, Extra: '' }
|
||||||
- { Field: auth_id, Type: int, 'Null': true, Extra: '' }
|
- { Field: auth_id, Type: varchar(255), 'Null': true, Extra: '' }
|
||||||
- { Field: username, Type: varchar(255), 'Null': false, Extra: '' }
|
- { Field: username, Type: varchar(255), 'Null': false, Extra: '' }
|
||||||
- { Field: password, Type: varchar(255), 'Null': true, Extra: '' }
|
- { Field: password, Type: varchar(255), 'Null': true, Extra: '' }
|
||||||
- { Field: realname, Type: varchar(64), 'Null': false, Extra: '' }
|
- { Field: realname, Type: varchar(64), 'Null': false, Extra: '' }
|
||||||
|
@@ -134,6 +134,7 @@ nav:
|
|||||||
- Galera Database Cluster: Extensions/Galera-Cluster.md
|
- Galera Database Cluster: Extensions/Galera-Cluster.md
|
||||||
- IRC Bot Extensions: Extensions/IRC-Bot-Extensions.md
|
- IRC Bot Extensions: Extensions/IRC-Bot-Extensions.md
|
||||||
- IRC Bot: Extensions/IRC-Bot.md
|
- IRC Bot: Extensions/IRC-Bot.md
|
||||||
|
- Oauth/SAML support: Extensions/OAuth-SAML.md
|
||||||
- RRDCached: Extensions/RRDCached.md
|
- RRDCached: Extensions/RRDCached.md
|
||||||
- RRDTune: Extensions/RRDTune.md
|
- RRDTune: Extensions/RRDTune.md
|
||||||
- Scaling LibreNMS: Extensions/Distributed-Poller.md
|
- Scaling LibreNMS: Extensions/Distributed-Poller.md
|
||||||
|
@@ -7615,21 +7615,6 @@ parameters:
|
|||||||
count: 1
|
count: 1
|
||||||
path: app/Http/Controllers/Ajax/RipeNccApiController.php
|
path: app/Http/Controllers/Ajax/RipeNccApiController.php
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Http\\\\Controllers\\\\Auth\\\\LoginController\\:\\:loggedOut\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: app/Http/Controllers/Auth/LoginController.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Http\\\\Controllers\\\\Auth\\\\LoginController\\:\\:showLoginForm\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: app/Http/Controllers/Auth/LoginController.php
|
|
||||||
|
|
||||||
-
|
|
||||||
message: "#^Method App\\\\Http\\\\Controllers\\\\Auth\\\\LoginController\\:\\:username\\(\\) has no return type specified\\.$#"
|
|
||||||
count: 1
|
|
||||||
path: app/Http/Controllers/Auth/LoginController.php
|
|
||||||
|
|
||||||
-
|
-
|
||||||
message: "#^Method App\\\\Http\\\\Controllers\\\\Auth\\\\TwoFactorController\\:\\:showTwoFactorForm\\(\\) has no return type specified\\.$#"
|
message: "#^Method App\\\\Http\\\\Controllers\\\\Auth\\\\TwoFactorController\\:\\:showTwoFactorForm\\(\\) has no return type specified\\.$#"
|
||||||
count: 1
|
count: 1
|
||||||
|
139
resources/js/components/SettingArraySubKeyed.vue
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
<!--
|
||||||
|
- SettingArraySubKeyed.vue
|
||||||
|
-
|
||||||
|
- Description-
|
||||||
|
-
|
||||||
|
- This program is free software: you can redistribute it and/or modify
|
||||||
|
- it under the terms of the GNU General Public License as published by
|
||||||
|
- the Free Software Foundation, either version 3 of the License, or
|
||||||
|
- (at your option) any later version.
|
||||||
|
-
|
||||||
|
- This program is distributed in the hope that it will be useful,
|
||||||
|
- but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
|
||||||
|
- GNU General Public License for more details.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the GNU General Public License
|
||||||
|
- along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
-
|
||||||
|
- @package LibreNMS
|
||||||
|
- @link https://www.librenms.org
|
||||||
|
-->
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-tooltip="disabled ? $t('settings.readonly') : false">
|
||||||
|
<div v-for="(item, index) in localList">
|
||||||
|
<b>{{ index }}</b>
|
||||||
|
<div v-for="(item, subindex) in item" class="input-group">
|
||||||
|
<span :class="['input-group-addon', disabled ? 'disabled' : '']">{{ subindex }}</span>
|
||||||
|
<input type="text"
|
||||||
|
class="form-control"
|
||||||
|
:value="item"
|
||||||
|
:readonly="disabled"
|
||||||
|
@blur="updateSubItem(index, subindex, $event.target.value)"
|
||||||
|
@keyup.enter="updateSubItem(index, subindex, $event.target.value)"
|
||||||
|
>
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button v-if="!disabled" @click="removeSubItem(index, subindex)" type="button" class="btn btn-danger"><i class="fa fa-minus-circle"></i></button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-if="!disabled">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" v-model="newSubItemKey[index]" class="form-control" placeholder="Key">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-8">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" v-model="newSubItemValue[index]" @keyup.enter="addSubItem(index)" class="form-control" placeholder="Value">
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button @click="addSubItem(index)" type="button" class="btn btn-primary"><i class="fa fa-plus-circle"></i></button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr/>
|
||||||
|
</div>
|
||||||
|
<div v-if="!disabled">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" v-model="newSubArray" @keyup.enter="addSubArray" class="form-control">
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button @click="addSubArray" type="button" class="btn btn-primary"><i class="fa fa-plus-circle"></i></button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import BaseSetting from "./BaseSetting";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "SettingArraySubKeyed",
|
||||||
|
mixins: [BaseSetting],
|
||||||
|
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
localList: this.value ?? new Object(),
|
||||||
|
newSubItemKey: {},
|
||||||
|
newSubItemValue: {},
|
||||||
|
newSubArray: ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addSubItem(index) {
|
||||||
|
if (this.disabled) return;
|
||||||
|
|
||||||
|
var obj = {};
|
||||||
|
obj[this.newSubItemKey[index]] = this.newSubItemValue[index];
|
||||||
|
|
||||||
|
if (Object.keys(this.localList[index]).length === 0) {
|
||||||
|
this.localList[index] = new Object();
|
||||||
|
}
|
||||||
|
Object.assign(this.localList[index], obj);
|
||||||
|
this.$emit('input', this.localList);
|
||||||
|
this.newSubItemValue[index] = "";
|
||||||
|
this.newSubItemKey[index] = "";
|
||||||
|
},
|
||||||
|
removeSubItem(index, subindex) {
|
||||||
|
if (this.disabled) return;
|
||||||
|
delete this.localList[index][subindex];
|
||||||
|
|
||||||
|
if (Object.keys(this.localList[index]).length === 0) {
|
||||||
|
delete this.localList[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit('input', this.localList);
|
||||||
|
},
|
||||||
|
updateSubItem(index, subindex, value) {
|
||||||
|
if (this.disabled || this.localList[index][subindex] === value) return;
|
||||||
|
this.localList[index][subindex] = value;
|
||||||
|
this.$emit('input', this.localList);
|
||||||
|
},
|
||||||
|
addSubArray() {
|
||||||
|
if (this.disabled) return;
|
||||||
|
this.localList[this.newSubArray] = new Object();
|
||||||
|
this.$emit('input', this.localList);
|
||||||
|
this.newSubArray = "";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value(updated) {
|
||||||
|
// careful to avoid loops with this
|
||||||
|
this.localList = updated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.input-group {
|
||||||
|
margin-bottom: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-addon:not(.disabled) {
|
||||||
|
cursor: move;
|
||||||
|
}
|
||||||
|
</style>
|
@@ -30,6 +30,7 @@ return [
|
|||||||
'general' => ['name' => 'General Authentication Settings'],
|
'general' => ['name' => 'General Authentication Settings'],
|
||||||
'ad' => ['name' => 'Active Directory Settings'],
|
'ad' => ['name' => 'Active Directory Settings'],
|
||||||
'ldap' => ['name' => 'LDAP Settings'],
|
'ldap' => ['name' => 'LDAP Settings'],
|
||||||
|
'socialite' => ['name' => 'Socialite Settings'],
|
||||||
],
|
],
|
||||||
'authorization' => [
|
'authorization' => [
|
||||||
'device-group' => ['name' => 'Device Group Settings'],
|
'device-group' => ['name' => 'Device Group Settings'],
|
||||||
@@ -258,6 +259,20 @@ return [
|
|||||||
'astext' => [
|
'astext' => [
|
||||||
'description' => 'Key to hold cache of autonomous systems descriptions',
|
'description' => 'Key to hold cache of autonomous systems descriptions',
|
||||||
],
|
],
|
||||||
|
'auth' => [
|
||||||
|
'socialite' => [
|
||||||
|
'redirect' => [
|
||||||
|
'description' => 'Redirect Login page',
|
||||||
|
'help' => 'Login page should redirect immediately to the first defined provider.<br><br>TIPS: You can prevent it by appending ?redirect=0 in the url',
|
||||||
|
],
|
||||||
|
'register' => [
|
||||||
|
'description' => 'Allow registration via provider',
|
||||||
|
],
|
||||||
|
'configs' => [
|
||||||
|
'description' => 'Provider configs',
|
||||||
|
],
|
||||||
|
],
|
||||||
|
],
|
||||||
'auth_ad_base_dn' => [
|
'auth_ad_base_dn' => [
|
||||||
'description' => 'Base DN',
|
'description' => 'Base DN',
|
||||||
'help' => 'groups and users must be under this dn. Example: dc=example,dc=com',
|
'help' => 'groups and users must be under this dn. Example: dc=example,dc=com',
|
||||||
|
@@ -46,8 +46,18 @@
|
|||||||
<button type="submit" id="login" class="btn btn-primary btn-block" name="submit">
|
<button type="submit" id="login" class="btn btn-primary btn-block" name="submit">
|
||||||
<i class="fa fa-btn fa-sign-in"></i> {{ __('Login') }}
|
<i class="fa fa-btn fa-sign-in"></i> {{ __('Login') }}
|
||||||
</button>
|
</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
@foreach (\LibreNMS\Config::get('auth.socialite.configs', []) as $provider => $config)
|
||||||
|
<br>
|
||||||
|
<form role="form" action="{{ route('socialite.redirect', $provider) }}" method="post">
|
||||||
|
{{ csrf_field() }}
|
||||||
|
<button type="submit" id="login" class="btn btn-success btn-block">
|
||||||
|
<i class="fab fa-btn fa-{{ $provider }}"></i> {{ __('Login with') }} {{ ucfirst($provider) }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</x-panel>
|
</x-panel>
|
||||||
|
@@ -101,6 +101,19 @@
|
|||||||
</form>
|
</form>
|
||||||
</x-panel>
|
</x-panel>
|
||||||
|
|
||||||
|
@config('auth.socialite.configs')
|
||||||
|
<x-panel title="{{ __('OAuth/SAML Authentication') }}">
|
||||||
|
@foreach (\LibreNMS\Config::get('auth.socialite.configs', []) as $provider => $config)
|
||||||
|
<form role="form" action="{{ route('socialite.redirect', $provider) }}" method="post">
|
||||||
|
{{ csrf_field() }}
|
||||||
|
<button type="submit" id="login" class="btn btn-success btn-block">
|
||||||
|
<i class="fab fa-btn fa-{{ $provider }}"></i> {{ __('Register with') }} {{ ucfirst($provider) }}
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
@endforeach
|
||||||
|
</x-panel>
|
||||||
|
@endconfig
|
||||||
|
|
||||||
@config('twofactor')
|
@config('twofactor')
|
||||||
<x-panel title="{{ __('Two-Factor Authentication') }}">
|
<x-panel title="{{ __('Two-Factor Authentication') }}">
|
||||||
@if($twofactor)
|
@if($twofactor)
|
||||||
|
@@ -16,6 +16,13 @@ use Illuminate\Support\Facades\Route;
|
|||||||
// Auth
|
// Auth
|
||||||
Auth::routes(['register' => false, 'reset' => false, 'verify' => false]);
|
Auth::routes(['register' => false, 'reset' => false, 'verify' => false]);
|
||||||
|
|
||||||
|
// Socialite
|
||||||
|
Route::prefix('auth')->name('socialite.')->group(function () {
|
||||||
|
Route::post('{provider}/redirect', [\App\Http\Controllers\Auth\SocialiteController::class, 'redirect'])->name('redirect');
|
||||||
|
Route::match(['get', 'post'], '{provider}/callback', [\App\Http\Controllers\Auth\SocialiteController::class, 'callback'])->name('callback');
|
||||||
|
Route::get('{provider}/metadata', [\App\Http\Controllers\Auth\SocialiteController::class, 'metadata'])->name('metadata');
|
||||||
|
});
|
||||||
|
|
||||||
// WebUI
|
// WebUI
|
||||||
Route::group(['middleware' => ['auth'], 'guard' => 'auth'], function () {
|
Route::group(['middleware' => ['auth'], 'guard' => 'auth'], function () {
|
||||||
|
|
||||||
|