Посоветуйте реализацию прав пользователей

Nelius
Вот вам наглядный пример:
Модуль менеджеры.
Менеджер может:
1. посмотреть пользователей
2. Посмотреть заказы
3. Обновить инфу о заказах
4. Удалить заказ
5. Обновить заказ.

Модуль новости
1.2.3.4. смотреть/добавить/редактировать/удалить.

Т.е. для разных отношений (модулей) у вас различное количество прав. Следовательно - надо делать отношение "модуль" и "права" - в разных таблицах.

Автор оригинала: triumvirat
ИМХО, не вяжетццо с этим: Реляционная СУБД

Nelius
и с чем из этого конкретно не вяжется?
Это не вяжется прежде всего с тем, что содержимое каждого поля должно быть атомарным (т.е. неделимым)
 
Да да, вы правы, мой подход может подойти для частного случая, но он не универсален. Всем спасибо, хоть автор темы и не я но для себя многое вынес!!!

-~{}~ 09.11.07 22:45:

Последовав вашим советам я создал таблицу permissions:

id | uid | mod | act
1 | 1 | 1 | add
2 | 1 | 1 | delete
3 | 1 | 1 | edit
2 | 2 | 3 | view

Но смотрите что выходит... если модулей 30 а среднее количество действий 6 то это 180 записей на юзера
при 100000 юзеров это ~18 000 000 записей...
Мне кажется это много и тормозно...

Я провел тесты, создал в таблицах users и perms1 по 250 000 и 400000 записей соответственно. В цикле 30 000 подряд вынимаю эти данные из бд...
В итоге получаю время генерации страницы MIN: 28.6972169876 - MAX: 58.4005129337 сек

код:

PHP:
$stm = explode(' ',microtime());
$stm = $stm[1] + $stm[0];

ini_set('max_execution_time',190);

$db['charset'] = 'utf8';
$db['name'] = 'tests_db';
$db['host']  = 'localhost';
$db['user'] = 'юзер';
$db['pass'] = 'пароль';
$db['cid'] = '';

$db['cid'] = mysql_connect($db['host'],$db['user'],$db['pass']) or die('DB error #'.mysql_errno());
mysql_select_db($db['name'], $db['cid']) or die('DB error #'.mysql_errno());
mysql_query('SET NAMES \''.$db['charset'].'\'') or die('DB Error #'.mysql_errno());

for ($z=0;$z<30000;$z++) {
	$f_res = mysql_query('SELECT * FROM users1 WHERE id=\'2\'') or die('DB Error #'.mysql_errno());
	$user = mysql_fetch_assoc($f_res); mysql_free_result($f_res);
	$f_res = mysql_query('SELECT * FROM perms1 FORCE INDEX (`srch`) WHERE uid=\'2\' && `mod`=\'1\'') or die('DB Error #'.mysql_errno());
	$user['perms'] = mysql_fetch_assoc($f_res); mysql_free_result($f_res);
	unset($user);
}

mysql_close($db['cid']);

$etm = explode(' ',microtime());
$etm = $etm[1] + $etm[0];
print $etm = $etm - $stm;

Далее я пошел на эксперимент и нарушение признаков Реляционной СУБД создав таблицу вида:

id | uid | mod | act
1 | 1 | 1 | add:delete:edit:view

колличество рядов в таблицах оставил тоже самое(хотя при такой реаллизации на одного пользователя не 180 записей а в 6 раз меньше 30) провел тесты получил время генерации MIN: 17.2141671181 - MAX: 34.9395740032 сек

код:

PHP:
//без изменения

for ($z=0;$z<30000;$z++) {
	$f_res = mysql_query('SELECT a.*, b.act as `perms` FROM users1 a, perms b FORCE INDEX (`srch`) WHERE a.id=\'2\' && b.uid=\'2\' && b.mod=\'37673951\'') or die('DB Error #'.mysql_errno());
	$user = mysql_fetch_assoc($f_res); mysql_free_result($f_res);
	$user['perms'] = explode(':',$user['perms']);
	unset($user);
}
//без изменения

Что вы думаете по этому поводу? Второй способ выигрывает и по скорости и по объему данных, хоть и данные не атомарные... Хотелось бы знать мнение гуру :)

Кстати кто-то тут писал что не понимает для чего нужны роли, вот наглядный пример, при большом кол-ве юзеров вместо того чтобы иметь мега таблицы с 1000 000 записей и можно иметь 10 ролей и табличку с разрешениями максимум на 1000 строк... думаю поиск будет гораздо быстрее, а для описания частных случаев можно завести еще табличку куда добавляются права пользователя, допутим модератора, которому запрещенно удалять темы, но можно редактировать, итого роль+ 1 запись быстро, удобно.
 
Из реализации ролей мне нравится древовидная. Там есть только роль, которая может наследовать права от какой-то другой. Роль имеет флаг - можно ли ее использовать для логина или нет. Например, заводим роль - модераторы, от них наследуем модераторов разделов, а от них уже - роли "логины".
Удобно, конечно, при большом кол-ве пользователей. Для небольшого, может, достаточно и обычной схемы "груупа-пользователь", которая, правда, легко строится на наследовании ролей.

-~{}~ 10.11.07 01:55:

По топу - если актов немного, а их скорее всего немного, введите их отдельными полями.
 
По топу - если актов немного, а их скорее всего немного, введите их отдельными полями.
Я бы с удовольствием ввел, но акты для разделов могут отличаться, так я могу просто дописать add:delete:edit:new_act:super_new_act не трогая БД, а если колонки, то потом придется добавлять :(

-~{}~ 10.11.07 02:06:

Кстати про древовидную реализауию ролей, мне она тоже нравится, но для моего проекта подойдут и группы, в какой-то степени они же и роли, тока примитивные) Так что будем следовать принципу Keep It Simple Stupid :)
 
for ($z=0;$z<30000;$z++) {
после этого уже смотреть что-то не хотелось..

-~{}~ 10.11.07 11:41:

Nelius
если модулей 30 а среднее количество действий 6 то это 180 записей на юзера
при 100000 юзеров это ~18 000 000 записей...
зачем, я не понял, на каждого юзера права ставить?
 
PHP:
abstract class User {
	function __construct ($name) {
		$this->name = $name;
	}

	function getName () {
		return $this->name;
	}

	// Методы определения прав доступа
	function hasReadPermission () {
		return true;
	}

	function hasModifyPermission () {
		return false;
	}

	function hasDeletePermission () {
		return false;
	}
}

class GuestUser extends User {
}

class CustomerUser extends user {
	function hasModifyPermission () {
		return true;
	}
}

class AdminUser extends User {
	function hasModifyPermission () {
		return true;
	}

	function hasDeletePermission () {
		return true;
	}
}

class UserFactory {
	private static $users = array ("Steel" => "Admin", "Alex" => "Customer", "Guest" => "Guest");

	static function Create ($name) {
		if (!isset(self::$users[$name])) {
			// Вывод сообщения об ошибке - пользователь не зарегистрирован
		}

		switch (self::$users[$name]) {
			case "Guest": return new GuestUser ($name);
			case "Customer":  return new CustomerUser ($name);
			case "Admin": return new AdminUser ($name);
			default: // Ошибка - неизвестный тип пользователя
		}
	}
}

function boolToStr ($b) {
	if ($b == true) {
		return "Да<br>";
	} else {
		return "Нет<br>";
	}
}

function displayPermissions (User $obj) {
	print "Права доступа пользователя ".$obj->getName().":<br>";
	print "Чтение: ".boolToStr($obj->hasReadPermission());
	print "Изменение: ".boolToStr($obj->hasModifyPermission());
	print "Удаление: ".boolToStr($obj->hasDeletePermission())."<br>";
}

$logins = array ("Steel", "Alex", "Guest");

foreach ($logins as $login) {
	displayPermissions (UserFactory::Create($login));
}
 
Я закончил недавно такую системку ограничения прав(безусловно навеяно мусклом:=)):
group:
id int
name varchar(16)
User.R_s enum('N','Y')
User.K_s enum('N','Y')
и тд

user
id int
id_g int
chng tinyint(1)
name varchar(16)
password varchar(41)
User.R_s enum('N','Y')
User.K_s enum('N','Y')
и тд

Схема простая:
Заводим некоторые группы.
Заводим пользователей
При заведении права подтягиваються из группы и если нужно потом изменяються.


ЗЫ: на данный момент ~500 пользователей все довольны всем нравитсо)
 
Автор оригинала: triumvirat зачем, я не понял, на каждого юзера права ставить?
Я и не собираюсь ставить права на каждого пользователя, использую роли, просто хотел выяснить какая реаллизация будет быстрее.
 
Nelius роли необходимы но заводить отдельную роль когда нужно добавить или забрать привилегию у единственного пользователя непрально.
 
Автор оригинала: TutanXamoN
Nelius роли необходимы но заводить отдельную роль когда нужно добавить или забрать привилегию у единственного пользователя непрально.
Согласен, я выше написал как я решаю эту задачу.
Повторюсь. Пользователь выполняет роль модератора значит обладает соответствующими правами, но есть еще таблица отдельно прав пользователей и если что-то меня не устраивает из прав роли модератора для данного пользователя я просто добавляю ему например move_topic: false. У роли модератор есть право на move_topic но я принудительно ее снял для конкретного пользователя.
Только возможно для моего случая понятие ролей использовать нельзя, а более правильно использовать понятие групп.
 
Nelius, мне кажется в твоем случае цепочка ролей будет не сильно сложнее в реализации ;) всего-то надо взять цепочку ролей, и пройти по ней. Соотв-но в каждой роли доступ может: не метяться, запрещаться, разрешаться. Назови в админке роли без права логиниться - группой, а с правом - юзерами, и никто даж не поймет ничего ;)
 
кстати, почему-то никто не юзает бинарные операции?

К примеру, права:
PHP:
define('P_VIEW', 1);
define('P_ADD', 2);
define('P_EDIT', 4);
define('P_DELETE', 8);
define('P_ALL', 15);

$permision = P_ALL ^ P_DELETE; // все права кроме удаления
$permision = P_VIEW | P_ADD; // просмотр и добавление

if ($permision & P_VIEW) echo 'просмотр<BR>';
if ($permision & P_ADD) echo 'добавление<BR>'; 
if ($permision & P_EDIT) echo 'редактирование<BR>'; 
if ($permision & P_DELETE) echo 'удаление<BR>';

Удобно, наглядно и компактно, в один TINYINT можно сохранять 8 прав доступа, в INT до 32. Точно также можно выбирать из базы информацию о конкретных правах, к примеру, выбор всех юзеров у которых есть право просмотра:
PHP:
$res = mysql_query('SELECT * FROM perm & ' . P_VIEW);
 
> кстати, почему-то никто не юзает бинарные операции?

Мне кажется просто потому, что мало кто из нынешних программеров имеет профильное образование или писал на низком уровне ;)
 
vovanium
Идея интересная, сам не догадался... (
Думаю так будет работать быстрее всего, спасибо, попробую...

MiksIr
у меня профильное образование, я программист, хоть и учился я далеко не в Бауманке... После того как пересел 3 года назад с С++ на php и С++ забросил сам начал замечать что порой простые задачи решаю криво и по ламерски...
Конечно хотелось бы сказать что мол пхп более прост и типа это он во всем виноват распологает к "недуманию", но понимаю что дело конечно же во мне.
 
Ну я без личностей. Просто сталкивался с хорошими ПХП программерами, которых рассказ о битовых флагах вводил в ступор.
 
Сверху