Изобретаю велик: система пермиссий для веба

Тема в разделе "WASM.HEAP", создана пользователем _DEN_, 1 фев 2011.

  1. _DEN_

    _DEN_ DEN

    Публикаций:
    0
    Регистрация:
    8 окт 2003
    Сообщения:
    5.383
    Адрес:
    Йобастан
    Привет!

    Поделитесь пожалуйста умными мыслями на тему того, как по современным понятиями проектируется концепция (архитектура) пермиссий? А я пока что расскажу, какие неумные мысли пришли мне в голову. Последовательно, от простого к сложному.

    Итак, у нас есть сайт, представляющий собой набор пар URL => Permissions. Под урлом подразумевается и GET-ы (страницы), и POST-ы (действия).

    Шаг первый.
    Вводим понятия Account и AccountType.
    Account - ну это собственно аккаунт :) Аккаунт имеет свой тип - AccountType. Возможности аккаунта определяются его типом.
    AccountType - пока что будут: Root, Moderator и User. Root - может все. Moderator - частичные админские привилегии. User - может менеджить только свои собственные юзерские данные.
    Теперь конфигурация пермиссий представляет собой, к примеру:

    / => Root, Moderator, User
    /user/edit/ => Root, Moderator, User
    /user/ban/ => Root, Moderator
    /admin/edit/ => Root

    Шаг второй.
    Предположим что правила сайта таковы, что все что может User - может и Moderator, а все что может Moderator - может и Root. Мы не хотим каждый раз писать "Root, Moderator, User", поэтому вводим соглашение, что AccountType представляет собой дерево с правилом: парентовая нода может все, что может чайлдовая, плюс свои собственные пермиссии. Теперь конфиг из первого шага будет выглядеть так:

    / => User
    /user/edit/ => User
    /user/ban/ => Moderator
    /admin/edit/ => Root

    Шаг третий.
    Пусть наш сайт - это сайт работодателей и соискателей. Добавляем AccountType "Employer". Теперь наши AccountType: Root, Moderator, User, Employer. Root и Moderator остаются без изменений. User может читать страницу Employer-а, но не редактировать. Employer может читать страницу User, но не редактировать. Дерево разветвилось: Root -> Moderator -> [User | Employer]. Теперь для некоторых страниц все же придется перечислять AccountType, если они находятся на разных ветках иерархии:

    /user/home/ => User, Employer
    /user/edit/ => User
    /employer/home/ => User, Employer
    /employer/edit/ => Employer

    Шаг четвертый, с которого начинаются проблемы :)
    Пока что я не придумал внятной концепции для случая, когда пермиссии определяются не только AccountType, но и данными в базе, связанными с самим Account. Например:
    1. Все тот же сайт работодателей и соискателей. Однако, теперь соискатель хочет, чтобы его данные могли просматривать только работодатели, выбранные им самим.
    2. Социальная сеть. Некоторые страницы или функции могут быть доступны только для друзей. Факт того, что UserA является другом UserB, уже хранится в базе, и связан с Account, а не с AccountType.

    Пока что все. Уверен на 95%, что все, изложенное в этом посте - полная хрень :) Поэтому прошу советов мудрых. Речь идет о концепции пермиссий для ситуации, когда пермиссии определяются деревом AccountType, И данными, связанными с Account. Ну хотя может это тоже полная хрень :) Короче, что скажете?
     
  2. blacktelecom

    blacktelecom New Member

    Публикаций:
    0
    Регистрация:
    8 ноя 2010
    Сообщения:
    235
    Может, кусок из моего проекта может помочь
    Код (Text):
    1. package Entity is
    2. pragma pure;
    3. end Entity;
    4.  
    5. package Entity.UserGroup IS
    6.  
    7. type Object is tagged private;
    8. type User_Group IS ACCESS Object;
    9. type User_GroupA IS ARRAY (POSITIVE RANGE <>) OF User_Group;
    10. type User_Group_List IS ACCESS User_GroupA;
    11.  
    12.  
    13. private
    14.  type Object IS Tagged Record
    15. id: Integer;
    16. Name: Unbounded_String;
    17.  
    18. end Record;
    19.  
    20.  
    21.   create_query: constant string:="INSERT INTO UserGroup (id, name) VALUES ($1, $2)";
    22.   find_query: constant string:="SELECT * FROM UserGroup WHERE id=$1";
    23.  
    24. end Entity.UserGroup;
    25.  
    26.  
    27. package Entity.SystemUser IS
    28.  
    29. type Object is tagged private;
    30. type System_User IS ACCESS Object;
    31. type System_UserA IS ARRAY (POSITIVE RANGE <>) OF System_User;
    32. type System_User_List IS ACCESS System_UserA;
    33.  
    34.  
    35. function listGroups ( obj: IN Object ) return User_Group_List;
    36.  
    37.  
    38. private:
    39.  
    40.   type Object IS TAGGED RECORD
    41. id: integer;
    42. -- Поля....
    43.  
    44. end Record;
    45. end Entity.SystemUser;
    46.  
    47. -- ВНИМАНИЕ:
    48. -- Пусть у нас есть аннотации "Юзер--->Список групп"
    49. list_groups_query: constant string:= "SELECT * FROM sysuser_gpr WHERE user_t=$1";
    К примеру, получаем - управляемого юзера, способного состоять в нескольких группах.


    Определяем интерфейс Web_Node

    Код (Text):
    1. TYPE Web_Node IS INTERFACE;
    2.  
    3. procedure set_rights ( wn IN OUT Web_Node, R: Right_Def ) IS ABStRACT;
    4. --procedure remove, modify, update и
    5. function get_rights (wn: Web_Node ) return Right_Def IS ABSTRACT;
    Далее - каждый web-ресурс ( или функции серв. приложений ) я наследую от Web_Node,
    соответственно расставляю права ( для юзера, для группы, для остальных ).

    И всё. Вся концепция стара как мир
     
  3. _DEN_

    _DEN_ DEN

    Публикаций:
    0
    Регистрация:
    8 окт 2003
    Сообщения:
    5.383
    Адрес:
    Йобастан
    Но есть еще и пятый пункт, в котором все еще сложнее - та же социальная сеть, в которой есть настройки приватности. То есть страницу юзера можно посмотреть, если:

    1. Либо мы сами и есть этот юзер.
    2. Либо мы любой залогиненый юзер и первый юзер разрешил смотреть свою страницу всем.
    3. Либо первый юзер разрешил смотреть свою страницу только друзьям, и мы - залогиненый юзер, являющийся другом первого юзера.

    При этом если мы залогиненый юзер, но не являемся другом, и первый юзер разрешает смотреть только друзьями, то нужно показать страницу из "Фотка - имя - фамилия" и уведомление о том, что страницу могут смотреть только друзья. Тут уже участвует переплетение деревьев нашего AccountType, и AccountType, для которого выставлена доступность самим юзером. А сопоставление деревьев делается через дополнительную таблиуц (UserA is a friend of UserB). Жесть.
     
  4. blacktelecom

    blacktelecom New Member

    Публикаций:
    0
    Регистрация:
    8 ноя 2010
    Сообщения:
    235
    Алсо я создаю:

    Systemuser----------->Player--------->HumanUser
    | |
    | |
    V V
    SystemService RobotUser
     
  5. green

    green New Member

    Публикаций:
    0
    Регистрация:
    15 июл 2003
    Сообщения:
    1.217
    Адрес:
    Ukraine
    _DEN_
    Сделай по тем же принципам, что в Windows, например.
    Механизма групп, ACL и привилегий хватит с головой.
     
  6. ava

    ava New Member

    Публикаций:
    0
    Регистрация:
    11 окт 2003
    Сообщения:
    169
    "Пермиссии" по-русски - "разрешения", "права".

    Может, лучше назвать его не Root, а Admin? Под root-ом обычно подразумевается учетка, которая может сделать действительно все, в том числе и уничтожить систему.
    Каждый пользователь может играть множество ролей. Например, это могут быть роли разработчика сайта, редактора статей, работодателя, члена клуба и т. п. Плюс у каждого раздела сайта может быть свой набор модераторов. Если каждому типу учетки соответствует единственная роль, то гибкое разграничение прав не получится. А регистрировать отдельный тип учетки для каждой возможной комбинации слишком накладно и бредово. Лучше оставить только два типа - root и не-root (или вовсе один), а для задания ролей завести группы пользователей. Причем членство в группе может задавать не только привилегии, но и ограничения. Пример - группа Banned, в которую заносятся наказанные пользователи.

    Помимо групп можно завести "псевдогруппы". Каждой псевдогруппе соответствует функция, которая принимает учетку текущего пользователя и запрошенный объект и определяет, входит ли данный пользователь в данную группу в данный момент. Примеры:
    - Author, обозначает владельца объекта: функция проверяет, совпадает ли учетка пользователя с учеткой создателя объекта;
    - ThisModer, задает модератора текущего раздела сайта: функция ищет текущего пользователя в списке модераторов раздела, в котором размещено сообщение;
    - Friend: функция проверяет, входит ли текущий пользователь в список друзей владельца объекта;
    - Anyone: функция ничего не делает, а просто дает "добро".

    Для задания политики можно использовать формулы. Формула является логическим выражением, состоящим из операндов (идентификаторов пользователей, групп, псевдогрупп, других формул) и логических операторов & (AND), | (OR), ! (NOT). Пример:
    - формула EditPost = (Author & !Banned) | ThisModer | Admin означает, что редактировать сообщение может автор сообщения (если он не наказан), модератор данного раздела или администратор.

    Чтобы задать разные права доступа для разных объектов одного класса (например, запись блога может быть подзамочной, а может и не быть), можно поступить двумя способами:
    - хранить в каждом объекте флаг (обычная/подзамочная запись); создать псевдогруппу (CanRead), функция которой проверяет, является ли запись подзамочной, и если да, то является ли текущий пользователь другом автора; в классе объекта использовать формулу вроде ViewPost = Author | CanRead;
    - хранить в каждом объекте метку - идентификатор псевдогруппы Anyone, если запись обычная, или Friend, если подзамочная; создать псевдогруппу (Reader), функция которой читает метку и вызывает соответствующую этой метке функцию; в классе объекта использовать формулу вроде ViewPost = Author | Reader.

    В результате любое, сколь угодно сложное правило можно записать в виде единственного идентификатора пользователя, (псевдо)группы, формулы или метки.
     
  7. _DEN_

    _DEN_ DEN

    Публикаций:
    0
    Регистрация:
    8 окт 2003
    Сообщения:
    5.383
    Адрес:
    Йобастан
    Всем спасибо. С пятым шагом кажется все ясно, можно придумывать свою систему правил. Теперь шаг номер шесть, с которым опять есть проблемы :)

    Пока что речь шла о единичных объектах - права посмотреть личную инфу, отправить сообщение, и т.д. Теперь нужно придумать, как по-быстрому разобраться с пермиссиями, когда речь идет о списке объектов.

    Например, у нас есть блог, и мы хотим просматривать ленту сообщений выбранного списка пользователей, и эти пользователи - не обязательно наши друзья. Простейшая выборка:

    select * from post where author_id in (select author_id from subscribe where user_id = current_user_id) limit 100

    При этом некоторые посты некоторых пользователей доступны только для друзей. Если выбрать 100 последних записей, и отрезать лишние на уровне верстки, то мы получим ленту из <= 100 записей. А хочется всегда иметь ленту из 100 записей. То есть, фильтр пермиссий нужно делать на уровне SQL-запроса.

    Из сессии мы собрали данные, на основе которых получили информацию о членстве текущего залогиненного юзера в каких-то группах. Однако, сложность в том, что наличие участия текущего юзера в псевдо-группах для каждой строки результата запроса будет свое, и эту проверку нужно каким-то образом делать в where-условии. То есть, на псевдо-SQL это будет так:

    select * from post where author_id in (select author_id from subscribe where user_id = current_user_id) and (select pseudo_group from post_privacy where author_id = post.author_id) = CurrentUserGroup limit 100

    При этом CurrentUserGroup будет разная, т.к. она зависит от post.author_id! Как тут быть? :)
     
  8. Black_mirror

    Black_mirror Active Member

    Публикаций:
    0
    Регистрация:
    14 окт 2002
    Сообщения:
    1.035
    _DEN_
    Один из вариантов со списком это перечислять все таблицы которые нужны для проверки условий после from, а в where перечислять все условия ссылаясь на эти таблицы, что-то типа такого безобразия:
    select * from post,friends where author_id in (select author_id from subscribe where user_id = current_user_id) and
    (post.canRead=='ALL' OR post.autor_id== current_user_id OR
    (post.canRead=='FRIENDS' AND friends.user_id==post.autor_id AND post.friend_id==current_user_id)) group by post.post_id limit 100
    Естественно что руками такого не напишешь, то есть нужно из выражений вида (Autor || Friend), как-то автоматически генерировать всё это безобразие.
     
  9. _DEN_

    _DEN_ DEN

    Публикаций:
    0
    Регистрация:
    8 окт 2003
    Сообщения:
    5.383
    Адрес:
    Йобастан
    Всем спасибо. Сделал практически так, как предложил ava. Получилось достаточно гибко и просто в использовании.

    Black_mirror
    Да, я тоже примерно об этом и думал. В общем, пока что решил забить на этот 6-й шаг :)