mirror of
https://github.com/librenms/librenms.git
synced 2024-09-21 02:18:39 +00:00
Implement RBAC (only built in roles) (#15212)
* Install bouncer * Seeder and level migration * Display and edit roles * remove unused deluser page * Update Radius and SSO to assign roles * update AlertUtil direct level check to use roles instead * rewrite ircbot auth handling * Remove legacy auth getUserlist and getUserlevel methods, add getRoles Set roles in LegacyUserProvider * Small cleanups * centralize role sync code show roles on user preferences page * VueSelect component WIP and a little docs * WIP * SelectControllers id and text fields. * LibrenmsSelect component extracted from SettingSelectDynamic * Handle multiple selections * allow type coercion * full width settings * final style adjustments * Final compiled assets update * Style fixes * Fix SSO tests * Lint cleanups * small style fix * don't use json yet * Update baseline for usptream package issues * Change schema, not 100% sure it is correct not sure why xor doesn't work
This commit is contained in:
parent
4fc27d98e9
commit
2cd207028a
@ -123,18 +123,18 @@ class AlertUtil
|
||||
}
|
||||
}
|
||||
foreach ($users as $user) {
|
||||
if (empty($user['email'])) {
|
||||
if (empty($user->email)) {
|
||||
continue; // no email, skip this user
|
||||
}
|
||||
if (empty($user['realname'])) {
|
||||
$user['realname'] = $user['username'];
|
||||
}
|
||||
if (Config::get('alert.globals') && ($user['level'] >= 5 && $user['level'] < 10)) {
|
||||
$contacts[$user['email']] = $user['realname'];
|
||||
} elseif (Config::get('alert.admins') && $user['level'] == 10) {
|
||||
$contacts[$user['email']] = $user['realname'];
|
||||
} elseif (Config::get('alert.users') == true && in_array($user['user_id'], $uids)) {
|
||||
$contacts[$user['email']] = $user['realname'];
|
||||
|
||||
$name = $user->realname ?: $user->username;
|
||||
|
||||
if (Config::get('alert.globals') && $user->hasGlobalRead()) {
|
||||
$contacts[$user->email] = $name;
|
||||
} elseif (Config::get('alert.admins') && $user->isAdmin()) {
|
||||
$contacts[$user->email] = $name;
|
||||
} elseif (Config::get('alert.users') && in_array($user['user_id'], $uids)) {
|
||||
$contacts[$user->email] = $name;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace LibreNMS\Authentication;
|
||||
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Enum\LegacyAuthLevel;
|
||||
use LibreNMS\Exceptions\AuthenticationException;
|
||||
use LibreNMS\Exceptions\LdapMissingException;
|
||||
|
||||
@ -92,14 +93,13 @@ class ADAuthorizationAuthorizer extends MysqlAuthorizer
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getUserlevel($username)
|
||||
public function getRoles(string $username): array
|
||||
{
|
||||
$userlevel = $this->authLdapSessionCacheGet('userlevel');
|
||||
if ($userlevel) {
|
||||
return $userlevel;
|
||||
} else {
|
||||
$userlevel = 0;
|
||||
$roles = $this->authLdapSessionCacheGet('roles');
|
||||
if ($roles !== null) {
|
||||
return $roles;
|
||||
}
|
||||
$roles = [];
|
||||
|
||||
// Find all defined groups $username is in
|
||||
$search = ldap_search(
|
||||
@ -110,18 +110,25 @@ class ADAuthorizationAuthorizer extends MysqlAuthorizer
|
||||
);
|
||||
$entries = ldap_get_entries($this->ldap_connection, $search);
|
||||
|
||||
// Loop the list and find the highest level
|
||||
// collect all roles
|
||||
$auth_ad_groups = Config::get('auth_ad_groups');
|
||||
foreach ($entries[0]['memberof'] as $entry) {
|
||||
$group_cn = $this->getCn($entry);
|
||||
$auth_ad_groups = Config::get('auth_ad_groups');
|
||||
if ($auth_ad_groups[$group_cn]['level'] > $userlevel) {
|
||||
$userlevel = $auth_ad_groups[$group_cn]['level'];
|
||||
|
||||
if (isset($auth_ad_groups[$group_cn]['roles']) && is_array($auth_ad_groups[$group_cn]['roles'])) {
|
||||
$roles = array_merge($roles, $auth_ad_groups[$group_cn]['roles']);
|
||||
} elseif (isset($auth_ad_groups[$group_cn]['level'])) {
|
||||
$role = LegacyAuthLevel::tryFrom($auth_ad_groups[$group_cn]['level'])?->getName();
|
||||
if ($role) {
|
||||
$roles[] = $role;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->authLdapSessionCacheSet('userlevel', $userlevel);
|
||||
$roles = array_unique($roles);
|
||||
$this->authLdapSessionCacheSet('roles', $roles);
|
||||
|
||||
return $userlevel;
|
||||
return $roles;
|
||||
}
|
||||
|
||||
public function getUserid($username)
|
||||
|
@ -7,6 +7,7 @@
|
||||
namespace LibreNMS\Authentication;
|
||||
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Enum\LegacyAuthLevel;
|
||||
use LibreNMS\Exceptions\AuthenticationException;
|
||||
use LibreNMS\Exceptions\LdapMissingException;
|
||||
|
||||
@ -124,26 +125,33 @@ class ActiveDirectoryAuthorizer extends AuthorizerBase
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getUserlevel($username)
|
||||
public function getRoles(string $username): array
|
||||
{
|
||||
$userlevel = 0;
|
||||
$roles = [];
|
||||
if (! Config::get('auth_ad_require_groupmembership', true)) {
|
||||
if (Config::get('auth_ad_global_read', false)) {
|
||||
$userlevel = 5;
|
||||
$roles[] = 'global-read';
|
||||
}
|
||||
}
|
||||
|
||||
// cycle through defined groups, test for memberOf-ship
|
||||
foreach (Config::get('auth_ad_groups', []) as $group => $level) {
|
||||
foreach (Config::get('auth_ad_groups', []) as $group => $data) {
|
||||
try {
|
||||
if ($this->userInGroup($username, $group)) {
|
||||
$userlevel = max($userlevel, $level['level']);
|
||||
if (isset($data['roles']) && is_array($data['roles'])) {
|
||||
$roles = array_merge($roles, $data['roles']);
|
||||
} elseif (isset($data['level'])) {
|
||||
$role = LegacyAuthLevel::tryFrom($data['level'])?->getName();
|
||||
if ($role) {
|
||||
$roles[] = $role;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (AuthenticationException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
return $userlevel;
|
||||
return array_unique($roles);
|
||||
}
|
||||
|
||||
public function getUserid($username)
|
||||
|
@ -148,32 +148,6 @@ trait ActiveDirectoryCommon
|
||||
return $ldap_groups;
|
||||
}
|
||||
|
||||
public function getUserlist()
|
||||
{
|
||||
$connection = $this->getConnection();
|
||||
|
||||
$userlist = [];
|
||||
$ldap_groups = $this->getGroupList();
|
||||
|
||||
foreach ($ldap_groups as $ldap_group) {
|
||||
$search_filter = "(&(memberOf:1.2.840.113556.1.4.1941:=$ldap_group)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))";
|
||||
if (Config::get('auth_ad_user_filter')) {
|
||||
$search_filter = '(&' . Config::get('auth_ad_user_filter') . $search_filter . ')';
|
||||
}
|
||||
$attributes = ['samaccountname', 'displayname', 'objectsid', 'mail'];
|
||||
$search = ldap_search($connection, Config::get('auth_ad_base_dn'), $search_filter, $attributes);
|
||||
$results = ldap_get_entries($connection, $search);
|
||||
|
||||
foreach ($results as $result) {
|
||||
if (isset($result['samaccountname'][0])) {
|
||||
$userlist[$result['samaccountname'][0]] = $this->userFromAd($result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_values($userlist);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a user array from an AD LDAP entry
|
||||
* Must have the attributes: objectsid, samaccountname, displayname, mail
|
||||
@ -191,7 +165,6 @@ trait ActiveDirectoryCommon
|
||||
'realname' => $entry['displayname'][0],
|
||||
'email' => isset($entry['mail'][0]) ? $entry['mail'][0] : null,
|
||||
'descr' => '',
|
||||
'level' => $this->getUserlevel($entry['samaccountname'][0]),
|
||||
'can_modify_passwd' => 0,
|
||||
];
|
||||
}
|
||||
|
@ -45,29 +45,11 @@ abstract class AuthorizerBase implements Authorizer
|
||||
return static::$HAS_AUTH_USERMANAGEMENT;
|
||||
}
|
||||
|
||||
public function addUser($username, $password, $level = 0, $email = '', $realname = '', $can_modify_passwd = 0, $description = '')
|
||||
{
|
||||
//not supported by default
|
||||
return false;
|
||||
}
|
||||
|
||||
public function deleteUser($user_id)
|
||||
{
|
||||
//not supported by default
|
||||
return false;
|
||||
}
|
||||
|
||||
public function canUpdateUsers()
|
||||
{
|
||||
return static::$CAN_UPDATE_USER;
|
||||
}
|
||||
|
||||
public function updateUser($user_id, $realname, $level, $can_modify_passwd, $email)
|
||||
{
|
||||
//not supported by default
|
||||
return false;
|
||||
}
|
||||
|
||||
public function authIsExternal()
|
||||
{
|
||||
return static::$AUTH_IS_EXTERNAL;
|
||||
@ -77,4 +59,9 @@ abstract class AuthorizerBase implements Authorizer
|
||||
{
|
||||
return $_SERVER[Config::get('http_auth_header')] ?? $_SERVER['PHP_AUTH_USER'] ?? null;
|
||||
}
|
||||
|
||||
public function getRoles(string $username): array
|
||||
{
|
||||
return []; // no roles by default
|
||||
}
|
||||
}
|
||||
|
@ -34,21 +34,6 @@ class HttpAuthAuthorizer extends MysqlAuthorizer
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getUserlevel($username)
|
||||
{
|
||||
$user_level = parent::getUserlevel($username);
|
||||
|
||||
if ($user_level) {
|
||||
return $user_level;
|
||||
}
|
||||
|
||||
if (Config::has('http_auth_guest')) {
|
||||
return parent::getUserlevel(Config::get('http_auth_guest'));
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function getUserid($username)
|
||||
{
|
||||
$user_id = parent::getUserid($username);
|
||||
|
@ -26,6 +26,7 @@ namespace LibreNMS\Authentication;
|
||||
|
||||
use App\Models\User;
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Enum\LegacyAuthLevel;
|
||||
use LibreNMS\Exceptions\AuthenticationException;
|
||||
use LibreNMS\Exceptions\LdapMissingException;
|
||||
|
||||
@ -113,32 +114,38 @@ class LdapAuthorizationAuthorizer extends AuthorizerBase
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getUserlevel($username)
|
||||
public function getRoles(string $username): array
|
||||
{
|
||||
$userlevel = $this->authLdapSessionCacheGet('userlevel');
|
||||
if ($userlevel) {
|
||||
return $userlevel;
|
||||
} else {
|
||||
$userlevel = 0;
|
||||
$roles = $this->authLdapSessionCacheGet('roles');
|
||||
if ($roles !== null) {
|
||||
return $roles;
|
||||
}
|
||||
$roles = [];
|
||||
|
||||
// Find all defined groups $username is in
|
||||
$filter = '(&(|(cn=' . implode(')(cn=', array_keys(Config::get('auth_ldap_groups'))) . '))(' . Config::get('auth_ldap_groupmemberattr') . '=' . $this->getMembername($username) . '))';
|
||||
$search = ldap_search($this->ldap_connection, Config::get('auth_ldap_groupbase'), $filter);
|
||||
$entries = ldap_get_entries($this->ldap_connection, $search);
|
||||
|
||||
// Loop the list and find the highest level
|
||||
$authLdapGroups = Config::get('auth_ldap_groups');
|
||||
// Collect all roles
|
||||
foreach ($entries as $entry) {
|
||||
$groupname = $entry['cn'][0];
|
||||
$authLdapGroups = Config::get('auth_ldap_groups');
|
||||
if ($authLdapGroups[$groupname]['level'] > $userlevel) {
|
||||
$userlevel = $authLdapGroups[$groupname]['level'];
|
||||
|
||||
if (isset($authLdapGroups[$groupname]['roles']) && is_array($authLdapGroups[$groupname]['roles'])) {
|
||||
$roles = array_merge($roles, $authLdapGroups[$groupname]['roles']);
|
||||
} elseif (isset($authLdapGroups[$groupname]['level'])) {
|
||||
$role = LegacyAuthLevel::tryFrom($authLdapGroups[$groupname]['level'])?->getName();
|
||||
if ($role) {
|
||||
$roles[] = $role;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->authLdapSessionCacheSet('userlevel', $userlevel);
|
||||
$roles = array_unique($roles);
|
||||
$this->authLdapSessionCacheSet('roles', $roles);
|
||||
|
||||
return $userlevel;
|
||||
return $roles;
|
||||
}
|
||||
|
||||
public function getUserid($username)
|
||||
@ -173,56 +180,38 @@ class LdapAuthorizationAuthorizer extends AuthorizerBase
|
||||
return $user_id;
|
||||
}
|
||||
|
||||
public function getUserlist()
|
||||
public function getUser($user_id)
|
||||
{
|
||||
$userlist = [];
|
||||
|
||||
$filter = '(' . Config::get('auth_ldap_prefix') . '*)';
|
||||
if (Config::get('auth_ldap_userlist_filter') != null) {
|
||||
$filter = '(' . Config::get('auth_ldap_userlist_filter') . ')';
|
||||
}
|
||||
$uid_attr = strtolower(Config::get('auth_ldap_uid_attribute', 'uidnumber'));
|
||||
$filter = "($uid_attr=$user_id)";
|
||||
$search = ldap_search($this->ldap_connection, trim(Config::get('auth_ldap_suffix'), ','), $filter);
|
||||
$entries = ldap_get_entries($this->ldap_connection, $search);
|
||||
|
||||
if ($entries['count']) {
|
||||
foreach ($entries as $entry) {
|
||||
$username = $entry['uid'][0];
|
||||
$realname = $entry['cn'][0];
|
||||
$user_id = $entry['uidnumber'][0];
|
||||
$email = $entry[Config::get('auth_ldap_emailattr')][0];
|
||||
$ldap_groups = $this->getGroupList();
|
||||
foreach ($ldap_groups as $ldap_group) {
|
||||
$ldap_comparison = ldap_compare(
|
||||
$this->ldap_connection,
|
||||
$ldap_group,
|
||||
Config::get('auth_ldap_groupmemberattr'),
|
||||
$this->getMembername($username)
|
||||
);
|
||||
if (! Config::has('auth_ldap_group') || $ldap_comparison === true) {
|
||||
$userlist[] = [
|
||||
'username' => $username,
|
||||
'realname' => $realname,
|
||||
'user_id' => $user_id,
|
||||
'email' => $email,
|
||||
];
|
||||
}
|
||||
$entry = $entries[0];
|
||||
$username = $entry['uid'][0];
|
||||
$realname = $entry['cn'][0];
|
||||
$user_id = $entry['uidnumber'][0];
|
||||
$email = $entry[Config::get('auth_ldap_emailattr')][0];
|
||||
$ldap_groups = $this->getGroupList();
|
||||
foreach ($ldap_groups as $ldap_group) {
|
||||
$ldap_comparison = ldap_compare(
|
||||
$this->ldap_connection,
|
||||
$ldap_group,
|
||||
Config::get('auth_ldap_groupmemberattr'),
|
||||
$this->getMembername($username)
|
||||
);
|
||||
if (! Config::has('auth_ldap_group') || $ldap_comparison === true) {
|
||||
return [
|
||||
'username' => $username,
|
||||
'realname' => $realname,
|
||||
'user_id' => $user_id,
|
||||
'email' => $email,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $userlist;
|
||||
}
|
||||
|
||||
public function getUser($user_id)
|
||||
{
|
||||
foreach ($this->getUserlist() as $user) {
|
||||
if ($user['user_id'] == $user_id) {
|
||||
$user['level'] = $this->getUserlevel($user['username']);
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ namespace LibreNMS\Authentication;
|
||||
|
||||
use ErrorException;
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Enum\LegacyAuthLevel;
|
||||
use LibreNMS\Exceptions\AuthenticationException;
|
||||
use LibreNMS\Exceptions\LdapMissingException;
|
||||
|
||||
@ -101,10 +102,8 @@ class LdapAuthorizer extends AuthorizerBase
|
||||
return false;
|
||||
}
|
||||
|
||||
public function getUserlevel($username)
|
||||
public function getRoles(string $username): array
|
||||
{
|
||||
$userlevel = 0;
|
||||
|
||||
try {
|
||||
$connection = $this->getLdapConnection();
|
||||
$groups = Config::get('auth_ldap_groups');
|
||||
@ -126,18 +125,27 @@ class LdapAuthorizer extends AuthorizerBase
|
||||
$search = ldap_search($connection, Config::get('auth_ldap_groupbase'), $filter);
|
||||
$entries = ldap_get_entries($connection, $search);
|
||||
|
||||
// Loop the list and find the highest level
|
||||
$roles = [];
|
||||
// Collect all assigned roles
|
||||
foreach ($entries as $entry) {
|
||||
$groupname = $entry['cn'][0];
|
||||
if ($groups[$groupname]['level'] > $userlevel) {
|
||||
$userlevel = $groups[$groupname]['level'];
|
||||
|
||||
if (isset($groups[$groupname]['roles']) && is_array($groups[$groupname]['roles'])) {
|
||||
$roles = array_merge($roles, $groups[$groupname]['roles']);
|
||||
} elseif (isset($groups[$groupname]['level'])) {
|
||||
$role = LegacyAuthLevel::tryFrom($groups[$groupname]['level'])?->getName();
|
||||
if ($role) {
|
||||
$roles[] = $role;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique($roles);
|
||||
} catch (AuthenticationException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
}
|
||||
|
||||
return $userlevel;
|
||||
return [];
|
||||
}
|
||||
|
||||
public function getUserid($username)
|
||||
@ -161,65 +169,6 @@ class LdapAuthorizer extends AuthorizerBase
|
||||
return -1;
|
||||
}
|
||||
|
||||
public function getUserlist()
|
||||
{
|
||||
$userlist = [];
|
||||
|
||||
try {
|
||||
$connection = $this->getLdapConnection();
|
||||
|
||||
$ldap_groups = $this->getGroupList();
|
||||
if (empty($ldap_groups)) {
|
||||
d_echo('No groups defined. Cannot search for users.');
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
$filter = '(' . Config::get('auth_ldap_prefix') . '*)';
|
||||
if (Config::get('auth_ldap_userlist_filter') != null) {
|
||||
$filter = '(' . Config::get('auth_ldap_userlist_filter') . ')';
|
||||
}
|
||||
|
||||
// build group filter
|
||||
$group_filter = '';
|
||||
foreach ($ldap_groups as $group) {
|
||||
$group_filter .= '(memberOf=' . trim($group) . ')';
|
||||
}
|
||||
if (count($ldap_groups) > 1) {
|
||||
$group_filter = "(|$group_filter)";
|
||||
}
|
||||
|
||||
// search using memberOf
|
||||
$search = ldap_search($connection, trim(Config::get('auth_ldap_suffix'), ','), "(&$filter$group_filter)");
|
||||
if (ldap_count_entries($connection, $search)) {
|
||||
foreach (ldap_get_entries($connection, $search) as $entry) {
|
||||
$user = $this->ldapToUser($entry);
|
||||
$userlist[$user['username']] = $user;
|
||||
}
|
||||
} else {
|
||||
// probably doesn't support memberOf, go through all users, this could be slow
|
||||
$search = ldap_search($connection, trim(Config::get('auth_ldap_suffix'), ','), $filter);
|
||||
foreach (ldap_get_entries($connection, $search) as $entry) {
|
||||
foreach ($ldap_groups as $ldap_group) {
|
||||
if (ldap_compare(
|
||||
$connection,
|
||||
$ldap_group,
|
||||
Config::get('auth_ldap_groupmemberattr', 'memberUid'),
|
||||
$this->getMembername($entry['uid'][0])
|
||||
)) {
|
||||
$user = $this->ldapToUser($entry);
|
||||
$userlist[$user['username']] = $user;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (AuthenticationException $e) {
|
||||
echo $e->getMessage() . PHP_EOL;
|
||||
}
|
||||
|
||||
return $userlist;
|
||||
}
|
||||
|
||||
public function getUser($user_id)
|
||||
{
|
||||
$connection = $this->getLdapConnection();
|
||||
@ -362,7 +311,6 @@ class LdapAuthorizer extends AuthorizerBase
|
||||
'realname' => $entry['cn'][0],
|
||||
'user_id' => $entry[$uid_attr][0],
|
||||
'email' => $entry[Config::get('auth_ldap_emailattr', 'mail')][0],
|
||||
'level' => $this->getUserlevel($entry['uid'][0]),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,6 @@ namespace LibreNMS\Authentication;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use LibreNMS\DB\Eloquent;
|
||||
use LibreNMS\Exceptions\AuthenticationException;
|
||||
|
||||
class MysqlAuthorizer extends AuthorizerBase
|
||||
@ -55,71 +54,17 @@ class MysqlAuthorizer extends AuthorizerBase
|
||||
}
|
||||
}
|
||||
|
||||
public function addUser($username, $password, $level = 0, $email = '', $realname = '', $can_modify_passwd = 1, $descr = '')
|
||||
{
|
||||
$user_array = get_defined_vars();
|
||||
|
||||
// no nulls
|
||||
$user_array = array_filter($user_array, function ($field) {
|
||||
return ! is_null($field);
|
||||
});
|
||||
|
||||
$new_user = User::thisAuth()->firstOrNew(['username' => $username], $user_array);
|
||||
|
||||
// only update new users
|
||||
if (! $new_user->user_id) {
|
||||
$new_user->auth_type = LegacyAuth::getType();
|
||||
$new_user->setPassword($password);
|
||||
$new_user->email = (string) $new_user->email;
|
||||
|
||||
$new_user->save();
|
||||
$user_id = $new_user->user_id;
|
||||
|
||||
// set auth_id
|
||||
$new_user->auth_id = (string) $this->getUserid($username);
|
||||
$new_user->save();
|
||||
|
||||
if ($user_id) {
|
||||
return $user_id;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function userExists($username, $throw_exception = false)
|
||||
{
|
||||
return User::thisAuth()->where('username', $username)->exists();
|
||||
}
|
||||
|
||||
public function getUserlevel($username)
|
||||
{
|
||||
return User::thisAuth()->where('username', $username)->value('level');
|
||||
}
|
||||
|
||||
public function getUserid($username)
|
||||
{
|
||||
// for mysql user_id == auth_id
|
||||
return User::thisAuth()->where('username', $username)->value('user_id');
|
||||
}
|
||||
|
||||
public function deleteUser($user_id)
|
||||
{
|
||||
// could be used on cli, use Eloquent helper
|
||||
Eloquent::DB()->table('bill_perms')->where('user_id', $user_id)->delete();
|
||||
Eloquent::DB()->table('devices_perms')->where('user_id', $user_id)->delete();
|
||||
Eloquent::DB()->table('devices_group_perms')->where('user_id', $user_id)->delete();
|
||||
Eloquent::DB()->table('ports_perms')->where('user_id', $user_id)->delete();
|
||||
Eloquent::DB()->table('users_prefs')->where('user_id', $user_id)->delete();
|
||||
|
||||
return (bool) User::destroy($user_id);
|
||||
}
|
||||
|
||||
public function getUserlist()
|
||||
{
|
||||
return User::thisAuth()->orderBy('username')->get()->toArray();
|
||||
}
|
||||
|
||||
public function getUser($user_id)
|
||||
{
|
||||
$user = User::find($user_id);
|
||||
@ -129,16 +74,4 @@ class MysqlAuthorizer extends AuthorizerBase
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function updateUser($user_id, $realname, $level, $can_modify_passwd, $email)
|
||||
{
|
||||
$user = User::find($user_id);
|
||||
|
||||
$user->realname = $realname;
|
||||
$user->level = (int) $level;
|
||||
$user->can_modify_passwd = (int) $can_modify_passwd;
|
||||
$user->email = $email;
|
||||
|
||||
return $user->save();
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,12 @@
|
||||
|
||||
namespace LibreNMS\Authentication;
|
||||
|
||||
use App\Models\User;
|
||||
use Dapphp\Radius\Radius;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Enum\LegacyAuthLevel;
|
||||
use LibreNMS\Exceptions\AuthenticationException;
|
||||
use LibreNMS\Util\Debug;
|
||||
|
||||
@ -13,8 +17,9 @@ class RadiusAuthorizer extends MysqlAuthorizer
|
||||
protected static $CAN_UPDATE_USER = true;
|
||||
protected static $CAN_UPDATE_PASSWORDS = false;
|
||||
|
||||
/** @var Radius */
|
||||
protected $radius;
|
||||
protected Radius $radius;
|
||||
|
||||
private array $roles = []; // temp cache of roles
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@ -33,30 +38,35 @@ class RadiusAuthorizer extends MysqlAuthorizer
|
||||
|
||||
$password = $credentials['password'] ?? null;
|
||||
if ($this->radius->accessRequest($credentials['username'], $password) === true) {
|
||||
// attribute 11 is "Filter-Id", apply and enforce user role (level) if set
|
||||
$user = User::thisAuth()->firstOrNew(['username' => $credentials['username']], [
|
||||
'auth_type' => LegacyAuth::getType(),
|
||||
'can_modify_passwd' => 0,
|
||||
]);
|
||||
$user->save();
|
||||
|
||||
$this->roles[$credentials['username']] = $this->getDefaultRoles();
|
||||
|
||||
// cache a single role from the Filter-ID attribute now because attributes are cleared every accessRequest
|
||||
$filter_id_attribute = $this->radius->getAttribute(11);
|
||||
$level = match ($filter_id_attribute) {
|
||||
'librenms_role_admin' => 10,
|
||||
'librenms_role_normal' => 1,
|
||||
'librenms_role_global-read' => 5,
|
||||
default => Config::get('radius.default_level', 1)
|
||||
};
|
||||
|
||||
// if Filter-Id was given and the user exists, update the level
|
||||
if ($filter_id_attribute && $this->userExists($credentials['username'])) {
|
||||
$user = \App\Models\User::find($this->getUserid($credentials['username']));
|
||||
$user->level = $level;
|
||||
$user->save();
|
||||
|
||||
return true;
|
||||
if ($filter_id_attribute && Str::startsWith($filter_id_attribute, 'librenms_role_')) {
|
||||
$this->roles[$credentials['username']] = [substr($filter_id_attribute, 14)];
|
||||
}
|
||||
|
||||
$this->addUser($credentials['username'], $password, $level, '', $credentials['username'], 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new AuthenticationException();
|
||||
}
|
||||
|
||||
public function getRoles(string $username): array
|
||||
{
|
||||
return $this->roles[$username] ?? $this->getDefaultRoles();
|
||||
}
|
||||
|
||||
private function getDefaultRoles(): array
|
||||
{
|
||||
// return roles or translate from the old radius.default_level
|
||||
return Config::get('radius.default_roles')
|
||||
?: Arr::wrap(LegacyAuthLevel::from(Config::get('radius.default_level') ?? 1)->getName());
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,10 @@
|
||||
|
||||
namespace LibreNMS\Authentication;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Support\Arr;
|
||||
use LibreNMS\Config;
|
||||
use LibreNMS\Enum\LegacyAuthLevel;
|
||||
use LibreNMS\Exceptions\AuthenticationException;
|
||||
use LibreNMS\Exceptions\InvalidIpException;
|
||||
use LibreNMS\Util\IP;
|
||||
@ -46,19 +49,21 @@ class SSOAuthorizer extends MysqlAuthorizer
|
||||
throw new AuthenticationException('\'sso.user_attr\' config setting was not found or was empty');
|
||||
}
|
||||
|
||||
// Build the user's details from attributes
|
||||
$email = $this->authSSOGetAttr(Config::get('sso.email_attr'));
|
||||
$realname = $this->authSSOGetAttr(Config::get('sso.realname_attr'));
|
||||
$description = $this->authSSOGetAttr(Config::get('sso.descr_attr'));
|
||||
$can_modify_passwd = 0;
|
||||
// User has already been approved by the authenticator so if automatic user create/update is enabled, do it
|
||||
if (Config::get('sso.create_users') || Config::get('sso.update_users')) {
|
||||
$user = User::thisAuth()->firstOrNew(['username' => $credentials['username']]);
|
||||
|
||||
$level = $this->authSSOCalculateLevel();
|
||||
$create = ! $user->exists && Config::get('sso.create_users');
|
||||
$update = $user->exists && Config::get('sso.update_users');
|
||||
|
||||
// User has already been approved by the authenicator so if automatic user create/update is enabled, do it
|
||||
if (Config::get('sso.create_users') && ! $this->userExists($credentials['username'])) {
|
||||
$this->addUser($credentials['username'], null, $level, $email, $realname, $can_modify_passwd, $description ? $description : 'SSO User');
|
||||
} elseif (Config::get('sso.update_users') && $this->userExists($credentials['username'])) {
|
||||
$this->updateUser($this->getUserid($credentials['username']), $realname, $level, $can_modify_passwd, $email);
|
||||
if ($create || $update) {
|
||||
$user->auth_type = LegacyAuth::getType();
|
||||
$user->can_modify_passwd = 0;
|
||||
$user->realname = $this->authSSOGetAttr(Config::get('sso.realname_attr'));
|
||||
$user->email = $this->authSSOGetAttr(Config::get('sso.email_attr'));
|
||||
$user->descr = $this->authSSOGetAttr(Config::get('sso.descr_attr')) ?: 'SSO User';
|
||||
$user->save();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -147,15 +152,19 @@ class SSOAuthorizer extends MysqlAuthorizer
|
||||
/**
|
||||
* Calculate the privilege level to assign to a user based on the configuration and attributes supplied by the external authenticator.
|
||||
* Returns an integer if the permission is found, or raises an AuthenticationException if the configuration is not valid.
|
||||
* Converts the legacy level into a role
|
||||
*
|
||||
* @return int
|
||||
* @param string $username
|
||||
* @return array
|
||||
*
|
||||
* @throws AuthenticationException
|
||||
*/
|
||||
public function authSSOCalculateLevel()
|
||||
public function getRoles(string $username): array
|
||||
{
|
||||
if (Config::get('sso.group_strategy') === 'attribute') {
|
||||
if (Config::get('sso.level_attr')) {
|
||||
if (is_numeric($this->authSSOGetAttr(Config::get('sso.level_attr')))) {
|
||||
return (int) $this->authSSOGetAttr(Config::get('sso.level_attr'));
|
||||
return Arr::wrap(LegacyAuthLevel::tryFrom((int) $this->authSSOGetAttr(Config::get('sso.level_attr')))?->getName());
|
||||
} else {
|
||||
throw new AuthenticationException('group assignment by attribute requested, but httpd is not setting the attribute to a number');
|
||||
}
|
||||
@ -164,13 +173,13 @@ class SSOAuthorizer extends MysqlAuthorizer
|
||||
}
|
||||
} elseif (Config::get('sso.group_strategy') === 'map') {
|
||||
if (Config::get('sso.group_level_map') && is_array(Config::get('sso.group_level_map')) && Config::get('sso.group_delimiter') && Config::get('sso.group_attr')) {
|
||||
return (int) $this->authSSOParseGroups();
|
||||
return Arr::wrap(LegacyAuthLevel::tryFrom((int) $this->authSSOParseGroups())?->getName());
|
||||
} else {
|
||||
throw new AuthenticationException('group assignment by level map requested, but \'sso.group_level_map\', \'sso.group_attr\', or \'sso.group_delimiter\' are not set in your config');
|
||||
}
|
||||
} elseif (Config::get('sso.group_strategy') === 'static') {
|
||||
if (Config::get('sso.static_level')) {
|
||||
return (int) Config::get('sso.static_level');
|
||||
return Arr::wrap(LegacyAuthLevel::tryFrom((int) Config::get('sso.static_level'))?->getName());
|
||||
} else {
|
||||
throw new AuthenticationException('group assignment by static level was requested, but \'sso.group_level_map\' was not set in your config');
|
||||
}
|
||||
|
31
LibreNMS/Enum/LegacyAuthLevel.php
Normal file
31
LibreNMS/Enum/LegacyAuthLevel.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace LibreNMS\Enum;
|
||||
|
||||
enum LegacyAuthLevel: int
|
||||
{
|
||||
case user = 1;
|
||||
case global_read = 5;
|
||||
case admin = 10;
|
||||
case demo = 11;
|
||||
|
||||
public function fromName(string $name): ?LegacyAuthLevel
|
||||
{
|
||||
return match ($name) {
|
||||
'admin' => LegacyAuthLevel::admin,
|
||||
'user' => LegacyAuthLevel::user,
|
||||
'global-read', 'global_read' => LegacyAuthLevel::global_read,
|
||||
'demo' => LegacyAuthLevel::demo,
|
||||
default => null
|
||||
};
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
if ($this == LegacyAuthLevel::global_read) {
|
||||
return 'global-read';
|
||||
}
|
||||
|
||||
return $this->name;
|
||||
}
|
||||
}
|
@ -20,13 +20,16 @@
|
||||
|
||||
namespace LibreNMS;
|
||||
|
||||
use LibreNMS\Authentication\LegacyAuth;
|
||||
use App\Models\Device;
|
||||
use App\Models\Eventlog;
|
||||
use App\Models\Port;
|
||||
use App\Models\Service;
|
||||
use App\Models\User;
|
||||
use LibreNMS\DB\Eloquent;
|
||||
use LibreNMS\Enum\AlertState;
|
||||
use LibreNMS\Util\Number;
|
||||
use LibreNMS\Util\Time;
|
||||
use LibreNMS\Util\Version;
|
||||
use Permissions;
|
||||
|
||||
class IRCBot
|
||||
{
|
||||
@ -657,18 +660,11 @@ class IRCBot
|
||||
$this->log("HostAuth on irc matching $host to " . $this->getUserHost($this->data));
|
||||
}
|
||||
if (preg_match("/$host/", $this->getUserHost($this->data))) {
|
||||
$user_id = LegacyAuth::get()->getUserid($nms_user);
|
||||
$user = LegacyAuth::get()->getUser($user_id);
|
||||
$this->user['name'] = $user['username'];
|
||||
$this->user['id'] = $user_id;
|
||||
$this->user['level'] = LegacyAuth::get()->getUserlevel($user['username']);
|
||||
$user = User::firstWhere('username', $nms_user);
|
||||
$this->user['user'] = $user;
|
||||
$this->user['expire'] = (time() + ($this->config['irc_authtime'] * 3600));
|
||||
if ($this->user['level'] < 5) {
|
||||
$this->user['devices'] = Permissions::devicesForUser($this->user['id'])->toArray();
|
||||
$this->user['ports'] = Permissions::portsForUser($this->user['id'])->toArray();
|
||||
}
|
||||
if ($this->debug) {
|
||||
$this->log("HostAuth on irc for '" . $user['username'] . "', ID: '" . $user_id . "', Host: '" . $host);
|
||||
$this->log("HostAuth on irc for '" . $user->username . "', ID: '" . $user->user_id . "', Host: '" . $host);
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -695,31 +691,22 @@ class IRCBot
|
||||
if (strlen($params[0]) == 64) {
|
||||
if ($this->tokens[$this->getUser($this->data)] == $params[0]) {
|
||||
$this->user['expire'] = (time() + ($this->config['irc_authtime'] * 3600));
|
||||
$tmp_user = LegacyAuth::get()->getUser($this->user['id']);
|
||||
$tmp = LegacyAuth::get()->getUserlevel($tmp_user['username']);
|
||||
$this->user['level'] = $tmp;
|
||||
if ($this->user['level'] < 5) {
|
||||
$this->user['devices'] = Permissions::devicesForUser($this->user['id'])->toArray();
|
||||
$this->user['ports'] = Permissions::portsForUser($this->user['id'])->toArray();
|
||||
}
|
||||
|
||||
return $this->respond('Authenticated.');
|
||||
} else {
|
||||
return $this->respond('Nope.');
|
||||
}
|
||||
} else {
|
||||
$user_id = LegacyAuth::get()->getUserid($params[0]);
|
||||
$user = LegacyAuth::get()->getUser($user_id);
|
||||
if ($user['email'] && $user['username'] == $params[0]) {
|
||||
$user = User::firstWhere('username', $params[0]);
|
||||
if ($user->email && $user->username == $params[0]) {
|
||||
$token = hash('gost', openssl_random_pseudo_bytes(1024));
|
||||
$this->tokens[$this->getUser($this->data)] = $token;
|
||||
$this->user['name'] = $params[0];
|
||||
$this->user['id'] = $user['user_id'];
|
||||
$this->user['user'] = $user;
|
||||
if ($this->debug) {
|
||||
$this->log("Auth for '" . $params[0] . "', ID: '" . $user['user_id'] . "', Token: '" . $token . "', Mail: '" . $user['email'] . "'");
|
||||
$this->log("Auth for '" . $params[0] . "', ID: '" . $user->user_id . "', Token: '" . $token . "', Mail: '" . $user->email . "'");
|
||||
}
|
||||
|
||||
if (send_mail($user['email'], 'LibreNMS IRC-Bot Authtoken', "Your Authtoken for the IRC-Bot:\r\n\r\n" . $token . "\r\n\r\n") === true) {
|
||||
if (send_mail($user->email, 'LibreNMS IRC-Bot Authtoken', "Your Authtoken for the IRC-Bot:\r\n\r\n" . $token . "\r\n\r\n") === true) {
|
||||
return $this->respond('Token sent!');
|
||||
} else {
|
||||
return $this->respond('Sorry, seems like mail doesnt like us.');
|
||||
@ -734,7 +721,7 @@ class IRCBot
|
||||
|
||||
private function _reload($params)
|
||||
{
|
||||
if ($this->user['level'] == 10) {
|
||||
if ($this->user['user']->can('irc.reload')) {
|
||||
if ($params == 'external') {
|
||||
$this->respond('Reloading external scripts.');
|
||||
|
||||
@ -756,7 +743,7 @@ class IRCBot
|
||||
|
||||
private function _join($params)
|
||||
{
|
||||
if ($this->user['level'] == 10) {
|
||||
if ($this->user['user']->can('irc.join')) {
|
||||
return $this->joinChan($params);
|
||||
} else {
|
||||
return $this->respond('Permission denied.');
|
||||
@ -767,7 +754,7 @@ class IRCBot
|
||||
|
||||
private function _quit($params)
|
||||
{
|
||||
if ($this->user['level'] == 10) {
|
||||
if ($this->user['user']->can('irc.quit')) {
|
||||
$this->ircRaw('QUIT :Requested');
|
||||
|
||||
return exit;
|
||||
@ -812,31 +799,30 @@ class IRCBot
|
||||
if (strlen($params[1]) > 0) {
|
||||
$hostname = preg_replace("/[^A-z0-9\.\-]/", '', $params[1]);
|
||||
}
|
||||
$hostname = $hostname . '%';
|
||||
if ($this->user['level'] < 5) {
|
||||
$tmp = dbFetchRows('SELECT `event_id`, eventlog.device_id, devices.hostname, `datetime`,`message`, eventlog.type FROM `eventlog`, `devices` WHERE eventlog.device_id=devices.device_id and devices.hostname like "' . $hostname . '" and eventlog.device_id IN (' . implode(',', $this->user['devices']) . ') ORDER BY `event_id` DESC LIMIT ' . (int) $num);
|
||||
} else {
|
||||
$tmp = dbFetchRows('SELECT `event_id`, eventlog.device_id, devices.hostname, `datetime`,`message`, eventlog.type FROM `eventlog`, `devices` WHERE eventlog.device_id=devices.device_id and devices.hostname like "' . $hostname . '" ORDER BY `event_id` DESC LIMIT ' . (int) $num);
|
||||
}
|
||||
|
||||
$tmp = Eventlog::with('device')->hasAccess($this->user['user'])->whereIn('device_id', function ($query) use ($hostname) {
|
||||
return $query->where('hostname', 'like', $hostname . '%')->select('device_id');
|
||||
})->select(['event_id', 'datetime', 'type', 'message'])->orderBy('event_id')->limit((int) $num)->get();
|
||||
|
||||
/** @var Eventlog $logline */
|
||||
foreach ($tmp as $logline) {
|
||||
$response = $logline['datetime'] . ' ';
|
||||
$response .= $this->_color($logline['hostname'], null, null, 'bold') . ' ';
|
||||
$response = $logline->datetime . ' ';
|
||||
$response .= $this->_color($logline->device->displayName(), null, null, 'bold') . ' ';
|
||||
if ($this->config['irc_alert_utf8']) {
|
||||
if (preg_match('/critical alert/', $logline['message'])) {
|
||||
$response .= preg_replace('/critical alert/', $this->_color('critical alert', 'red'), $logline['message']) . ' ';
|
||||
} elseif (preg_match('/warning alert/', $logline['message'])) {
|
||||
$response .= preg_replace('/warning alert/', $this->_color('warning alert', 'yellow'), $logline['message']) . ' ';
|
||||
} elseif (preg_match('/recovery/', $logline['message'])) {
|
||||
$response .= preg_replace('/recovery/', $this->_color('recovery', 'green'), $logline['message']) . ' ';
|
||||
if (preg_match('/critical alert/', $logline->message)) {
|
||||
$response .= preg_replace('/critical alert/', $this->_color('critical alert', 'red'), $logline->message) . ' ';
|
||||
} elseif (preg_match('/warning alert/', $logline->message)) {
|
||||
$response .= preg_replace('/warning alert/', $this->_color('warning alert', 'yellow'), $logline->message) . ' ';
|
||||
} elseif (preg_match('/recovery/', $logline->message)) {
|
||||
$response .= preg_replace('/recovery/', $this->_color('recovery', 'green'), $logline->message) . ' ';
|
||||
} else {
|
||||
$response .= $logline['message'] . ' ';
|
||||
$response .= $logline->message . ' ';
|
||||
}
|
||||
} else {
|
||||
$response .= $logline['message'] . ' ';
|
||||
$response .= $logline->message . ' ';
|
||||
}
|
||||
if ($logline['type'] != 'NULL') {
|
||||
$response .= $logline['type'] . ' ';
|
||||
if ($logline->type != 'NULL') {
|
||||
$response .= $logline->type . ' ';
|
||||
}
|
||||
if ($this->config['irc_floodlimit'] > 100) {
|
||||
$this->floodcount += strlen($response);
|
||||
@ -862,23 +848,12 @@ class IRCBot
|
||||
|
||||
private function _down($params)
|
||||
{
|
||||
if ($this->user['level'] < 5) {
|
||||
$tmp = dbFetchRows('SELECT `hostname` FROM `devices` WHERE status=0 AND `device_id` IN (' . implode(',', $this->user['devices']) . ')');
|
||||
} else {
|
||||
$tmp = dbFetchRows('SELECT `hostname` FROM `devices` WHERE status=0');
|
||||
}
|
||||
$devices = Device::hasAccess($this->user['user'])->isDown()
|
||||
->select(['device_id', 'hostname', 'sysName', 'display', 'ip'])->get();
|
||||
|
||||
$msg = '';
|
||||
foreach ($tmp as $db) {
|
||||
if ($db['hostname']) {
|
||||
$msg .= ', ' . $db['hostname'];
|
||||
}
|
||||
}
|
||||
$msg = $devices->map->displayName()->implode(', ');
|
||||
|
||||
$msg = substr($msg, 2);
|
||||
$msg = $msg ? $msg : 'Nothing to show :)';
|
||||
|
||||
return $this->respond($msg);
|
||||
return $this->respond($msg ?: 'Nothing to show :)');
|
||||
}
|
||||
|
||||
//end _down()
|
||||
@ -887,20 +862,16 @@ class IRCBot
|
||||
{
|
||||
$params = explode(' ', $params);
|
||||
$hostname = $params[0];
|
||||
$device = dbFetchRow('SELECT * FROM `devices` WHERE `hostname` = ?', [$hostname]);
|
||||
$device = Device::hasAccess($this->user['user'])->firstWhere('hostname', $hostname);
|
||||
if (! $device) {
|
||||
return $this->respond('Error: Bad or Missing hostname, use .listdevices to show all devices.');
|
||||
}
|
||||
|
||||
if ($this->user['level'] < 5 && ! in_array($device['device_id'], $this->user['devices'])) {
|
||||
return $this->respond('Error: Permission denied.');
|
||||
}
|
||||
$status = $device->status ? 'Up ' . Time::formatInterval($device->uptime) : 'Down';
|
||||
$status .= $device->ignore ? '*Ignored*' : '';
|
||||
$status .= $device->disabled ? '*Disabled*' : '';
|
||||
|
||||
$status = $device['status'] ? 'Up ' . Time::formatInterval($device['uptime']) : 'Down';
|
||||
$status .= $device['ignore'] ? '*Ignored*' : '';
|
||||
$status .= $device['disabled'] ? '*Disabled*' : '';
|
||||
|
||||
return $this->respond($device['os'] . ' ' . $device['version'] . ' ' . $device['features'] . ' ' . $status);
|
||||
return $this->respond($device->displayName() . ': ' . $device->os . ' ' . $device->version . ' ' . $device->features . ' ' . $status);
|
||||
}
|
||||
|
||||
//end _device()
|
||||
@ -914,10 +885,14 @@ class IRCBot
|
||||
return $this->respond('Error: Missing hostname or ifname.');
|
||||
}
|
||||
|
||||
$device = dbFetchRow('SELECT * FROM `devices` WHERE `hostname` = ?', [$hostname]);
|
||||
$port = dbFetchRow('SELECT * FROM `ports` WHERE (`ifName` = ? OR `ifDescr` = ?) AND device_id = ?', [$ifname, $ifname, $device['device_id']]);
|
||||
if ($this->user['level'] < 5 && ! in_array($port['port_id'], $this->user['ports']) && ! in_array($device['device_id'], $this->user['devices'])) {
|
||||
return $this->respond('Error: Permission denied.');
|
||||
$device = Device::hasAccess($this->user['user'])->firstWhere('hostname', $hostname);
|
||||
if (! $device) {
|
||||
return $this->respond('Error: Bad or Missing hostname, use .listdevices to show all devices.');
|
||||
}
|
||||
|
||||
$port = $device->ports()->hasAccess($this->user['user'])->where('ifName', $ifname)->orWhere('ifDescr', $ifname);
|
||||
if (! $port) {
|
||||
return $this->respond('Error: Port not found or you do not have access.');
|
||||
}
|
||||
|
||||
$bps_in = Number::formatSi($port['ifInOctets_rate'] * 8, 2, 3, 'bps');
|
||||
@ -932,21 +907,11 @@ class IRCBot
|
||||
|
||||
private function _listdevices($params)
|
||||
{
|
||||
if ($this->user['level'] < 5) {
|
||||
$tmp = dbFetchRows('SELECT `hostname` FROM `devices` WHERE `device_id` IN (' . implode(',', $this->user['devices']) . ')');
|
||||
} else {
|
||||
$tmp = dbFetchRows('SELECT `hostname` FROM `devices`');
|
||||
}
|
||||
$devices = Device::hasAccess($this->user['user'])->pluck('hostname');
|
||||
|
||||
$msg = '';
|
||||
foreach ($tmp as $device) {
|
||||
$msg .= ', ' . $device['hostname'];
|
||||
}
|
||||
$msg = $devices->implode(', ');
|
||||
|
||||
$msg = substr($msg, 2);
|
||||
$msg = $msg ? $msg : 'Nothing to show..?';
|
||||
|
||||
return $this->respond($msg);
|
||||
return $this->respond($msg ?: 'Nothing to show..?');
|
||||
}
|
||||
|
||||
//end _listdevices()
|
||||
@ -956,26 +921,15 @@ class IRCBot
|
||||
$params = explode(' ', $params);
|
||||
$statustype = $params[0];
|
||||
|
||||
$d_w = '';
|
||||
$d_a = '';
|
||||
$p_w = '';
|
||||
$p_a = '';
|
||||
if ($this->user['level'] < 5) {
|
||||
$d_w = ' WHERE device_id IN (' . implode(',', $this->user['devices']) . ')';
|
||||
$d_a = ' AND device_id IN (' . implode(',', $this->user['devices']) . ')';
|
||||
$p_w = ' WHERE port_id IN (' . implode(',', $this->user['ports']) . ') OR device_id IN (' . implode(',', $this->user['devices']) . ')';
|
||||
$p_a = ' AND (I.port_id IN (' . implode(',', $this->user['ports']) . ') OR I.device_id IN (' . implode(',', $this->user['devices']) . '))';
|
||||
}
|
||||
|
||||
switch ($statustype) {
|
||||
case 'devices':
|
||||
case 'device':
|
||||
case 'dev':
|
||||
$devcount = dbFetchCell('SELECT count(*) FROM devices' . $d_w);
|
||||
$devup = dbFetchCell("SELECT count(*) FROM devices WHERE status = '1' AND `ignore` = '0'" . $d_a);
|
||||
$devdown = dbFetchCell("SELECT count(*) FROM devices WHERE status = '0' AND `ignore` = '0'" . $d_a);
|
||||
$devign = dbFetchCell("SELECT count(*) FROM devices WHERE `ignore` = '1'" . $d_a);
|
||||
$devdis = dbFetchCell("SELECT count(*) FROM devices WHERE `disabled` = '1'" . $d_a);
|
||||
$devcount = Device::hasAccess($this->user['user'])->count();
|
||||
$devup = Device::hasAccess($this->user['user'])->isUp()->count();
|
||||
$devdown = Device::hasAccess($this->user['user'])->isDown()->count();
|
||||
$devign = Device::hasAccess($this->user['user'])->isIgnored()->count();
|
||||
$devdis = Device::hasAccess($this->user['user'])->isDisabled()->count();
|
||||
if ($devup > 0) {
|
||||
$devup = $this->_color($devup, 'green');
|
||||
}
|
||||
@ -991,11 +945,13 @@ class IRCBot
|
||||
case 'ports':
|
||||
case 'port':
|
||||
case 'prt':
|
||||
$prtcount = dbFetchCell('SELECT count(*) FROM ports' . $p_w);
|
||||
$prtup = dbFetchCell("SELECT count(*) FROM ports AS I, devices AS D WHERE I.ifOperStatus = 'up' AND I.ignore = '0' AND I.device_id = D.device_id AND D.ignore = '0'" . $p_a);
|
||||
$prtdown = dbFetchCell("SELECT count(*) FROM ports AS I, devices AS D WHERE I.ifOperStatus = 'down' AND I.ifAdminStatus = 'up' AND I.ignore = '0' AND D.device_id = I.device_id AND D.ignore = '0'" . $p_a);
|
||||
$prtsht = dbFetchCell("SELECT count(*) FROM ports AS I, devices AS D WHERE I.ifAdminStatus = 'down' AND I.ignore = '0' AND D.device_id = I.device_id AND D.ignore = '0'" . $p_a);
|
||||
$prtign = dbFetchCell("SELECT count(*) FROM ports AS I, devices AS D WHERE D.device_id = I.device_id AND (I.ignore = '1' OR D.ignore = '1')" . $p_a);
|
||||
$prtcount = Port::hasAccess($this->user['user'])->count();
|
||||
$prtup = Port::hasAccess($this->user['user'])->isUp()->count();
|
||||
$prtdown = Port::hasAccess($this->user['user'])->isDown()->whereHas('device', fn ($q) => $q->where('ignore', 0))->count();
|
||||
$prtsht = Port::hasAccess($this->user['user'])->isShutdown()->whereHas('device', fn ($q) => $q->where('ignore', 0))->count();
|
||||
$prtign = Port::hasAccess($this->user['user'])->where(function ($query) {
|
||||
$query->isIgnored()->orWhereHas('device', fn ($q) => $q->where('ignore', 1));
|
||||
})->count();
|
||||
// $prterr = dbFetchCell("SELECT count(*) FROM ports AS I, devices AS D WHERE D.device_id = I.device_id AND (I.ignore = '0' OR D.ignore = '0') AND (I.ifInErrors_delta > '0' OR I.ifOutErrors_delta > '0')".$p_a);
|
||||
if ($prtup > 0) {
|
||||
$prtup = $this->_color($prtup, 'green');
|
||||
@ -1014,15 +970,16 @@ class IRCBot
|
||||
case 'srv':
|
||||
$status_counts = [];
|
||||
$status_colors = [0 => 'green', 3 => 'lightblue', 1 => 'yellow', 2 => 'red'];
|
||||
$srvcount = dbFetchCell('SELECT COUNT(*) FROM services' . $d_w);
|
||||
$srvign = dbFetchCell('SELECT COUNT(*) FROM services WHERE service_ignore = 1' . $d_a);
|
||||
$srvdis = dbFetchCell('SELECT COUNT(*) FROM services WHERE service_disabled = 1' . $d_a);
|
||||
$service_status = dbFetchRows("SELECT `service_status`, COUNT(*) AS `count` FROM `services` WHERE `service_disabled`=0 AND `service_ignore`=0 $d_a GROUP BY `service_status`");
|
||||
$service_status = array_column($service_status, 'count', 'service_status'); // key by status
|
||||
$srvcount = Service::hasAccess($this->user['user'])->count();
|
||||
$srvign = Service::hasAccess($this->user['user'])->isIgnored()->count();
|
||||
$srvdis = Service::hasAccess($this->user['user'])->isDisabled()->count();
|
||||
$service_status = Service::hasAccess($this->user['user'])->isActive()->groupBy('service_status')
|
||||
->select('service_status', \DB::raw('count(*) as count'))->get()
|
||||
->pluck('count', 'service_status');
|
||||
|
||||
foreach ($status_colors as $status => $color) {
|
||||
if (isset($service_status[$status])) {
|
||||
$status_counts[$status] = $this->_color($service_status[$status], $color);
|
||||
if ($service_status->has($status)) {
|
||||
$status_counts[$status] = $this->_color($service_status->get($status), $color);
|
||||
$srvcount = $this->_color($srvcount, $color, null, 'bold'); // upgrade the main count color
|
||||
} else {
|
||||
$status_counts[$status] = 0;
|
||||
|
@ -26,14 +26,6 @@ interface Authorizer
|
||||
*/
|
||||
public function userExists($username, $throw_exception = false);
|
||||
|
||||
/**
|
||||
* Get the userlevel of $username
|
||||
*
|
||||
* @param string $username The username to check
|
||||
* @return int
|
||||
*/
|
||||
public function getUserlevel($username);
|
||||
|
||||
/**
|
||||
* Get the user_id of $username
|
||||
*
|
||||
@ -51,7 +43,6 @@ interface Authorizer
|
||||
* realname
|
||||
* email
|
||||
* descr
|
||||
* level
|
||||
* can_modify_passwd
|
||||
*
|
||||
* @param int $user_id
|
||||
@ -59,48 +50,6 @@ interface Authorizer
|
||||
*/
|
||||
public function getUser($user_id);
|
||||
|
||||
/**
|
||||
* Add a new user.
|
||||
*
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @param int $level
|
||||
* @param string $email
|
||||
* @param string $realname
|
||||
* @param int $can_modify_passwd If this user is allowed to edit their password
|
||||
* @param string $description
|
||||
* @return int|false Returns the added user_id or false if adding failed
|
||||
*/
|
||||
public function addUser($username, $password, $level = 0, $email = '', $realname = '', $can_modify_passwd = 0, $description = '');
|
||||
|
||||
/**
|
||||
* Update the some of the fields of a user
|
||||
*
|
||||
* @param int $user_id The user_id to update
|
||||
* @param string $realname
|
||||
* @param int $level
|
||||
* @param int $can_modify_passwd
|
||||
* @param string $email
|
||||
* @return bool If the update was successful
|
||||
*/
|
||||
public function updateUser($user_id, $realname, $level, $can_modify_passwd, $email);
|
||||
|
||||
/**
|
||||
* Delete a user.
|
||||
*
|
||||
* @param int $user_id
|
||||
* @return bool If the deletion was successful
|
||||
*/
|
||||
public function deleteUser($user_id);
|
||||
|
||||
/**
|
||||
* Get a list of all users in this Authorizer
|
||||
* !Warning! this could be very slow for some Authorizer types or configurations
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getUserlist();
|
||||
|
||||
/**
|
||||
* Check if this Authorizer can add or remove users.
|
||||
* You must also check canUpdateUsers() to see if it can edit users.
|
||||
@ -140,4 +89,10 @@ interface Authorizer
|
||||
* @return string|null
|
||||
*/
|
||||
public function getExternalUsername();
|
||||
|
||||
/**
|
||||
* @param string $username
|
||||
* @return string[] get a list of roles for the user, they need not exist ahead of time
|
||||
*/
|
||||
public function getRoles(string $username): array;
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ use App\Models\User;
|
||||
use Illuminate\Validation\Rule;
|
||||
use LibreNMS\Authentication\LegacyAuth;
|
||||
use LibreNMS\Config;
|
||||
use Silber\Bouncer\Database\Role;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
|
||||
@ -50,7 +51,7 @@ class AddUserCommand extends LnmsCommand
|
||||
|
||||
$this->addArgument('username', InputArgument::REQUIRED);
|
||||
$this->addOption('password', 'p', InputOption::VALUE_REQUIRED);
|
||||
$this->addOption('role', 'r', InputOption::VALUE_REQUIRED, __('commands.user:add.options.role', ['roles' => '[normal, global-read, admin]']), 'normal');
|
||||
$this->addOption('role', 'r', InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, __('commands.user:add.options.role', ['roles' => '[user, global-read, admin]']), ['user']);
|
||||
$this->addOption('email', 'e', InputOption::VALUE_REQUIRED);
|
||||
$this->addOption('full-name', 'l', InputOption::VALUE_REQUIRED);
|
||||
$this->addOption('descr', 's', InputOption::VALUE_REQUIRED);
|
||||
@ -67,16 +68,12 @@ class AddUserCommand extends LnmsCommand
|
||||
$this->warn(__('commands.user:add.wrong-auth'));
|
||||
}
|
||||
|
||||
$roles = [
|
||||
'normal' => 1,
|
||||
'global-read' => 5,
|
||||
'admin' => 10,
|
||||
];
|
||||
$roles = Role::pluck('name');
|
||||
|
||||
$this->validate([
|
||||
'username' => ['required', Rule::unique('users', 'username')->where('auth_type', 'mysql')],
|
||||
'email' => 'nullable|email',
|
||||
'role' => Rule::in(array_keys($roles)),
|
||||
'role' => Rule::in($roles->keys()),
|
||||
]);
|
||||
|
||||
// set get password
|
||||
@ -87,7 +84,6 @@ class AddUserCommand extends LnmsCommand
|
||||
|
||||
$user = new User([
|
||||
'username' => $this->argument('username'),
|
||||
'level' => $roles[$this->option('role')],
|
||||
'descr' => $this->option('descr'),
|
||||
'email' => $this->option('email'),
|
||||
'realname' => $this->option('full-name'),
|
||||
@ -96,6 +92,7 @@ class AddUserCommand extends LnmsCommand
|
||||
|
||||
$user->setPassword($password);
|
||||
$user->save();
|
||||
$user->allow($this->option('role'));
|
||||
|
||||
$user->auth_id = (string) LegacyAuth::get()->getUserid($user->username) ?: $user->user_id;
|
||||
$user->save();
|
||||
|
@ -30,6 +30,7 @@ use Illuminate\Database\QueryException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use LibreNMS\Interfaces\InstallerStep;
|
||||
use Silber\Bouncer\BouncerFacade as Bouncer;
|
||||
|
||||
class MakeUserController extends InstallationController implements InstallerStep
|
||||
{
|
||||
@ -72,10 +73,12 @@ class MakeUserController extends InstallationController implements InstallerStep
|
||||
if (! $this->complete()) {
|
||||
$this->configureDatabase();
|
||||
$user = new User($request->only(['username', 'password', 'email']));
|
||||
$user->level = 10; // admin
|
||||
$user->setPassword($request->get('password'));
|
||||
$res = $user->save();
|
||||
|
||||
Bouncer::allow('admin')->everything(); // make sure admin role exists
|
||||
$user->assign('admin');
|
||||
|
||||
if ($res) {
|
||||
$message = trans('install.user.success');
|
||||
$this->markStepComplete();
|
||||
|
46
app/Http/Controllers/Select/RoleController.php
Normal file
46
app/Http/Controllers/Select/RoleController.php
Normal file
@ -0,0 +1,46 @@
|
||||
<?php
|
||||
/*
|
||||
* RolesController.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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2023 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace App\Http\Controllers\Select;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Silber\Bouncer\BouncerFacade as Bouncer;
|
||||
|
||||
class RoleController extends SelectController
|
||||
{
|
||||
protected ?string $idField = 'name';
|
||||
protected ?string $textField = 'title';
|
||||
|
||||
protected function searchFields(Request $request)
|
||||
{
|
||||
return ['name'];
|
||||
}
|
||||
|
||||
protected function baseQuery(Request $request)
|
||||
{
|
||||
return Bouncer::role()
|
||||
->whereRaw('1 = ' . ((int) $request->user()->can('viewAny', Bouncer::role())));
|
||||
}
|
||||
}
|
@ -27,13 +27,18 @@ namespace App\Http\Controllers\Select;
|
||||
|
||||
use App\Http\Controllers\PaginatedAjaxController;
|
||||
use Illuminate\Contracts\Pagination\Paginator;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Query\Builder;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
abstract class SelectController extends PaginatedAjaxController
|
||||
{
|
||||
protected ?string $idField = null;
|
||||
protected ?string $textField = null;
|
||||
|
||||
final protected function baseRules()
|
||||
{
|
||||
return [
|
||||
@ -54,9 +59,11 @@ abstract class SelectController extends PaginatedAjaxController
|
||||
$this->validate($request, $this->rules());
|
||||
$limit = $request->get('limit', 50);
|
||||
|
||||
$query = $this->baseQuery($request)->when($request->has('id'), function ($query) {
|
||||
return $query->whereKey(request('id'));
|
||||
});
|
||||
$query = $this->baseQuery($request);
|
||||
if ($this->idField && $this->textField) {
|
||||
$query->select([$this->idField, $this->textField]);
|
||||
}
|
||||
$this->filterById($query, $request->get('id'));
|
||||
$this->filter($request, $query, $this->filterFields($request));
|
||||
$this->search($request->get('term'), $query, $this->searchFields($request));
|
||||
$this->sort($request, $query);
|
||||
@ -88,6 +95,14 @@ abstract class SelectController extends PaginatedAjaxController
|
||||
*/
|
||||
public function formatItem($model)
|
||||
{
|
||||
if ($this->idField && $this->textField) {
|
||||
return [
|
||||
'id' => $model->getAttribute($this->idField),
|
||||
'text' => $model->getAttribute($this->textField),
|
||||
];
|
||||
}
|
||||
|
||||
// guess
|
||||
$attributes = collect($model->getAttributes());
|
||||
|
||||
return [
|
||||
@ -106,4 +121,21 @@ abstract class SelectController extends PaginatedAjaxController
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function filterById(EloquentBuilder|Builder $query, ?string $id): EloquentBuilder|Builder
|
||||
{
|
||||
if ($id) {
|
||||
// multiple
|
||||
if (str_contains($id, ',')) {
|
||||
$keys = explode(',', $id);
|
||||
|
||||
return $this->idField ? $query->whereIn($this->idField, $keys) : $query->whereKey($keys);
|
||||
}
|
||||
|
||||
// use id field if given
|
||||
return $this->idField ? $query->where($this->idField, $id) : $query->whereKey($id);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
}
|
||||
|
@ -74,7 +74,7 @@ class UserController extends Controller
|
||||
$this->authorize('create', User::class);
|
||||
|
||||
$tmp_user = new User;
|
||||
$tmp_user->can_modify_passwd = (int) LegacyAuth::get()->canUpdatePasswords(); // default to true for new users
|
||||
$tmp_user->can_modify_passwd = LegacyAuth::getType() == 'mysql' ? 1 : 0; // default to true mysql
|
||||
|
||||
return view('user.create', [
|
||||
'user' => $tmp_user,
|
||||
@ -92,13 +92,14 @@ class UserController extends Controller
|
||||
*/
|
||||
public function store(StoreUserRequest $request, FlasherInterface $flasher)
|
||||
{
|
||||
$user = $request->only(['username', 'realname', 'email', 'descr', 'level', 'can_modify_passwd']);
|
||||
$user = $request->only(['username', 'realname', 'email', 'descr', 'can_modify_passwd']);
|
||||
$user['auth_type'] = LegacyAuth::getType();
|
||||
$user['can_modify_passwd'] = $request->get('can_modify_passwd'); // checkboxes are missing when unchecked
|
||||
|
||||
$user = User::create($user);
|
||||
|
||||
$user->setPassword($request->new_password);
|
||||
$user->setRoles($request->get('roles', []));
|
||||
$user->auth_id = (string) LegacyAuth::get()->getUserid($user->username) ?: $user->user_id;
|
||||
$this->updateDashboard($user, $request->get('dashboard'));
|
||||
$this->updateTimezone($user, $request->get('timezone'));
|
||||
@ -184,6 +185,7 @@ class UserController extends Controller
|
||||
}
|
||||
|
||||
$user->fill($request->validated());
|
||||
$user->setRoles($request->get('roles', []));
|
||||
|
||||
if ($request->has('dashboard') && $this->updateDashboard($user, $request->get('dashboard'))) {
|
||||
$flasher->addSuccess(__('Updated dashboard for :username', ['username' => $user->username]));
|
||||
|
@ -7,6 +7,7 @@ use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use LibreNMS\Authentication\LegacyAuth;
|
||||
use LibreNMS\Config;
|
||||
use Silber\Bouncer\BouncerFacade as Bouncer;
|
||||
|
||||
class StoreUserRequest extends FormRequest
|
||||
{
|
||||
@ -17,7 +18,15 @@ class StoreUserRequest extends FormRequest
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
return $this->user()->can('create', User::class);
|
||||
if ($this->user()->can('create', User::class)) {
|
||||
if ($this->user()->cannot('manage', Bouncer::role())) {
|
||||
unset($this['roles']);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -37,7 +46,8 @@ class StoreUserRequest extends FormRequest
|
||||
'realname' => 'nullable|max:64|alpha_space',
|
||||
'email' => 'nullable|email|max:64',
|
||||
'descr' => 'nullable|max:30|alpha_space',
|
||||
'level' => 'int',
|
||||
'roles' => 'array',
|
||||
'roles.*' => Rule::in(Bouncer::role()->pluck('name')),
|
||||
'new_password' => 'required|confirmed|min:' . Config::get('password.min_length', 8),
|
||||
'dashboard' => 'int',
|
||||
];
|
||||
|
@ -2,9 +2,12 @@
|
||||
|
||||
namespace App\Http\Requests;
|
||||
|
||||
use App\Models\User;
|
||||
use Hash;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Validation\Rule;
|
||||
use LibreNMS\Config;
|
||||
use Silber\Bouncer\BouncerFacade as Bouncer;
|
||||
|
||||
class UpdateUserRequest extends FormRequest
|
||||
{
|
||||
@ -15,14 +18,17 @@ class UpdateUserRequest extends FormRequest
|
||||
*/
|
||||
public function authorize(): bool
|
||||
{
|
||||
if ($this->user()->isAdmin()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** @var User|null $user */
|
||||
$user = $this->route('user');
|
||||
if ($user && $this->user()->can('update', $user)) {
|
||||
// normal users cannot edit their level or ability to modify a password
|
||||
unset($this['level'], $this['can_modify_passwd']);
|
||||
// normal users cannot update their roles or ability to modify a password
|
||||
if ($this->user()->cannot('manage', Bouncer::role())) {
|
||||
unset($this['roles']);
|
||||
}
|
||||
|
||||
if ($user->is($this->user())) {
|
||||
unset($this['can_modify_passwd']);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@ -37,7 +43,7 @@ class UpdateUserRequest extends FormRequest
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
if ($this->user()->isAdmin()) {
|
||||
if ($this->user()->can('update', User::class)) {
|
||||
return [
|
||||
'realname' => 'nullable|max:64|alpha_space',
|
||||
'email' => 'nullable|email|max:64',
|
||||
@ -45,7 +51,8 @@ class UpdateUserRequest extends FormRequest
|
||||
'new_password' => 'nullable|confirmed|min:' . Config::get('password.min_length', 8),
|
||||
'new_password_confirmation' => 'nullable|same:new_password',
|
||||
'dashboard' => 'int',
|
||||
'level' => 'int',
|
||||
'roles' => 'array',
|
||||
'roles.*' => Rule::in(Bouncer::role()->pluck('name')),
|
||||
'enabled' => 'nullable',
|
||||
'can_modify_passwd' => 'nullable',
|
||||
];
|
||||
@ -72,7 +79,8 @@ class UpdateUserRequest extends FormRequest
|
||||
{
|
||||
$validator->after(function ($validator) {
|
||||
// if not an admin and new_password is set, check old password matches
|
||||
if (! $this->user()->isAdmin()) {
|
||||
$user = $this->route('user');
|
||||
if ($user && $this->user()->can('update', $user) && $this->user()->is($user)) {
|
||||
if ($this->has('new_password')) {
|
||||
if ($this->has('old_password')) {
|
||||
$user = $this->route('user');
|
||||
|
@ -27,6 +27,18 @@ class Service extends DeviceRelatedModel
|
||||
|
||||
// ---- Query Scopes ----
|
||||
|
||||
/**
|
||||
* @param Builder $query
|
||||
* @return Builder
|
||||
*/
|
||||
public function scopeIsActive($query)
|
||||
{
|
||||
return $query->where([
|
||||
['service_ignore', '=', 0],
|
||||
['service_disabled', '=', 0],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Builder $query
|
||||
* @return Builder
|
||||
|
@ -13,16 +13,18 @@ use Illuminate\Support\Facades\Hash;
|
||||
use LibreNMS\Authentication\LegacyAuth;
|
||||
use NotificationChannels\WebPush\HasPushSubscriptions;
|
||||
use Permissions;
|
||||
use Silber\Bouncer\BouncerFacade as Bouncer;
|
||||
use Silber\Bouncer\Database\HasRolesAndAbilities;
|
||||
|
||||
/**
|
||||
* @method static \Database\Factories\UserFactory factory(...$parameters)
|
||||
*/
|
||||
class User extends Authenticatable
|
||||
{
|
||||
use Notifiable, HasFactory, HasPushSubscriptions;
|
||||
use HasRolesAndAbilities, Notifiable, HasFactory, HasPushSubscriptions;
|
||||
|
||||
protected $primaryKey = 'user_id';
|
||||
protected $fillable = ['realname', 'username', 'email', 'level', 'descr', 'can_modify_passwd', 'auth_type', 'auth_id', 'enabled'];
|
||||
protected $fillable = ['realname', 'username', 'email', 'descr', 'can_modify_passwd', 'auth_type', 'auth_id', 'enabled'];
|
||||
protected $hidden = ['password', 'remember_token', 'pivot'];
|
||||
protected $attributes = [ // default values
|
||||
'descr' => '',
|
||||
@ -42,31 +44,29 @@ class User extends Authenticatable
|
||||
|
||||
public function toFlare(): array
|
||||
{
|
||||
return $this->only(['level', 'auth_type', 'enabled']);
|
||||
return $this->only(['auth_type', 'enabled']);
|
||||
}
|
||||
|
||||
// ---- Helper Functions ----
|
||||
|
||||
/**
|
||||
* Test if this user has global read access
|
||||
* these users have a level of 5, 10 or 11 (demo).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasGlobalRead()
|
||||
{
|
||||
return $this->hasGlobalAdmin() || $this->level == 5;
|
||||
return $this->isA('admin', 'global-read');
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if this user has global admin access
|
||||
* these users have a level of 10 or 11 (demo).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasGlobalAdmin()
|
||||
{
|
||||
return $this->level >= 10;
|
||||
return $this->isA('admin', 'demo');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,7 +76,7 @@ class User extends Authenticatable
|
||||
*/
|
||||
public function isAdmin()
|
||||
{
|
||||
return $this->level == 10;
|
||||
return $this->isA('admin');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -86,7 +86,7 @@ class User extends Authenticatable
|
||||
*/
|
||||
public function isDemo()
|
||||
{
|
||||
return $this->level == 11;
|
||||
return $this->isA('demo');
|
||||
}
|
||||
|
||||
/**
|
||||
@ -110,6 +110,20 @@ class User extends Authenticatable
|
||||
$this->attributes['password'] = $password ? Hash::make($password) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set roles and remove extra roles, optionally creating non-existent roles, flush permissions cache for this user if roles changed
|
||||
*/
|
||||
public function setRoles(array $roles, bool $create = false): void
|
||||
{
|
||||
if ($roles != $this->getRoles()) {
|
||||
if ($create) {
|
||||
$this->assign($roles);
|
||||
}
|
||||
Bouncer::sync($this)->roles($roles);
|
||||
Bouncer::refresh($this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given user can set the password for this user
|
||||
*
|
||||
@ -167,7 +181,7 @@ class User extends Authenticatable
|
||||
|
||||
public function scopeAdminOnly($query)
|
||||
{
|
||||
$query->where('level', 10);
|
||||
$query->whereIs('admin');
|
||||
}
|
||||
|
||||
// ---- Accessors/Mutators ----
|
||||
|
@ -9,35 +9,15 @@ class UserPolicy
|
||||
{
|
||||
use HandlesAuthorization;
|
||||
|
||||
/**
|
||||
* Determine whether the user can manage users.
|
||||
*
|
||||
* @param User $user
|
||||
*/
|
||||
public function manage(User $user): bool
|
||||
{
|
||||
return $user->isAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view the user.
|
||||
*
|
||||
* @param User $user
|
||||
* @param User $target
|
||||
*/
|
||||
public function view(User $user, User $target): bool
|
||||
public function view(User $user, User $target): ?bool
|
||||
{
|
||||
return $user->isAdmin() || $target->is($user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the user can view any user.
|
||||
*
|
||||
* @param User $user
|
||||
*/
|
||||
public function viewAny(User $user): bool
|
||||
{
|
||||
return $user->isAdmin();
|
||||
return $target->is($user) ?: null; // allow users to view themselves
|
||||
}
|
||||
|
||||
/**
|
||||
@ -45,9 +25,14 @@ class UserPolicy
|
||||
*
|
||||
* @param User $user
|
||||
*/
|
||||
public function create(User $user): bool
|
||||
public function create(User $user): ?bool
|
||||
{
|
||||
return $user->isAdmin();
|
||||
// if not mysql, forbid, otherwise defer to bouncer
|
||||
if (\LibreNMS\Config::get('auth_mechanism') != 'mysql') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -56,9 +41,13 @@ class UserPolicy
|
||||
* @param User $user
|
||||
* @param User $target
|
||||
*/
|
||||
public function update(User $user, User $target): bool
|
||||
public function update(User $user, User $target = null): ?bool
|
||||
{
|
||||
return $user->isAdmin() || $target->is($user);
|
||||
if ($target == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $target->is($user) ?: null; // allow user to update self or defer to bouncer
|
||||
}
|
||||
|
||||
/**
|
||||
@ -67,8 +56,8 @@ class UserPolicy
|
||||
* @param User $user
|
||||
* @param User $target
|
||||
*/
|
||||
public function delete(User $user, User $target): bool
|
||||
public function delete(User $user, User $target): ?bool
|
||||
{
|
||||
return $user->isAdmin();
|
||||
return $target->is($user) ? false : null; // do not allow users to delete themselves or defer to bouncer
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use App\Guards\ApiTokenGuard;
|
||||
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Gate;
|
||||
use Silber\Bouncer\BouncerFacade as Bouncer;
|
||||
|
||||
class AuthServiceProvider extends ServiceProvider
|
||||
{
|
||||
@ -31,6 +32,8 @@ class AuthServiceProvider extends ServiceProvider
|
||||
*/
|
||||
public function boot(): void
|
||||
{
|
||||
Bouncer::cache();
|
||||
|
||||
Auth::provider('legacy', function ($app, array $config) {
|
||||
return new LegacyUserProvider();
|
||||
});
|
||||
|
@ -208,6 +208,9 @@ class LegacyUserProvider implements UserProvider
|
||||
$user->auth_id = (string) $auth_id;
|
||||
$user->save();
|
||||
|
||||
// create and update roles
|
||||
$user->setRoles($auth->getRoles($user->username), true);
|
||||
|
||||
return $user;
|
||||
}
|
||||
}
|
||||
|
@ -48,6 +48,7 @@
|
||||
"php-flasher/flasher-laravel": "^1.12",
|
||||
"phpmailer/phpmailer": "~6.0",
|
||||
"predis/predis": "^2.0",
|
||||
"silber/bouncer": "^1.0",
|
||||
"socialiteproviders/manager": "^4.3",
|
||||
"spatie/laravel-ignition": "^2.0",
|
||||
"symfony/yaml": "^6.2",
|
||||
|
77
composer.lock
generated
77
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "ce02f0a79191fafde1a88553b37d0b4e",
|
||||
"content-hash": "21dbcfec63eafb1ae9172473314a57f8",
|
||||
"packages": [
|
||||
{
|
||||
"name": "amenadiel/jpgraph",
|
||||
@ -5169,6 +5169,81 @@
|
||||
],
|
||||
"time": "2023-04-15T23:01:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "silber/bouncer",
|
||||
"version": "v1.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/JosephSilber/bouncer.git",
|
||||
"reference": "502221b6724fe806aa01ffe08070edaa10222101"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/JosephSilber/bouncer/zipball/502221b6724fe806aa01ffe08070edaa10222101",
|
||||
"reference": "502221b6724fe806aa01ffe08070edaa10222101",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/auth": "^6.0|^7.0|^8.0|^9.0|^10.0",
|
||||
"illuminate/cache": "^6.0|^7.0|^8.0|^9.0|^10.0",
|
||||
"illuminate/container": "^6.0|^7.0|^8.0|^9.0|^10.0",
|
||||
"illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0",
|
||||
"illuminate/database": "^6.0|^7.0|^8.0|^9.0|^10.0",
|
||||
"php": "^7.2|^8.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0",
|
||||
"illuminate/events": "^6.0|^7.0|^8.0|^9.0|^10.0",
|
||||
"larapack/dd": "^1.1",
|
||||
"mockery/mockery": "^1.3.3",
|
||||
"phpunit/phpunit": "^8.0|^9.0"
|
||||
},
|
||||
"suggest": {
|
||||
"illuminate/console": "Allows running the bouncer:clean artisan command",
|
||||
"illuminate/events": "Required for multi-tenancy support"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Silber\\Bouncer\\BouncerServiceProvider"
|
||||
],
|
||||
"aliases": {
|
||||
"Bouncer": "Silber\\Bouncer\\BouncerFacade"
|
||||
}
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Silber\\Bouncer\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Joseph Silber",
|
||||
"email": "contact@josephsilber.com"
|
||||
}
|
||||
],
|
||||
"description": "Eloquent roles and abilities.",
|
||||
"keywords": [
|
||||
"abilities",
|
||||
"acl",
|
||||
"capabilities",
|
||||
"eloquent",
|
||||
"laravel",
|
||||
"permissions",
|
||||
"roles"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/JosephSilber/bouncer/issues",
|
||||
"source": "https://github.com/JosephSilber/bouncer/tree/v1.0.1"
|
||||
},
|
||||
"time": "2023-02-10T16:47:25+00:00"
|
||||
},
|
||||
{
|
||||
"name": "socialiteproviders/manager",
|
||||
"version": "v4.3.0",
|
||||
|
42
database/factories/RoleFactory.php
Normal file
42
database/factories/RoleFactory.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/*
|
||||
* RoleFactory.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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* @package LibreNMS
|
||||
* @link http://librenms.org
|
||||
* @copyright 2023 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Silber\Bouncer\Database\Role;
|
||||
|
||||
class RoleFactory extends Factory
|
||||
{
|
||||
protected $model = Role::class;
|
||||
|
||||
public function definition()
|
||||
{
|
||||
return [
|
||||
'name' => $this->faker->text(),
|
||||
'title' => $this->faker->text(),
|
||||
];
|
||||
}
|
||||
}
|
@ -2,10 +2,10 @@
|
||||
|
||||
namespace Database\Factories;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||
use Silber\Bouncer\BouncerFacade as Bouncer;
|
||||
|
||||
/** @extends Factory<User> */
|
||||
/** @extends Factory<\App\Models\User> */
|
||||
class UserFactory extends Factory
|
||||
{
|
||||
/**
|
||||
@ -21,25 +21,23 @@ class UserFactory extends Factory
|
||||
'realname' => $this->faker->name(),
|
||||
'email' => $this->faker->safeEmail(),
|
||||
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
|
||||
'level' => 1,
|
||||
];
|
||||
}
|
||||
|
||||
public function admin()
|
||||
{
|
||||
return $this->state(function () {
|
||||
return [
|
||||
'level' => '10',
|
||||
];
|
||||
return $this->afterCreating(function ($user) {
|
||||
Bouncer::allow('admin')->everything();
|
||||
$user->assign('admin');
|
||||
});
|
||||
}
|
||||
|
||||
public function read()
|
||||
{
|
||||
return $this->state(function () {
|
||||
return [
|
||||
'level' => '5',
|
||||
];
|
||||
return $this->afterCreating(function ($user) {
|
||||
Bouncer::allow(Bouncer::role()->firstOrCreate(['name' => 'global-read'], ['title' => 'Global Read']))
|
||||
->to('viewAny', '*', []);
|
||||
$user->assign('global-read');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
100
database/migrations/2023_06_18_195618_create_bouncer_tables.php
Normal file
100
database/migrations/2023_06_18_195618_create_bouncer_tables.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Silber\Bouncer\Database\Models;
|
||||
|
||||
class CreateBouncerTables extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
if (! Schema::hasTable('abilities')) {
|
||||
Schema::create(Models::table('abilities'), function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->string('name');
|
||||
$table->string('title')->nullable();
|
||||
$table->bigInteger('entity_id')->unsigned()->nullable();
|
||||
$table->string('entity_type')->nullable();
|
||||
$table->boolean('only_owned')->default(false);
|
||||
$table->longText('options')->nullable();
|
||||
$table->integer('scope')->nullable()->index();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
if (! Schema::hasTable('roles')) {
|
||||
Schema::create(Models::table('roles'), function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->string('name');
|
||||
$table->string('title')->nullable();
|
||||
$table->integer('scope')->nullable()->index();
|
||||
$table->timestamps();
|
||||
|
||||
$table->unique(
|
||||
['name', 'scope'],
|
||||
'roles_name_unique'
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (! Schema::hasTable('assigned_roles')) {
|
||||
Schema::create(Models::table('assigned_roles'), function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->bigInteger('role_id')->unsigned()->index();
|
||||
$table->bigInteger('entity_id')->unsigned();
|
||||
$table->string('entity_type');
|
||||
$table->bigInteger('restricted_to_id')->unsigned()->nullable();
|
||||
$table->string('restricted_to_type')->nullable();
|
||||
$table->integer('scope')->nullable()->index();
|
||||
|
||||
$table->index(
|
||||
['entity_id', 'entity_type', 'scope'],
|
||||
'assigned_roles_entity_index'
|
||||
);
|
||||
|
||||
$table->foreign('role_id')
|
||||
->references('id')->on(Models::table('roles'))
|
||||
->onUpdate('cascade')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
|
||||
if (! Schema::hasTable('permissions')) {
|
||||
Schema::create(Models::table('permissions'), function (Blueprint $table) {
|
||||
$table->bigIncrements('id');
|
||||
$table->bigInteger('ability_id')->unsigned()->index();
|
||||
$table->bigInteger('entity_id')->unsigned()->nullable();
|
||||
$table->string('entity_type')->nullable();
|
||||
$table->boolean('forbidden')->default(false);
|
||||
$table->integer('scope')->nullable()->index();
|
||||
|
||||
$table->index(
|
||||
['entity_id', 'entity_type', 'scope'],
|
||||
'permissions_entity_index'
|
||||
);
|
||||
|
||||
$table->foreign('ability_id')
|
||||
->references('id')->on(Models::table('abilities'))
|
||||
->onUpdate('cascade')->onDelete('cascade');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::drop(Models::table('permissions'));
|
||||
Schema::drop(Models::table('assigned_roles'));
|
||||
Schema::drop(Models::table('roles'));
|
||||
Schema::drop(Models::table('abilities'));
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Silber\Bouncer\BouncerFacade as Bouncer;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
User::all()->each(function (User $user) {
|
||||
$role = match ($user->getAttribute('level')) {
|
||||
1 => 'user',
|
||||
5 => 'global-read',
|
||||
10 => 'admin',
|
||||
default => null,
|
||||
};
|
||||
|
||||
if ($role) {
|
||||
Bouncer::assign($role)->to($user);
|
||||
}
|
||||
});
|
||||
|
||||
Bouncer::refresh(); // clear cache
|
||||
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('level');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
if (! Schema::hasColumn('users', 'level')) {
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->tinyInteger('level')->default(0)->after('descr');
|
||||
});
|
||||
}
|
||||
|
||||
User::whereIs('admin', 'global-read', 'user')->get()->each(function (User $user) {
|
||||
$user->setAttribute('level', $this->getLevel($user));
|
||||
$user->save();
|
||||
});
|
||||
|
||||
Bouncer::refresh();
|
||||
}
|
||||
|
||||
private function getLevel(User $user): int
|
||||
{
|
||||
if ($user->isA('admin')) {
|
||||
return 10;
|
||||
}
|
||||
|
||||
if ($user->isA('global-read')) {
|
||||
return 7;
|
||||
}
|
||||
|
||||
if ($user->isA('user')) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
41
database/seeders/RolesSeeder.php
Normal file
41
database/seeders/RolesSeeder.php
Normal file
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* RolesSeeder.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
|
||||
*
|
||||
* @copyright 2023 Tony Murray
|
||||
* @author Tony Murray <murraytony@gmail.com>
|
||||
*/
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use Illuminate\Database\Seeder;
|
||||
use Silber\Bouncer\BouncerFacade as Bouncer;
|
||||
|
||||
class RolesSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
// set abilities for default rules
|
||||
Bouncer::allow('admin')->everything();
|
||||
Bouncer::allow(Bouncer::role()->firstOrCreate(['name' => 'global-read'], ['title' => 'Global Read']))
|
||||
->to('viewAny', '*', []);
|
||||
Bouncer::role()->firstOrCreate(['name' => 'user'], ['title' => 'User']);
|
||||
}
|
||||
}
|
@ -291,23 +291,23 @@ setsebool -P httpd_can_connect_ldap 1
|
||||
## Radius Authentication
|
||||
|
||||
Please note that a mysql user is created for each user the logs in
|
||||
successfully. User level 1 is assigned by default to those accounts
|
||||
unless radius sends a reply attribute with the correct userlevel.
|
||||
successfully. Users are assigned the `user` role by default,
|
||||
unless radius sends a reply attribute with a role.
|
||||
|
||||
You can change the default userlevel by setting
|
||||
`radius.userlevel` to something other than 1.
|
||||
You can change the default role(s) by setting
|
||||
!!! setting "auth/radius"
|
||||
```bash
|
||||
lnms config:set radius.default_roles '["csr"]'
|
||||
```
|
||||
|
||||
The attribute `Filter-ID` is a standard Radius-Reply-Attribute (string) that
|
||||
can be assigned a value which translates into a userlevel in LibreNMS.
|
||||
can be assigned a specially formatted string to assign a single role to the user.
|
||||
|
||||
The strings to send in `Filter-ID` reply attribute is *one* of the following:
|
||||
|
||||
- `librenms_role_normal` - Sets the value `1`, which is the normal user level.
|
||||
- `librenms_role_admin` - Sets the value `5`, which is the administrator level.
|
||||
- `librenms_role_global-read` - Sets the value `10`, which is the global read level.
|
||||
The string to send in `Filter-ID` reply attribute must start with `librenms_role_` followed by the role name.
|
||||
For example to set the admin role send `librenms_role_admin`
|
||||
|
||||
LibreNMS will ignore any other strings sent in `Filter-ID` and revert to default
|
||||
userlevel that is set in your config.
|
||||
role that is set in your config.
|
||||
|
||||
```php
|
||||
$config['radius']['hostname'] = 'localhost';
|
||||
@ -408,9 +408,9 @@ $config['auth_mechanism'] = 'ldap-authorization';
|
||||
$config['auth_ldap_server'] = 'ldap.example.com'; // Set server(s), space separated. Prefix with ldaps:// for ssl
|
||||
$config['auth_ldap_suffix'] = ',ou=People,dc=example,dc=com'; // appended to usernames
|
||||
$config['auth_ldap_groupbase'] = 'ou=groups,dc=example,dc=com'; // all groups must be inside this
|
||||
$config['auth_ldap_groups']['admin']['level'] = 10; // set admin group to admin level
|
||||
$config['auth_ldap_groups']['pfy']['level'] = 5; // set pfy group to global read only level
|
||||
$config['auth_ldap_groups']['support']['level'] = 1; // set support group as a normal user
|
||||
$config['auth_ldap_groups']['admin']['roles'] = ['admin']; // set admin group to admin role
|
||||
$config['auth_ldap_groups']['pfy']['roles'] = ['global-read']; // set pfy group to global read only role
|
||||
$config['auth_ldap_groups']['support']['roles'] = ['user']; // set support group as a normal user
|
||||
```
|
||||
|
||||
#### Additional options (usually not needed)
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,11 +1,11 @@
|
||||
{
|
||||
"/js/app.js": "/js/app.js?id=5ddec7f7302f146a8dcc",
|
||||
"/js/app.js": "/js/app.js?id=8f48674c187c7d68c740",
|
||||
"/js/manifest.js": "/js/manifest.js?id=2951ae529be231f05a93",
|
||||
"/css/vendor.css": "/css/vendor.css?id=2568831af31dbfc3128a",
|
||||
"/css/app.css": "/css/app.css?id=1cd88608bf4eaee000d8",
|
||||
"/js/vendor.js": "/js/vendor.js?id=c5fd3d75a63757080dbb",
|
||||
"/css/app.css": "/css/app.css?id=ddaa1664b2b7b9dc3293",
|
||||
"/js/vendor.js": "/js/vendor.js?id=9a257386f44b3d7f29bc",
|
||||
"/js/lang/de.js": "/js/lang/de.js?id=d74df23e729c5dabfee8",
|
||||
"/js/lang/en.js": "/js/lang/en.js?id=20e52084af3a0a8f4724",
|
||||
"/js/lang/en.js": "/js/lang/en.js?id=4ea5e56f2ff6fbab1244",
|
||||
"/js/lang/fr.js": "/js/lang/fr.js?id=22902d30358443ef2877",
|
||||
"/js/lang/it.js": "/js/lang/it.js?id=6220e138068a7e58387f",
|
||||
"/js/lang/ru.js": "/js/lang/ru.js?id=f6b7c078755312a0907c",
|
||||
|
@ -1,66 +0,0 @@
|
||||
<?php
|
||||
|
||||
use LibreNMS\Authentication\LegacyAuth;
|
||||
|
||||
echo '<div style="margin: 10px;">';
|
||||
|
||||
if (! Auth::user()->isAdmin()) {
|
||||
include 'includes/html/error-no-perm.inc.php';
|
||||
} else {
|
||||
echo '<h3>Delete User</h3>';
|
||||
|
||||
$pagetitle[] = 'Delete user';
|
||||
|
||||
if (LegacyAuth::get()->canManageUsers()) {
|
||||
if ($vars['action'] == 'del') {
|
||||
$id = (int) $vars['id'];
|
||||
$user = LegacyAuth::get()->getUser($id);
|
||||
|
||||
if ($vars['confirm'] == 'yes') {
|
||||
if (LegacyAuth::get()->deleteUser($id) >= 0) {
|
||||
print_message('<div class="infobox">User "' . $user['username'] . '" deleted!');
|
||||
} else {
|
||||
print_error('Error deleting user "' . $user['username'] . '"!');
|
||||
}
|
||||
} else {
|
||||
print_error('You have requested deletion of the user "' . $user['username'] . '". This action can not be reversed.<br /><a class="btn btn-danger" href="deluser/action=del/id=' . $id . '/confirm=yes">Click to confirm</a>');
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME v mysql query should be replaced by authmodule
|
||||
$userlist = LegacyAuth::get()->getUserlist();
|
||||
|
||||
echo '
|
||||
<form role="form" class="form-horizontal" method="GET" action="">
|
||||
' . csrf_field() . '
|
||||
<input type="hidden" name="action" value="del">
|
||||
<div class="form-group">
|
||||
<label for="user_id" class="col-sm-2 control-label">Select User: </label>
|
||||
<div class="col-sm-6">
|
||||
<select id="user_id" name="id" class="form-control input-sm">
|
||||
';
|
||||
|
||||
foreach ($userlist as $userentry) {
|
||||
$i++;
|
||||
echo '<option value="' . $userentry['user_id'] . '">' . $userentry['username'] . '</option>';
|
||||
}
|
||||
|
||||
echo '
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-2">
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<button class="btn btn-danger btn-sm">Delete User</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
';
|
||||
} else {
|
||||
print_error('Authentication module does not allow user management!');
|
||||
}//end if
|
||||
}//end if
|
||||
|
||||
echo '</div>';
|
@ -81,29 +81,33 @@ if (! $auth) {
|
||||
|
||||
print_optionbar_end();
|
||||
|
||||
$thumb_array = Config::get('graphs.row.normal');
|
||||
$show_command = isset($vars['showcommand']) && $vars['showcommand'] == 'yes';
|
||||
if (! $show_command) {
|
||||
$thumb_array = Config::get('graphs.row.normal');
|
||||
|
||||
echo '<table width=100% class="thumbnail_graph_table"><tr>';
|
||||
echo '<table width=100% class="thumbnail_graph_table"><tr>';
|
||||
|
||||
foreach ($thumb_array as $period => $text) {
|
||||
$graph_array['from'] = Config::get("time.$period");
|
||||
foreach ($thumb_array as $period => $text) {
|
||||
$graph_array['from'] = Config::get("time.$period");
|
||||
|
||||
$link_array = $vars;
|
||||
$link_array['from'] = $graph_array['from'];
|
||||
$link_array['to'] = $graph_array['to'];
|
||||
$link_array['page'] = 'graphs';
|
||||
$link = \LibreNMS\Util\Url::generate($link_array);
|
||||
$link_array = $vars;
|
||||
$link_array['from'] = $graph_array['from'];
|
||||
$link_array['to'] = $graph_array['to'];
|
||||
$link_array['page'] = 'graphs';
|
||||
$link = \LibreNMS\Util\Url::generate($link_array);
|
||||
|
||||
echo '<td style="text-align: center;">';
|
||||
echo '<b>' . $text . '</b>';
|
||||
echo '<a href="' . $link . '">';
|
||||
echo \LibreNMS\Util\Url::lazyGraphTag($graph_array);
|
||||
echo '</a>';
|
||||
echo '</td>';
|
||||
echo '<td style="text-align: center;">';
|
||||
echo '<b>' . $text . '</b>';
|
||||
echo '<a href="' . $link . '">';
|
||||
echo \LibreNMS\Util\Url::lazyGraphTag($graph_array);
|
||||
echo '</a>';
|
||||
echo '</td>';
|
||||
}
|
||||
|
||||
echo '</tr></table>';
|
||||
echo '<hr />';
|
||||
}
|
||||
|
||||
echo '</tr></table>';
|
||||
|
||||
$graph_array = $vars;
|
||||
$graph_array['height'] = Config::get('webui.min_graph_height');
|
||||
$graph_array['width'] = $graph_width;
|
||||
@ -124,8 +128,6 @@ if (! $auth) {
|
||||
}
|
||||
}
|
||||
|
||||
echo '<hr />';
|
||||
|
||||
include_once 'includes/html/print-date-selector.inc.php';
|
||||
|
||||
echo '<div style="padding-top: 5px";></div>';
|
||||
@ -148,7 +150,7 @@ if (! $auth) {
|
||||
// }
|
||||
|
||||
echo ' | ';
|
||||
if (isset($vars['showcommand']) && $vars['showcommand'] == 'yes') {
|
||||
if ($show_command) {
|
||||
echo generate_link('Hide RRD Command', $vars, ['page' => 'graphs', 'showcommand' => null]);
|
||||
} else {
|
||||
echo generate_link('Show RRD Command', $vars, ['page' => 'graphs', 'showcommand' => 'yes']);
|
||||
@ -188,7 +190,7 @@ if (! $auth) {
|
||||
print_optionbar_end();
|
||||
}
|
||||
|
||||
if (! empty($vars['showcommand'])) {
|
||||
if ($show_command) {
|
||||
$vars = $graph_array;
|
||||
$_GET = $graph_array;
|
||||
$command_only = 1;
|
||||
|
@ -30,6 +30,7 @@ return [
|
||||
'general' => ['name' => 'General Authentication Settings'],
|
||||
'ad' => ['name' => 'Active Directory Settings'],
|
||||
'ldap' => ['name' => 'LDAP Settings'],
|
||||
'radius' => ['name' => 'Radius Settings'],
|
||||
'socialite' => ['name' => 'Socialite Settings'],
|
||||
],
|
||||
'authorization' => [
|
||||
@ -1259,6 +1260,12 @@ return [
|
||||
'help' => 'Networks/IPs which will not be discovered automatically. Excludes also IPs from Autodiscovery Networks',
|
||||
],
|
||||
],
|
||||
'radius' => [
|
||||
'default_roles' => [
|
||||
'description' => 'Default user roles',
|
||||
'help' => 'Sets the roles that will be assigned to the user unless Radius sends attributes that specify role(s)',
|
||||
],
|
||||
],
|
||||
'reporting' => [
|
||||
'error' => [
|
||||
'description' => 'Send Error Reports',
|
||||
|
@ -434,10 +434,15 @@
|
||||
"group": "auth",
|
||||
"section": "ad",
|
||||
"order": 4,
|
||||
"type": "ldap-groups",
|
||||
"type": "group-role-map",
|
||||
"options": {
|
||||
"groupPlaceholder": "Group Name"
|
||||
},
|
||||
"validate": {
|
||||
"value": "array",
|
||||
"value.*": "array"
|
||||
"value.*": "array",
|
||||
"value.*.roles": "array|required",
|
||||
"value.*.roles.*": "string"
|
||||
}
|
||||
},
|
||||
"auth_ad_user_filter": {
|
||||
@ -574,10 +579,15 @@
|
||||
"group": "auth",
|
||||
"section": "ldap",
|
||||
"order": 4,
|
||||
"type": "ldap-groups",
|
||||
"type": "group-role-map",
|
||||
"options": {
|
||||
"groupPlaceholder": "Group DN"
|
||||
},
|
||||
"validate": {
|
||||
"value": "array",
|
||||
"value.*": "array"
|
||||
"value.*": "array",
|
||||
"value.*.roles": "array|required",
|
||||
"value.*.roles.*": "string"
|
||||
}
|
||||
},
|
||||
"auth_ldap_port": {
|
||||
@ -5094,6 +5104,13 @@
|
||||
"order": 7,
|
||||
"type": "boolean"
|
||||
},
|
||||
"radius.default_roles": {
|
||||
"default": [],
|
||||
"group": "auth",
|
||||
"section": "radius",
|
||||
"order": 3,
|
||||
"type": "array"
|
||||
},
|
||||
"rancid_configs": {
|
||||
"default": [],
|
||||
"type": "array"
|
||||
|
@ -64,7 +64,7 @@
|
||||
},
|
||||
"required": ["type"],
|
||||
"additionalProperties": false,
|
||||
"oneOf": [
|
||||
"anyOf": [
|
||||
{
|
||||
"properties": {
|
||||
"type": {"const": "select"},
|
||||
@ -96,6 +96,21 @@
|
||||
},
|
||||
"required": ["options", "validate"]
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {"const": "group-role-map"},
|
||||
"options": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"groupPlaceholder": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"validate": {
|
||||
"minProperties": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
@ -112,7 +127,6 @@
|
||||
"float",
|
||||
"graph",
|
||||
"snmp3auth",
|
||||
"ldap-groups",
|
||||
"ad-groups",
|
||||
"oxidized-maps",
|
||||
"executable",
|
||||
|
@ -1,3 +1,18 @@
|
||||
abilities:
|
||||
Columns:
|
||||
- { Field: id, Type: 'bigint unsigned', 'Null': false, Extra: auto_increment }
|
||||
- { Field: name, Type: varchar(255), 'Null': false, Extra: '' }
|
||||
- { Field: title, Type: varchar(255), 'Null': true, Extra: '' }
|
||||
- { Field: entity_id, Type: 'bigint unsigned', 'Null': true, Extra: '' }
|
||||
- { Field: entity_type, Type: varchar(255), 'Null': true, Extra: '' }
|
||||
- { Field: only_owned, Type: tinyint, 'Null': false, Extra: '', Default: '0' }
|
||||
- { Field: options, Type: longtext, 'Null': true, Extra: '' }
|
||||
- { Field: scope, Type: int, 'Null': true, Extra: '' }
|
||||
- { Field: created_at, Type: timestamp, 'Null': true, Extra: '' }
|
||||
- { Field: updated_at, Type: timestamp, 'Null': true, Extra: '' }
|
||||
Indexes:
|
||||
PRIMARY: { Name: PRIMARY, Columns: [id], Unique: true, Type: BTREE }
|
||||
abilities_scope_index: { Name: abilities_scope_index, Columns: [scope], Unique: false, Type: BTREE }
|
||||
access_points:
|
||||
Columns:
|
||||
- { Field: accesspoint_id, Type: 'int unsigned', 'Null': false, Extra: auto_increment }
|
||||
@ -183,6 +198,22 @@ application_metrics:
|
||||
- { Field: value_prev, Type: double, 'Null': true, Extra: '' }
|
||||
Indexes:
|
||||
application_metrics_app_id_metric_unique: { Name: application_metrics_app_id_metric_unique, Columns: [app_id, metric], Unique: true, Type: BTREE }
|
||||
assigned_roles:
|
||||
Columns:
|
||||
- { Field: id, Type: 'bigint unsigned', 'Null': false, Extra: auto_increment }
|
||||
- { Field: role_id, Type: 'bigint unsigned', 'Null': false, Extra: '' }
|
||||
- { Field: entity_id, Type: 'bigint unsigned', 'Null': false, Extra: '' }
|
||||
- { Field: entity_type, Type: varchar(255), 'Null': false, Extra: '' }
|
||||
- { Field: restricted_to_id, Type: 'bigint unsigned', 'Null': true, Extra: '' }
|
||||
- { Field: restricted_to_type, Type: varchar(255), 'Null': true, Extra: '' }
|
||||
- { Field: scope, Type: int, 'Null': true, Extra: '' }
|
||||
Indexes:
|
||||
PRIMARY: { Name: PRIMARY, Columns: [id], Unique: true, Type: BTREE }
|
||||
assigned_roles_entity_index: { Name: assigned_roles_entity_index, Columns: [entity_id, entity_type, scope], Unique: false, Type: BTREE }
|
||||
assigned_roles_role_id_index: { Name: assigned_roles_role_id_index, Columns: [role_id], Unique: false, Type: BTREE }
|
||||
assigned_roles_scope_index: { Name: assigned_roles_scope_index, Columns: [scope], Unique: false, Type: BTREE }
|
||||
Constraints:
|
||||
assigned_roles_role_id_foreign: { name: assigned_roles_role_id_foreign, foreign_key: role_id, table: roles, key: id, extra: 'ON DELETE CASCADE ON UPDATE CASCADE' }
|
||||
authlog:
|
||||
Columns:
|
||||
- { Field: id, Type: 'int unsigned', 'Null': false, Extra: auto_increment }
|
||||
@ -1343,6 +1374,21 @@ pdb_ix_peers:
|
||||
- { Field: timestamp, Type: 'int unsigned', 'Null': true, Extra: '' }
|
||||
Indexes:
|
||||
PRIMARY: { Name: PRIMARY, Columns: [pdb_ix_peers_id], Unique: true, Type: BTREE }
|
||||
permissions:
|
||||
Columns:
|
||||
- { Field: id, Type: 'bigint unsigned', 'Null': false, Extra: auto_increment }
|
||||
- { Field: ability_id, Type: 'bigint unsigned', 'Null': false, Extra: '' }
|
||||
- { Field: entity_id, Type: 'bigint unsigned', 'Null': true, Extra: '' }
|
||||
- { Field: entity_type, Type: varchar(255), 'Null': true, Extra: '' }
|
||||
- { Field: forbidden, Type: tinyint, 'Null': false, Extra: '', Default: '0' }
|
||||
- { Field: scope, Type: int, 'Null': true, Extra: '' }
|
||||
Indexes:
|
||||
PRIMARY: { Name: PRIMARY, Columns: [id], Unique: true, Type: BTREE }
|
||||
permissions_ability_id_index: { Name: permissions_ability_id_index, Columns: [ability_id], Unique: false, Type: BTREE }
|
||||
permissions_entity_index: { Name: permissions_entity_index, Columns: [entity_id, entity_type, scope], Unique: false, Type: BTREE }
|
||||
permissions_scope_index: { Name: permissions_scope_index, Columns: [scope], Unique: false, Type: BTREE }
|
||||
Constraints:
|
||||
permissions_ability_id_foreign: { name: permissions_ability_id_foreign, foreign_key: ability_id, table: abilities, key: id, extra: 'ON DELETE CASCADE ON UPDATE CASCADE' }
|
||||
plugins:
|
||||
Columns:
|
||||
- { Field: plugin_id, Type: 'int unsigned', 'Null': false, Extra: auto_increment }
|
||||
@ -1767,6 +1813,18 @@ push_subscriptions:
|
||||
PRIMARY: { Name: PRIMARY, Columns: [id], Unique: true, Type: BTREE }
|
||||
push_subscriptions_endpoint_unique: { Name: push_subscriptions_endpoint_unique, Columns: [endpoint], Unique: true, Type: BTREE }
|
||||
push_subscriptions_subscribable_type_subscribable_id_index: { Name: push_subscriptions_subscribable_type_subscribable_id_index, Columns: [subscribable_type, subscribable_id], Unique: false, Type: BTREE }
|
||||
roles:
|
||||
Columns:
|
||||
- { Field: id, Type: 'bigint unsigned', 'Null': false, Extra: auto_increment }
|
||||
- { Field: name, Type: varchar(255), 'Null': false, Extra: '' }
|
||||
- { Field: title, Type: varchar(255), 'Null': true, Extra: '' }
|
||||
- { Field: scope, Type: int, 'Null': true, Extra: '' }
|
||||
- { Field: created_at, Type: timestamp, 'Null': true, Extra: '' }
|
||||
- { Field: updated_at, Type: timestamp, 'Null': true, Extra: '' }
|
||||
Indexes:
|
||||
PRIMARY: { Name: PRIMARY, Columns: [id], Unique: true, Type: BTREE }
|
||||
roles_name_unique: { Name: roles_name_unique, Columns: [name, scope], Unique: true, Type: BTREE }
|
||||
roles_scope_index: { Name: roles_scope_index, Columns: [scope], Unique: false, Type: BTREE }
|
||||
route:
|
||||
Columns:
|
||||
- { Field: route_id, Type: 'int unsigned', 'Null': false, Extra: auto_increment }
|
||||
@ -2052,7 +2110,6 @@ users:
|
||||
- { Field: realname, Type: varchar(64), 'Null': false, Extra: '' }
|
||||
- { Field: email, Type: varchar(64), 'Null': false, Extra: '' }
|
||||
- { Field: descr, Type: char(30), 'Null': false, Extra: '' }
|
||||
- { Field: level, Type: tinyint, 'Null': false, Extra: '', Default: '0' }
|
||||
- { Field: can_modify_passwd, Type: tinyint, 'Null': false, Extra: '', Default: '1' }
|
||||
- { Field: created_at, Type: timestamp, 'Null': false, Extra: '', Default: '1970-01-02 00:00:01' }
|
||||
- { Field: updated_at, Type: timestamp, 'Null': false, Extra: '', Default: CURRENT_TIMESTAMP }
|
||||
|
@ -1,15 +1,5 @@
|
||||
parameters:
|
||||
ignoreErrors:
|
||||
-
|
||||
message: "#^If condition is always false\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Authentication/MysqlAuthorizer.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#2 \\$password of method LibreNMS\\\\Authentication\\\\MysqlAuthorizer\\:\\:addUser\\(\\) expects string, null given\\.$#"
|
||||
count: 1
|
||||
path: LibreNMS/Authentication/SSOAuthorizer.php
|
||||
|
||||
-
|
||||
message: "#^Property LibreNMS\\\\Data\\\\Store\\\\Rrd\\:\\:\\$async_process \\(LibreNMS\\\\Proc\\) in isset\\(\\) is not nullable\\.$#"
|
||||
count: 1
|
||||
@ -205,6 +195,11 @@ parameters:
|
||||
count: 1
|
||||
path: app/Http/Controllers/PortGroupController.php
|
||||
|
||||
-
|
||||
message: "#^Static method Silber\\\\Bouncer\\\\BouncerFacade\\:\\:role\\(\\) invoked with 0 parameters, 1 required\\.$#"
|
||||
count: 2
|
||||
path: app/Http/Controllers/Select/RoleController.php
|
||||
|
||||
-
|
||||
message: "#^Call to an undefined static method App\\\\Models\\\\ServiceTemplate\\:\\:hasAccess\\(\\)\\.$#"
|
||||
count: 1
|
||||
@ -235,11 +230,21 @@ parameters:
|
||||
count: 1
|
||||
path: app/Http/Kernel.php
|
||||
|
||||
-
|
||||
message: "#^Static method Silber\\\\Bouncer\\\\BouncerFacade\\:\\:role\\(\\) invoked with 0 parameters, 1 required\\.$#"
|
||||
count: 2
|
||||
path: app/Http/Requests/StoreUserRequest.php
|
||||
|
||||
-
|
||||
message: "#^Cannot access property \\$password on object\\|string\\.$#"
|
||||
count: 1
|
||||
path: app/Http/Requests/UpdateUserRequest.php
|
||||
|
||||
-
|
||||
message: "#^Static method Silber\\\\Bouncer\\\\BouncerFacade\\:\\:role\\(\\) invoked with 0 parameters, 1 required\\.$#"
|
||||
count: 2
|
||||
path: app/Http/Requests/UpdateUserRequest.php
|
||||
|
||||
-
|
||||
message: "#^Access to an undefined property App\\\\Models\\\\DeviceRelatedModel\\:\\:\\$device_id\\.$#"
|
||||
count: 1
|
||||
@ -250,6 +255,11 @@ parameters:
|
||||
count: 1
|
||||
path: app/Models/UserPref.php
|
||||
|
||||
-
|
||||
message: "#^Static method Silber\\\\Bouncer\\\\BouncerFacade\\:\\:cache\\(\\) invoked with 0 parameters, 1 required\\.$#"
|
||||
count: 1
|
||||
path: app/Providers/AuthServiceProvider.php
|
||||
|
||||
-
|
||||
message: "#^Result of && is always false\\.$#"
|
||||
count: 1
|
||||
@ -270,6 +280,41 @@ parameters:
|
||||
count: 1
|
||||
path: app/View/SimpleTemplate.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$abilities of method Silber\\\\Bouncer\\\\Conductors\\\\GivesAbilities\\:\\:to\\(\\) expects array\\|Illuminate\\\\Database\\\\Eloquent\\\\Model\\|int, string given\\.$#"
|
||||
count: 1
|
||||
path: database/factories/UserFactory.php
|
||||
|
||||
-
|
||||
message: "#^Property 'name' does not exist in Silber\\\\Bouncer\\\\Database\\\\Role model\\.$#"
|
||||
count: 1
|
||||
path: database/factories/UserFactory.php
|
||||
|
||||
-
|
||||
message: "#^Static method Silber\\\\Bouncer\\\\BouncerFacade\\:\\:role\\(\\) invoked with 0 parameters, 1 required\\.$#"
|
||||
count: 1
|
||||
path: database/factories/UserFactory.php
|
||||
|
||||
-
|
||||
message: "#^Static method Silber\\\\Bouncer\\\\BouncerFacade\\:\\:refresh\\(\\) invoked with 0 parameters, 1 required\\.$#"
|
||||
count: 2
|
||||
path: database/migrations/2023_06_18_201914_migrate_level_to_roles.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#1 \\$abilities of method Silber\\\\Bouncer\\\\Conductors\\\\GivesAbilities\\:\\:to\\(\\) expects array\\|Illuminate\\\\Database\\\\Eloquent\\\\Model\\|int, string given\\.$#"
|
||||
count: 1
|
||||
path: database/seeders/RolesSeeder.php
|
||||
|
||||
-
|
||||
message: "#^Property 'name' does not exist in Silber\\\\Bouncer\\\\Database\\\\Role model\\.$#"
|
||||
count: 2
|
||||
path: database/seeders/RolesSeeder.php
|
||||
|
||||
-
|
||||
message: "#^Static method Silber\\\\Bouncer\\\\BouncerFacade\\:\\:role\\(\\) invoked with 0 parameters, 1 required\\.$#"
|
||||
count: 2
|
||||
path: database/seeders/RolesSeeder.php
|
||||
|
||||
-
|
||||
message: "#^Parameter \\#4 \\$transport of function addHost expects string, int given\\.$#"
|
||||
count: 1
|
||||
|
145
resources/js/components/LibrenmsSelect.vue
Normal file
145
resources/js/components/LibrenmsSelect.vue
Normal file
@ -0,0 +1,145 @@
|
||||
<!--
|
||||
- LibrenmsSelect.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 <http://www.gnu.org/licenses/>.
|
||||
-
|
||||
- @package LibreNMS
|
||||
- @link http://librenms.org
|
||||
- @copyright 2023 Tony Murray
|
||||
- @author Tony Murray <murraytony@gmail.com>
|
||||
-->
|
||||
|
||||
<template>
|
||||
<select :multiple="multiple"></select>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "LibrenmsSelect",
|
||||
props: {
|
||||
routeName: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
allowClear: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
value: {
|
||||
type: [String, Number, Array],
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
model: {
|
||||
event: 'change',
|
||||
prop: 'value'
|
||||
},
|
||||
data: () => ({
|
||||
select2: null
|
||||
}),
|
||||
methods: {
|
||||
checkValue() {
|
||||
if (this.value === '' || this.value === []) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// search for missing options and fetch them
|
||||
let values = this.value instanceof Array ? this.value : [this.value];
|
||||
if (this.select2.find("option").filter((id, el) => values.includes(el.value)).length < values.length) {
|
||||
axios.get(route(this.routeName), {params: {id: values.join(',')}}).then((response) => {
|
||||
response.data.results.forEach((item) => {
|
||||
if (values.find(x => x == item.id) !== undefined) {
|
||||
this.select2.append(new Option(item.text, item.id, false, true));
|
||||
}
|
||||
})
|
||||
|
||||
this.select2.trigger('change');
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(value) {
|
||||
if (value instanceof Object && value.hasOwnProperty('id') && value.hasOwnProperty('text')) {
|
||||
this.select2.append(new Option(value.text, value.id, true, true))
|
||||
.trigger('change');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// check value and if the value doesn't exist, cancel this update to fetch it
|
||||
if (! this.checkValue()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (value instanceof Array) {
|
||||
this.select2.val([...value]);
|
||||
} else {
|
||||
this.select2.val([value]);
|
||||
}
|
||||
|
||||
this.select2.trigger('change');
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
settings() {
|
||||
return {
|
||||
theme: "bootstrap",
|
||||
dropdownAutoWidth : true,
|
||||
width: "auto",
|
||||
allowClear: Boolean(this.allowClear),
|
||||
placeholder: this.placeholder,
|
||||
multiple: this.multiple,
|
||||
ajax: {
|
||||
url: route(this.routeName).toString(),
|
||||
delay: 250,
|
||||
cache: true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.select2 = $(this.$el);
|
||||
|
||||
this.checkValue();
|
||||
|
||||
this.select2.select2(this.settings)
|
||||
.on('select2:select select2:unselect', ev => {
|
||||
this.$emit('change', this.select2.val());
|
||||
this.$emit('select', ev['params']['data']);
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.select2.select2('destroy');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
@ -23,8 +23,8 @@
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div :class="['form-group', 'has-feedback', setting.class, feedback]">
|
||||
<label :for="setting.name" class="col-sm-5 control-label" v-tooltip="{ content: setting.name }">
|
||||
<div :class="['form-group', 'row', 'has-feedback', setting.class, feedback]">
|
||||
<label :for="setting.name" class="col-sm-5 col-md-3 col-form-label" v-tooltip="{ content: setting.name }">
|
||||
{{ getDescription() }}
|
||||
<span v-if="setting.units">({{ getUnits() }})</span>
|
||||
</label>
|
||||
@ -42,7 +42,7 @@
|
||||
></component>
|
||||
<span class="form-control-feedback"></span>
|
||||
</div>
|
||||
<div class="col-sm-2">
|
||||
<div>
|
||||
<button :style="{'opacity': showResetToDefault()?1:0}" @click="resetToDefault" class="btn btn-default" :class="{'disable-events': ! showResetToDefault()}" type="button" v-tooltip="{ content: $t('Reset to default') }"><i class="fa fa-refresh"></i></button>
|
||||
<button :style="{'opacity': showUndo()?1:0}" @click="resetToInitial" class="btn btn-primary" :class="{'disable-events': ! showUndo()}" type="button" v-tooltip="{ content: $t('Undo') }"><i class="fa fa-undo"></i></button>
|
||||
<div v-if="hasHelp()" v-tooltip="{content: getHelp(), trigger: 'hover click'}" class="fa fa-fw fa-lg fa-question-circle"></div>
|
||||
|
123
resources/js/components/SettingGroupRoleMap.vue
Normal file
123
resources/js/components/SettingGroupRoleMap.vue
Normal file
@ -0,0 +1,123 @@
|
||||
<!--
|
||||
- SettingGroupRoleMap.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
|
||||
- @copyright 2023 Tony Murray
|
||||
- @author Tony Murray <murraytony@gmail.com>
|
||||
-->
|
||||
|
||||
<template>
|
||||
<div v-tooltip="disabled ? $t('settings.readonly') : false">
|
||||
<div v-for="(data, group) in localList" class="tw-flex">
|
||||
<input type="text"
|
||||
class="form-control !tw-w-auto"
|
||||
:value="group"
|
||||
:readonly="disabled"
|
||||
:placeholder="options.groupPlaceholder"
|
||||
@blur="updateItem(group, $event.target.value)"
|
||||
@keyup.enter="updateItem(group, $event.target.value)"
|
||||
>
|
||||
<librenms-select class="form-control tw-flex-grow" @change="updateRoles(group, $event)" route-name="ajax.select.role" :value="data.roles" multiple :disabled="disabled" :allow-clear="false"></librenms-select>
|
||||
<button v-if="!disabled" @click="removeItem(group)" type="button" class="btn btn-danger"><i class="fa fa-minus-circle"></i></button>
|
||||
</div>
|
||||
<div v-if="!disabled" class="tw-flex">
|
||||
<input type="text" class="form-control !tw-w-auto" v-model="newItem" :placeholder="options.groupPlaceholder">
|
||||
<librenms-select class="form-control tw-flex-grow" v-model="newItemRoles" route-name="ajax.select.role" placeholder="Role" multiple :disabled="disabled" :allow-clear="false"></librenms-select>
|
||||
<button @click="addItem" type="button" class="btn btn-primary"><i class="fa fa-plus-circle"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseSetting from "./BaseSetting";
|
||||
import LibrenmsSelect from "./LibrenmsSelect.vue";
|
||||
|
||||
export default {
|
||||
name: "SettingGroupRoleMap",
|
||||
components: {LibrenmsSelect},
|
||||
mixins: [BaseSetting],
|
||||
data() {
|
||||
return {
|
||||
newItem: "",
|
||||
newItemRoles: [],
|
||||
localList: this.parseValue(this.value),
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addItem() {
|
||||
this.localList[this.newItem] = {roles: this.newItemRoles};
|
||||
this.newItem = "";
|
||||
this.newItemRoles = [];
|
||||
this.$emit('input', this.localList)
|
||||
},
|
||||
removeItem(index) {
|
||||
delete this.localList[index]
|
||||
this.$emit('input', this.localList)
|
||||
},
|
||||
updateItem(oldValue, newValue) {
|
||||
this.localList = Object.keys(this.localList).reduce((newList, current) => {
|
||||
let key = (current === oldValue ? newValue : current);
|
||||
newList[key] = this.localList[current];
|
||||
return newList;
|
||||
}, {});
|
||||
this.$emit('input', this.localList)
|
||||
},
|
||||
updateRoles(group, roles) {
|
||||
console.log(group, roles, this.lock);
|
||||
this.localList[group].roles = roles;
|
||||
this.$emit('input', this.localList)
|
||||
},
|
||||
parseValue(value) {
|
||||
// empty lists parse to an array
|
||||
if (Array.isArray(value)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const levels = {
|
||||
1: "user",
|
||||
5: "global-read",
|
||||
10: "admin",
|
||||
};
|
||||
|
||||
for (const group of Object.keys(value)) {
|
||||
if (! value[group].hasOwnProperty('roles') && value[group].hasOwnProperty('level')) {
|
||||
value[group].roles = levels[value[group].level] ? [levels[value[group].level]] : [];
|
||||
delete value[group]["level"];
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value() {
|
||||
this.localList = this.parseValue(this.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
div >>> .select2-container {
|
||||
flex-grow: 1;
|
||||
}
|
||||
div >>> .select2-selection--multiple .select2-search--inline .select2-search__field {
|
||||
width: 0.75em !important;
|
||||
}
|
||||
</style>
|
@ -24,70 +24,27 @@
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<select class="form-control"
|
||||
:name="name"
|
||||
<librenms-select class="form-control"
|
||||
:value="value"
|
||||
:route-name="'ajax.select.' + this.options.target"
|
||||
:placeholder="this.options.placeholder"
|
||||
:allow-clear="this.options.allowClear"
|
||||
:required="required"
|
||||
:disabled="disabled"
|
||||
@change="$emit('change', $event)"
|
||||
>
|
||||
</select>
|
||||
</librenms-select>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseSetting from "./BaseSetting";
|
||||
import LibrenmsSelect from "./LibrenmsSelect.vue";
|
||||
|
||||
export default {
|
||||
name: "SettingSelectDynamic",
|
||||
mixins: [BaseSetting],
|
||||
data() {
|
||||
return {
|
||||
select2: null
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
value(value) {
|
||||
this.select2.val(value).trigger('change');
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
settings() {
|
||||
return {
|
||||
theme: "bootstrap",
|
||||
dropdownAutoWidth : true,
|
||||
width: "auto",
|
||||
allowClear: Boolean(this.options.allowClear),
|
||||
placeholder: this.options.placeholder,
|
||||
ajax: {
|
||||
url: route('ajax.select.' + this.options.target).toString(),
|
||||
delay: 250,
|
||||
data: this.options.callback
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// load initial data
|
||||
axios.get(route('ajax.select.' + this.options.target), {params: {id: this.value}}).then((response) => {
|
||||
response.data.results.forEach((item) => {
|
||||
if (item.id == this.value) {
|
||||
this.select2.append(new Option(item.text, item.id, true, true))
|
||||
.trigger('change');
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this.select2 = $(this.$el)
|
||||
.find('select')
|
||||
.select2(this.settings)
|
||||
.on('select2:select select2:unselect', ev => {
|
||||
this.$emit('change', this.select2.val());
|
||||
this.$emit('select', ev['params']['data']);
|
||||
});
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.select2.select2('destroy');
|
||||
}
|
||||
components: {LibrenmsSelect},
|
||||
mixins: [BaseSetting]
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
@section('title', __('settings.title'))
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="container-fluid">
|
||||
<div id="app">
|
||||
<librenms-settings
|
||||
prefix="{{ url('settings') }}"
|
||||
|
@ -30,17 +30,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@can('admin')
|
||||
<div class="form-group @if($errors->has('level')) has-error @endif">
|
||||
<label for="level" class="control-label col-sm-3">{{ __('Level') }}</label>
|
||||
@can('viewAny', Bouncer::role())
|
||||
<div class="form-group @if($errors->has('roles')) has-error @endif">
|
||||
<label for="level" class="control-label col-sm-3">{{ __('Roles') }}</label>
|
||||
<div class="col-sm-9">
|
||||
<select class="form-control" id="level" name="level">
|
||||
<option value="1">{{ __('Normal') }}</option>
|
||||
<option value="5" @if(old('level', $user->level) == 5) selected @endif>{{ __('Global Read') }}</option>
|
||||
<option value="10" @if(old('level', $user->level) == 10) selected @endif>{{ __('Admin') }}</option>
|
||||
@if(old('level', $user->level) == 11)<option value="11" selected>{{ __('Demo') }}</option>@endif
|
||||
<select class="form-control" id="roles" name="roles[]" multiple @cannot('manage', Bouncer::role()) readonly @endcannot>
|
||||
@foreach(Bouncer::role()->all() as $role)
|
||||
<option value="{{ $role->name }}" @if(collect(old('roles', $user->roles->pluck('name')))->contains($role->name)) selected @endif>{{ __($role->title) }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<span class="help-block">{{ $errors->first('level') }}</span>
|
||||
<span class="help-block">{{ $errors->first('roles') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@endcan
|
||||
|
@ -16,9 +16,9 @@
|
||||
<th data-column-id="user_id" data-visible="false" data-identifier="true" data-type="numeric">{{ __('ID') }}</th>
|
||||
<th data-column-id="username" data-formatter="text">{{ __('Username') }}</th>
|
||||
<th data-column-id="realname" data-formatter="text">{{ __('Real Name') }}</th>
|
||||
<th data-column-id="level" data-formatter="level" data-type="numeric">{{ __('Access') }}</th>
|
||||
<th data-column-id="roles" data-formatter="roles">{{ __('Roles') }}</th>
|
||||
<th data-column-id="auth_type" data-visible="{{ $multiauth ? 'true' : 'false' }}">{{ __('auth.title') }}</th>
|
||||
<th data-column-id="email">{{ __('Email') }}</th>
|
||||
<th data-column-id="email" data-formatter="text">{{ __('Email') }}</th>
|
||||
<th data-column-id="timezone">{{ __('Timezone') }}</th>
|
||||
@if(\LibreNMS\Authentication\LegacyAuth::getType() == 'mysql')
|
||||
<th data-column-id="enabled" data-formatter="enabled">{{ __('Enabled') }}</th>
|
||||
@ -36,7 +36,7 @@
|
||||
<td>{{ $user->user_id }}</td>
|
||||
<td>{{ $user->username }}</td>
|
||||
<td>{{ $user->realname }}</td>
|
||||
<td>{{ $user->level }}</td>
|
||||
<td>{{ $user->roles->pluck('title') }}</td>
|
||||
<td>{{ $user->auth_type }}</td>
|
||||
<td>{{ $user->email }}</td>
|
||||
<td>{{ \App\Models\UserPref::getPref($user, 'timezone') ?: "Browser Timezone" }}</td>
|
||||
@ -93,12 +93,8 @@
|
||||
var delete_button = '<button type="button" title="{{ __('Delete') }}" class="btn btn-sm btn-danger" onclick="return delete_user(' + row['user_id'] + ', \'' + row['username'] + '\');">' +
|
||||
'<i class="fa fa-trash"></i></button> ';
|
||||
|
||||
// FIXME don't show for super admin
|
||||
var manage_button = '<form action="{{ url('edituser') }}/" method="GET"';
|
||||
|
||||
if (row['level'] >= 5) {
|
||||
manage_button += ' style="visibility:hidden;"'
|
||||
}
|
||||
|
||||
manage_button += '>@csrf<input type="hidden" name="user_id" value="' + row['user_id'] +
|
||||
'"><button type="submit" title="{{ __('Manage Access') }}" class="btn btn-sm btn-primary"><i class="fa fa-tasks"></i></button>' +
|
||||
'</form> ';
|
||||
@ -110,24 +106,25 @@
|
||||
|
||||
return output
|
||||
},
|
||||
level: function (column, row) {
|
||||
var level = row[column.id];
|
||||
if (level == 10) {
|
||||
return '{{ __('Admin') }}';
|
||||
} else if (level == 5) {
|
||||
return '{{ __('Global Read') }}';
|
||||
} else if (level == 11) {
|
||||
return '{{ __('Demo') }}';
|
||||
}
|
||||
roles: function (column, row) {
|
||||
let roles = JSON.parse(row[column.id]);
|
||||
let div = document.createElement('div');
|
||||
|
||||
return '{{ __('Normal') }}';
|
||||
roles.forEach((role) => {
|
||||
let label = document.createElement('span');
|
||||
label.className = 'label label-info tw-mr-1';
|
||||
label.innerText = role;
|
||||
div.appendChild(label);
|
||||
})
|
||||
|
||||
return div.outerHTML;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@if(\LibreNMS\Config::get('auth_mechanism') == 'mysql')
|
||||
@can('create', \App\Models\User::class)
|
||||
$('.actionBar').append('<div class="pull-left"><a href="{{ route('users.create') }}" type="button" class="btn btn-primary">{{ __('Add User') }}</a></div>');
|
||||
@endif
|
||||
@endcan
|
||||
|
||||
user_grid.css('display', 'table'); // done loading, show
|
||||
});
|
||||
|
@ -186,6 +186,14 @@
|
||||
</x-panel>
|
||||
@endconfig
|
||||
|
||||
<x-panel title="{{ __('Roles') }}">
|
||||
@forelse(auth()->user()->roles->pluck('title') as $role)
|
||||
<span class="label label-info tw-mr-1">{{ $role }}</span>
|
||||
@empty
|
||||
<strong class="red">{{ __('No roles!') }}</strong>
|
||||
@endforelse
|
||||
</x-panel>
|
||||
|
||||
<x-panel title="{{ __('Device Permissions') }}">
|
||||
@if(auth()->user()->hasGlobalAdmin())
|
||||
<strong class="blue">{{ __('Global Administrative Access') }}</strong>
|
||||
|
@ -164,6 +164,7 @@ Route::middleware(['auth'])->group(function () {
|
||||
Route::get('device-field', 'DeviceFieldController')->name('ajax.select.device-field');
|
||||
Route::get('device-group', 'DeviceGroupController')->name('ajax.select.device-group');
|
||||
Route::get('port-group', 'PortGroupController')->name('ajax.select.port-group');
|
||||
Route::get('role', 'RoleController')->name('ajax.select.role');
|
||||
Route::get('eventlog', 'EventlogController')->name('ajax.select.eventlog');
|
||||
Route::get('graph', 'GraphController')->name('ajax.select.graph');
|
||||
Route::get('graph-aggregate', 'GraphAggregateController')->name('ajax.select.graph-aggregate');
|
||||
@ -172,6 +173,7 @@ Route::middleware(['auth'])->group(function () {
|
||||
Route::get('syslog', 'SyslogController')->name('ajax.select.syslog');
|
||||
Route::get('location', 'LocationController')->name('ajax.select.location');
|
||||
Route::get('munin', 'MuninPluginController')->name('ajax.select.munin');
|
||||
Route::get('role', 'RoleController')->name('ajax.select.role');
|
||||
Route::get('service', 'ServiceController')->name('ajax.select.service');
|
||||
Route::get('template', 'ServiceTemplateController')->name('ajax.select.template');
|
||||
Route::get('poller-group', 'PollerGroupController')->name('ajax.select.poller-group');
|
||||
|
@ -9,8 +9,7 @@ use LibreNMS\Util\Debug;
|
||||
$options = getopt('u:rldvh');
|
||||
if (isset($options['h']) || (! isset($options['l']) && ! isset($options['u']))) {
|
||||
echo ' -u <username> (Required) username to test
|
||||
-l List all users (checks that auth can enumerate all allowed users)
|
||||
-d Enable debug output
|
||||
] -d Enable debug output
|
||||
-v Enable verbose debug output
|
||||
-h Display this help message
|
||||
';
|
||||
@ -88,17 +87,6 @@ try {
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($options['l'])) {
|
||||
$users = $authorizer->getUserlist();
|
||||
$output = array_map(function ($user) {
|
||||
return "{$user['username']} ({$user['user_id']})";
|
||||
}, $users);
|
||||
|
||||
echo 'Users: ' . implode(', ', $output) . PHP_EOL;
|
||||
echo 'Total users: ' . count($users) . PHP_EOL;
|
||||
exit;
|
||||
}
|
||||
|
||||
$test_username = $options['u'];
|
||||
$auth = false;
|
||||
|
||||
|
@ -25,6 +25,7 @@
|
||||
|
||||
namespace LibreNMS\Tests;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||||
use Illuminate\Support\Str;
|
||||
use LibreNMS\Authentication\LegacyAuth;
|
||||
@ -118,8 +119,7 @@ class AuthSSOTest extends DBTestCase
|
||||
$this->assertTrue($a->authenticate(['username' => $user]));
|
||||
|
||||
// Retrieve it and validate
|
||||
$dbuser = $a->getUser($a->getUserid($user));
|
||||
$this->assertFalse($dbuser);
|
||||
$this->assertFalse(User::thisAuth()->where('username', $user)->exists());
|
||||
}
|
||||
|
||||
// Excercise general auth flow with creation enabled
|
||||
@ -138,10 +138,10 @@ class AuthSSOTest extends DBTestCase
|
||||
$this->assertTrue($a->authenticate(['username' => $user]));
|
||||
|
||||
// Retrieve it and validate
|
||||
$dbuser = $a->getUser($a->getUserid($user));
|
||||
$this->assertSame($dbuser['realname'], $a->authSSOGetAttr(Config::get('sso.realname_attr')));
|
||||
$this->assertTrue($dbuser['level'] == -1);
|
||||
$this->assertSame($dbuser['email'], $a->authSSOGetAttr(Config::get('sso.email_attr')));
|
||||
$dbuser = User::thisAuth()->where('username', $user)->firstOrNew();
|
||||
$this->assertSame($dbuser->realname, $a->authSSOGetAttr(Config::get('sso.realname_attr')));
|
||||
$this->assertEmpty($dbuser->getRoles());
|
||||
$this->assertSame($dbuser->email, $a->authSSOGetAttr(Config::get('sso.email_attr')));
|
||||
|
||||
// Change a few things and reauth
|
||||
$_SERVER['mail'] = 'test@example.net';
|
||||
@ -150,10 +150,10 @@ class AuthSSOTest extends DBTestCase
|
||||
$this->assertTrue($a->authenticate(['username' => $user]));
|
||||
|
||||
// Retrieve it and validate the update was not persisted
|
||||
$dbuser = $a->getUser($a->getUserid($user));
|
||||
$this->assertFalse($a->authSSOGetAttr(Config::get('sso.realname_attr')) === $dbuser['realname']);
|
||||
$this->assertFalse($dbuser['level'] === '10');
|
||||
$this->assertFalse($a->authSSOGetAttr(Config::get('sso.email_attr')) === $dbuser['email']);
|
||||
$dbuser = User::thisAuth()->where('username', $user)->firstOrNew();
|
||||
$this->assertFalse($a->authSSOGetAttr(Config::get('sso.realname_attr')) === $dbuser->realname);
|
||||
$this->assertFalse($dbuser->roles()->where('name', 'admin')->exists());
|
||||
$this->assertFalse($a->authSSOGetAttr(Config::get('sso.email_attr')) === $dbuser->email);
|
||||
}
|
||||
|
||||
// Excercise general auth flow with updates enabled
|
||||
@ -166,19 +166,19 @@ class AuthSSOTest extends DBTestCase
|
||||
// Create a random username and store it with the defaults
|
||||
$this->basicEnvironmentEnv();
|
||||
$user = $this->makeUser();
|
||||
$this->assertTrue($a->authenticate(['username' => $user]));
|
||||
$this->assertTrue(auth()->attempt(['username' => $user]));
|
||||
|
||||
// Change a few things and reauth
|
||||
$_SERVER['mail'] = 'test@example.net';
|
||||
$_SERVER['displayName'] = 'Testier User';
|
||||
Config::set('sso.static_level', 10);
|
||||
$this->assertTrue($a->authenticate(['username' => $user]));
|
||||
$this->assertTrue(auth()->attempt(['username' => $user]));
|
||||
|
||||
// Retrieve it and validate the update persisted
|
||||
$dbuser = $a->getUser($a->getUserid($user));
|
||||
$this->assertSame($dbuser['realname'], $a->authSSOGetAttr(Config::get('sso.realname_attr')));
|
||||
$this->assertTrue($dbuser['level'] == 10);
|
||||
$this->assertSame($dbuser['email'], $a->authSSOGetAttr(Config::get('sso.email_attr')));
|
||||
$dbuser = User::thisAuth()->where('username', $user)->firstOrNew();
|
||||
$this->assertSame($dbuser->realname, $a->authSSOGetAttr(Config::get('sso.realname_attr')));
|
||||
$this->assertTrue($dbuser->roles()->where('name', 'admin')->exists());
|
||||
$this->assertSame($dbuser->email, $a->authSSOGetAttr(Config::get('sso.email_attr')));
|
||||
}
|
||||
|
||||
// Check some invalid authentication modes
|
||||
@ -212,13 +212,13 @@ class AuthSSOTest extends DBTestCase
|
||||
unset($_SERVER['displayName']);
|
||||
unset($_SERVER['mail']);
|
||||
|
||||
$this->assertTrue($a->authenticate(['username' => $this->makeUser()]));
|
||||
$this->assertTrue(auth()->attempt(['username' => $this->makeUser()]));
|
||||
|
||||
$this->basicEnvironmentHeader();
|
||||
unset($_SERVER['HTTP_DISPLAYNAME']);
|
||||
unset($_SERVER['HTTP_MAIL']);
|
||||
|
||||
$this->assertTrue($a->authenticate(['username' => $this->makeUser()]));
|
||||
$this->assertTrue(auth()->attempt(['username' => $this->makeUser()]));
|
||||
}
|
||||
|
||||
// Document the modules current behaviour, so that changes trigger test failures
|
||||
@ -347,7 +347,7 @@ class AuthSSOTest extends DBTestCase
|
||||
|
||||
public function testLevelCaulculationFromAttr(): void
|
||||
{
|
||||
/** @var \LibreNMS\Authentication\SSOAuthorizer */
|
||||
/** @var \LibreNMS\Authentication\SSOAuthorizer $a */
|
||||
$a = LegacyAuth::reset();
|
||||
|
||||
Config::set('sso.mode', 'env');
|
||||
@ -355,37 +355,42 @@ class AuthSSOTest extends DBTestCase
|
||||
|
||||
//Integer
|
||||
Config::set('sso.level_attr', 'level');
|
||||
$_SERVER['level'] = 9;
|
||||
$this->assertSame(9, $a->authSSOCalculateLevel());
|
||||
$_SERVER['level'] = 5;
|
||||
$this->assertSame(['global-read'], $a->getRoles(''));
|
||||
|
||||
//String
|
||||
Config::set('sso.level_attr', 'level');
|
||||
$_SERVER['level'] = '9';
|
||||
$this->assertSame(9, $a->authSSOCalculateLevel());
|
||||
$_SERVER['level'] = '5';
|
||||
$this->assertSame(['global-read'], $a->getRoles(''));
|
||||
|
||||
// invalid level
|
||||
Config::set('sso.level_attr', 'level');
|
||||
$_SERVER['level'] = 9;
|
||||
$this->assertSame([], $a->getRoles(''));
|
||||
|
||||
//Invalid String
|
||||
Config::set('sso.level_attr', 'level');
|
||||
$_SERVER['level'] = 'foobar';
|
||||
$this->expectException('LibreNMS\Exceptions\AuthenticationException');
|
||||
$a->authSSOCalculateLevel();
|
||||
$a->getRoles('');
|
||||
|
||||
//null
|
||||
Config::set('sso.level_attr', 'level');
|
||||
$_SERVER['level'] = null;
|
||||
$this->expectException('LibreNMS\Exceptions\AuthenticationException');
|
||||
$a->authSSOCalculateLevel();
|
||||
$a->getRoles('');
|
||||
|
||||
//Unset pointer
|
||||
Config::forget('sso.level_attr');
|
||||
$_SERVER['level'] = '9';
|
||||
$this->expectException('LibreNMS\Exceptions\AuthenticationException');
|
||||
$a->authSSOCalculateLevel();
|
||||
$a->getRoles('');
|
||||
|
||||
//Unset attr
|
||||
Config::set('sso.level_attr', 'level');
|
||||
unset($_SERVER['level']);
|
||||
$this->expectException('LibreNMS\Exceptions\AuthenticationException');
|
||||
$a->authSSOCalculateLevel();
|
||||
$a->getRoles('');
|
||||
}
|
||||
|
||||
public function testGroupParsing(): void
|
||||
|
@ -36,6 +36,7 @@ class BasicApiTest extends DBTestCase
|
||||
|
||||
public function testListDevices(): void
|
||||
{
|
||||
/** @var User $user */
|
||||
$user = User::factory()->admin()->create();
|
||||
$token = ApiToken::generateToken($user);
|
||||
$device = Device::factory()->create();
|
||||
|
@ -42,8 +42,8 @@ class TestConfigCommands extends InMemoryDbTestCase
|
||||
|
||||
// set inside
|
||||
$this->assertCliGets('auth_ldap_groups.somegroup', null);
|
||||
$this->artisan('config:set', ['setting' => 'auth_ldap_groups.somegroup', 'value' => '{"level": 3}'])->assertExitCode(0);
|
||||
$this->assertCliGets('auth_ldap_groups.somegroup', ['level' => 3]);
|
||||
$this->artisan('config:set', ['setting' => 'auth_ldap_groups.somegroup', 'value' => '{"roles": ["banana"]}'])->assertExitCode(0);
|
||||
$this->assertCliGets('auth_ldap_groups.somegroup', ['roles' => ['banana']]);
|
||||
$this->artisan('config:set', ['setting' => 'auth_ldap_groups.somegroup'])
|
||||
->expectsConfirmation(trans('commands.config:set.forget_from', ['path' => 'somegroup', 'parent' => 'auth_ldap_groups']), 'yes')
|
||||
->assertExitCode(0);
|
||||
|
Loading…
Reference in New Issue
Block a user