mod_shared_roster_ldap логика работы и возможности

2.1.8, ad ldap
в первый раз, хочу понять логику работы.
ищу группы:
{ldap_rfilter,"(objectClass=organizationalUnit)"}
{ldap_groupattr,"distinguishedName"}
{ldap_groupdesc,"name"}
запрашиваю:
{searchRequest,
.{'SearchRequest',"DC=ecouk,DC=ru",wholeSubtree,
..neverDerefAliases,0,5,false,
..{equalityMatch,
...{'AttributeValueAssertion',"objectClass",
...."organizationalUnit"}},
..["distinguishedName"]}}
получаю:
{searchResEntry,
....{'SearchResultEntry',
........"OU=Domain Controllers,DC=ecouk,DC=ru",
........[{'PartialAttributeList_SEQOF',
............."distinguishedName",
.............["OU=Domain Controllers,DC=ecouk,DC=ru"]}]}}
{searchResEntry,
....{'SearchResultEntry',
........"OU=Principal,DC=ecouk,DC=ru",
........[{'PartialAttributeList_SEQOF',
............."distinguishedName",
.............["OU=Principal,DC=ecouk,DC=ru"]}]}}
{searchResEntry,
....{'SearchResultEntry',
........"OU=Ordinary,DC=ecouk,DC=ru",
........[{'PartialAttributeList_SEQOF',
............."distinguishedName",
.............["OU=Ordinary,DC=ecouk,DC=ru"]}]}}
ок.
* {ldap_gfilter, ""},{ldap_ufilter, ""},{ldap_filter, ""} -дабы не мешали кучей вложенных and
ищу юзеров:
{ldap_memberattr,"cn"}
{ldap_userdesc,"userPrincipalName"}
запрос:
{searchRequest,
.{'SearchRequest',"DC=ecouk,DC=ru",wholeSubtree,
..neverDerefAliases,0,5,false,
..{'and',
...[{present,"cn"},
....{equalityMatch,
.....{'AttributeValueAssertion',"distinguishedName",
......"OU=Ordinary,DC=ecouk,DC=ru"}}]},
..["distinguishedName","name","cn"]}}
в ответ ничего.
и вот по какой причине, как мне думается: distinguishedName для члена OU содержит имя члена, т.е. "CN=123,OU=Principal,DC=ecouk,DC=ru", что не удовлетворяет условию equalityMatch "OU=Principal,DC=ecouk,DC=ru"
если так, то собственно вопрос: какие есть инструменты по мм..кромсанию ответов, заданию условий- для тех случаев, когда нужно работать только с частью?

зы: в данном случае хотел получить группы на основе OU, содержащие юзеров, принадлежащих этим OU
ззы: видимо неправильно понимаю, поскольку в запрос, как мне казалось, поиска юзера вклинивается name- атрибут из groupdesc, а что ему тут делать-то? %\

неадекватный модуль. версия

неадекватный модуль. версия от 030811 хоть немного вменяема.
как сделать, чтобы у юзеров из шаред ростера присутствие отображалось реальное, а не всегда оффлайн?

я ни как не пойму почему не

я ни как не пойму почему не работает версия модуля от 03.08.2011г. и вообще он способен работать с OpenLDAP ? В общем конфиг такой:

{mod_shared_roster_ldap, [
{ldap_servers, ["192.168.100.7"]},
{ldap_group_base, "ou=testing,ou=addressbook,dc=example,dc=com"},
{ldap_rfilter, "(objectClass=organizationalUnit)"},
%%{ldap_groupattr, "ou"},
{ldap_gfilter, "(&(objectClass=organizationalUnit)(ou=%g))"},
{ldap_groupdesc, "description"},
{ldap_member_selection_mode, group_children},
{ldap_ufilter, "(&(objectClass=inetOrgPerson)(cn=%u))"},
{ldap_userdesc, "displayName"},
{ldap_auth_check, off}
%%{ldap_useruid, "cn=%u"}
]},

в логах ежика никаких ошибок нет, вообще никаких рапортов.

структура ldap http://s1.ipicture.ru/uploads/20110818/TB1JrU45.png

ткните пальцем, совсем голову сломал

Неадекватный вопрос. Вообще

Неадекватный вопрос. Вообще непонятно, что написано.

А если по существу, то во-первых, Вы не разобрались в работе модуля, что видно из строки "* {ldap_gfilter, ""},{ldap_ufilter, ""},{ldap_filter, ""} -дабы не мешали кучей вложенных and", а во-вторых, оригинальный модуль вообще не способен на то, что Вы хотите, поэтому и идёт доработка. А что Вас не устраивает в доработанной (раз уж она лишь немного вменяема)?

не разобрался. не знаю какую

не разобрался. не знаю какую именно версию модуля описывает документация, идущая с программой, но явно не ту, что в самой программе. не понимаю отличие между rfilter и gfilter, в реальности <есть ростер(один)>, [есть группы в ростере], [есть члены этих группы]. единственный пришедший в голову вариант привязки rfilter к реальности- первичный(и единственный, обращающийся к лдап) фильтр отбора объектов для дальнейшей обработки, с тремя(!) последующими(локальными) фильтрами- на группы, на юзеров, общий(?). но выяснить как оно на самом деле не представлялось возможным из-за невменяемости модуля. к примеру, каким-то невероятным наложенияем багов в модуле из версии 2.1.8 удалось получить почти то, что хотелось:
{ldap_rfilter,"(objectClass=group)"},
{ldap_gfilter,""},
{ldap_ufilter,""},
{ldap_filter,""},
{ldap_groupattr,"distinguishedName"},
{ldap_groupdesc,"name"},
{ldap_memberattr,"member"},
{ldap_userdesc,"userPrincipalName"}
однако в этом случае userdesc делал не то, что ему приписывалось, и не то, что он делал в других случаях(!), последняя надежда на memberattr_format_re не оправдалась, в божеский вид привести не удалось.
не устраивает существующая концепция, приводящая к большому числу запросов к лдап и, опять-таки, непонятному предназначению rfilter; не устраивает ман, должным образом не раскрывающий эту концепцию. вот, на третьего числа версии модуля, почти(ну да бог с этими OU) добился желаемого:
{ldap_rfilter,"(objectClass=group)"},
{ldap_gfilter,"(&(objectClass=group)(distinguishedName=%g)(CN=All))"},
{ldap_ufilter,"(&(objectClass=user)(|(userAccountControl=512)(userAccountControl=66048))(distinguishedName=%u))"},
{ldap_filter,""},
{ldap_groupattr,"distinguishedName"},
{ldap_groupdesc,"name"},
{ldap_memberattr,"member"},
{ldap_userdesc,"userPrincipalName"},
{ldap_useruid,"sAMAccountName"}
я простой юзер и ничего не понимаю в вопросе, но мне кажется информации только из gfilter и ufilter достаточно чтобы двумя запросами построить два списка- групп и юзеров, а затем, на основе groupattr и memberattr, связать их друг с другом, а если использовать distinguishedName и шаблоны/регэкспы для groupattr и memberattr, то вообще будет.. универсально?

1. Положим, в Вашей ситуации

1. Положим, в Вашей ситуации двух запросов было бы достаточно.
Однако до универсальности решения далековато. Во-первых, AD - не единственный LDAP. Во-вторых, группировать пользователей можно по разным критериям. И запросы бывают трёх разных типов (самого объекта, одного уровня и всего поддерева). Так что если есть желание - попробуйте детально описать универсальную процедуру, как Вы её видите (не так, как Вы это делаете здесь - выбрасывая поток сознания, который Вам совершенно очевиден, но в котором стороннему очень трудно разобраться). Это не стёб, мне было бы очень полезно увидеть альтернативные подходы, а то отовсюду только вопросы, решать приходится только самому (можно хотя бы посмотреть мою переписку на странице https://support.process-one.net/browse/EJAB-1480).

2. Насчёт разных фильтров - я постарался детально описать их использование с примерами на вышеупомянутой странице. Насчёт OU - буквально накануне Вашего вопроса я как раз реализовал требующуюся Вам функциональность (именно в версии от 3 августа).

3. В старых версиях (и в т.ч. в той, которая идёт с программой начиная с версии 2.1.6) механизм построения запросов LDAP был достаточно запутанным, неочевидным и избыточно сложным. В частности, реальные фильтры строились из разных параметров (включая ldap_filter) по сложным правилам. Именно поэтому я упростил это в новой разрабатываемой версии. Однако документация была абсолютно адекватной, внимательное прочтение (хотя и требует большого напряжения) даёт всё необходимое (в рамках её возможностей).

Конкретно насчёт Вашего случая. Буду исходить из версии от 3 августа.
1. ldap_rfilter - это запрос, который выдаст список ссылок на группы. (Обратите внимание, что не всегда это - сами объекты групп, возможна ситуация, когда я хочу получить группы, являющиеся членами другой группы, а в данной LDAP нет атрибута типа memberOf - такое бывает.) Для Вас это - "(objectClass=organizationalUnit)", Вы правильно сделали вначале (хотя в той версии это было нереализуемо).
2. ldap_groupattr - это тот атрибут в полученном(ых) с помощью ldap_rfilter объекте(ах), который потом будет использоваться для поочерёдного обращения к каждой группе на последующих этапах. "distinguishedName" - достаточно адекватный выбор. Нужно обратить внимание, что в связи с тем, что мы получаем DN объекта, имеет смысл новый параметр ldap_group_is_dn выставить в true.

В результате использования первых двух параметров будет выполнен запрос на поиск объектов, удовлетворяющих условию "(objectClass=organizationalUnit)" во всём поддереве Вашего ldap_base (Вы его выставили, наверное, в разделе аутентификации). У полученных объектов будет запрошен атрибут "distinguishedName", и создан (неповторяющийся) список этих строк (т.е. список DN Ваших OU).

3. Как было указано, {ldap_group_is_dn, true} - значит, на этапе обращений к отдельным группам будем обращаться напрямую к объекту, чей DN мы получили рашьше. Иначе нам нужно было бы производить поиск объекта с заданным значением атрибута, что менее эффективно (и, кстати, не во всех LDAP есть атрибут типа distinguishedName).
4. Мы можем не устанавливать ldap_gfilter, т.к. по умолчанию он станет "(objectClass=*)", т.е. эквивалентно "выбрать всё". Однако для запросов к LDAP всегда нужен корректный фильтр, поэтому нельзя выставлять этот параметр в "".
5. Теперь нам нужно спросить каждую группу, как её название и какие у неё пользователи. Название определяется параметром ldap_groupdesc. Вы можете воспользоваться, например, параметрами ou или name (как Вы вначале и сделали).
6. Насчёт пользователей. Поскольку у пользователя нет атрибута, обозначающего принадлежность к ou, и у ou также нет такого атрибута, придётся воспользоваться параметром ldap_member_selection_mode, введённым в последней версии: {ldap_member_selection_mode, group_children} - это заставит систему искать пользователей как подобъекты объекта "группа". (Должен предупредить, Вы будете первым испытателем этой возможности.) При этом ldap_memberattr не нужен.
7. Ну, и теперь проход по пользователям для определения их имени и пользовательской части jid. Будут выбираться подобъекты объекта-группы (т.е. в данном случае - подобъекты OU). В фильтре ldap_ufilter нужно только указать класс подобъектов объектов и, возможно, дополнительные параметры. Часто упоминаемый на странице разработки фильтр "(&(objectClass=user)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))" здесь чудесно подойдёт. То есть, будут выбраны все объекты в поддереве данного OU, которые пользователи и которые не отключены.
8. У них нас интересуют: ldap_useruid для определения UID (пойдёт "sAMAccountName") и ldap_userdesc для его видимого имени (прекрасно работает "displayName"). В этом случае можно не определять ldap_useruid_format, т.к. он по умолчанию - "%u", т.е. весь sAMAccountName пойдёт в UID.

В новых версиях параметр ldap_filter вообще не используется. Он не нужен, и выставлять его в "" тоже не нужно.

Насчёт отсутствия у пользователя атрибута, определяющего OU, в котором он находится. Дело в том, что атрибут с типом значения "distinguished name" по стандарту LDAP нельзя использовать для поиска типа "ou=*,dc=example,dc=com". Так что этот параметр нам не поможет.

спасибо за объяснение

спасибо за объяснение алгоритма. OU работает, даже выдёргивает юзеров из вложенных OU.
невозможность делать поиск по distinguishedName неприятна, не знал об этом ограничении, но что если вовсе отказаться от помощи ldap?
делаем по ldap-запросу для получения списка групп и списка юзеров, выдёргивая как минимум по два атрибута- один для тега, другой для описания/отображаемого имени для дальнейшей локальной группировки. с лдап всё, объём и подбор данных целиком на совести админа, задающего ldap-запрос. уже локально применяем к этим атрибутам по шаблону, после оного оставшееся от атрибутов и будет тем тэгом, по которому будем определять принадлежность юзеров группам- опять на совести админа, задающего эти два шаблона. станут не нужны костыли типа group_is_dn, можно будет создавать феерические схемы, зависящие только от фантазии/кривизны рук ;)
например, для OU группы получим из запроса objectClass=organizationalUnit, юзеров из objectClass=user. тэгом для групп будет атрибут distinguishedName из объекта organizationalUnit, на который наложим пустой шаблон(т.е. не будем ничего вырезать/изменять). тэгом для юзеров будет тоже distinguishedName, но из объекта user, его зададим так, чтобы он отрезал слева все CN вместе с запятой. т.е. в итоге в этих двух списках будут совпадающие тэги. разве такой механизм не универсальнее, прозрачнее и.. безумнее? ^.^

:) Заманчивое предложение. И,

:) Заманчивое предложение. И, к сожалению, не универсальное.

Вы почему-то ограничились прямым отображением "Объект LDAP -> объект XMPP". Кроме того, Вы исходите из сравнительно небольшого домена. И ещё к тому же всё время думаете в рамках AD.
Во-первых, как группы, так и контакты XMPP могут не быть отдельными объектами в LDAP, это могут быть атрибуты одних и тех же объектов. Во-вторых, вариант с тотальным выбором, скажем, всех пользователей домена может создать значительный трафик и нагрузку на дальнейшую обработку списка, тогда как выбор только нужных пользователей на основе предварительно проведённой фильтрации может значительно сократить эти расходы - всё это в предположении, что в больших системах в общий ростер войдут далеко не все (хотя при малых размерах домена, наоборот, этот алгоритм приводит к дополнительным расходам). В третьих, ну как Вы реализуете Ваш вариант (я имею ввиду OU и его подобъекты) при отсутствии атрибута типа distinguishedName? А в OpenLDAP (и в других тоже) это сплошь и рядом. В четвёртых, если Вы почитаете стандарт LDAP, Вы увидите, что distinguished name - это достаточно сложный конструкт, состоящий из RDN, каждый из которых лишь в наиболее примитивном случае имеет привычный вид "attribute=value", а может иметь произвольное количество разных утверждений об атрибутах в любой последовательности, а для принятия решения об эквивалентности dn требуется полное совпадение каждого rdn по всем утверждениям, но не обязательно - по порядку. Далее, вариант с шаблонами создаёт дополнительную вычислительную нагрузку на ejabberd. Опять-таки, трудно будет сделать схемы с одним пользователем во многих группах (хотя это не очень актуально, учитывая, что большинство клиентов (если не все) эту возможность не поддерживают). Честно говоря, пока не вижу плюсов. Что касается феерических схем - не могу пока представить схему, которая бы была осмысленна и при этом не была бы реализуема при существующем варианте.

Два замечания.
1. Вам, вероятно, не понравилась исходная версия. Это понятно, но не стоит судить об обновлённой версии по старой. Попробуйте забыть о своём отрицательном опыте, и попробуйте просто подумать о новой конфигурации - без хитро конструируемых самой системой по непонятным законам фильтров. Это должно быть гораздо логичнее - для этого и затевалось.
2. Я также думал (и думаю) о варианте отбора не сверху вниз - от групп к пользователям - а наоборот, от пользователей к группам. Но вот реального варианта, который был бы достаточно гибким, универсальным и эффективным, придумать не могу.

запрашиваю у лдап поиск

запрашиваю у лдап поиск объектов с заданным признаком и получаю обратно какой-либо атрибут найденных объектов.. так может Объект LDAP -> объект XMPP это не моя вина? ;)
AD- поскольку ничего другого под рукой нет, и, как всякому простому пользователю, мне лень строить виртуальный мир под данный вопрос.. стыдно, но лень сильнее ^.^
могут быть даже одним атрибутом одного объекта ;) у нас два запроса(чего угодно), два шаблона(каких угодно), т.е. можем получить даже два идентичных списка- любые феерические схемы, даже не имеющие практического значения %).
ответственность за критерии выбора юзеров только на админе и составленном им запросе, т.е. использование того же department, как критерия фильтрации, не отменяется ;)
сократить- возможно, но намного ли? поскольку в существующей схеме, кроме получения самих групп, идёт ещё обход этих групп- дополнительные запросы.
тип атрибута не важен, важно только его значение, только корреляция информации между теми объектами, что выбираем в качестве групп, и тех, что выбираем в качестве юзеров. для OU в AD distinguishedName является единственной логической корреляцией при незаполненных прочих атрибутах, но можем заполнить атрибут описания и плясать от него, нет разницы.
нам не нужно полное совпадение, нам вообще ничего не нужно, поскольку всё это перекладывается на админа, на его запросы, на его шаблоны, на его голову, остаётся только дать ему для этого инструменты, универсальные и прозрачные ;)
оценить нагрузку не могу.
да, если тэги сравнивать строго- то да, множественное вхождение реализовать видимо можно будет только через шаблон для групп, генерирующий одинаковый тэг у двух и более групп, но можно ведь сравнивать не строго, а использовав регэкспы, в которые включить частью(переменная) тэг группы при сравнении с тэгами юзеров(или наоборот)- получим нестрогое соответствие.

ещё раз:
1. не призываю от чего-либо отказываться, кроме одного- отказаться от попытки всё сделать руками лдап. по той причине, что возможностей лдап для этого недостаточно.
это влечёт меньше запросов(не нужно обходить группы), но больше входящего трафика(из критериев отбора юзеров исключается информация из этапа отбора групп). возможно останемся при своих.
2. предполагаю, что между объектами, выбранными для групп и для юзеров существует какая-то корреляция, совпадающая часть в произвольных местах различных атрибутов- в худшем случае, иначе даже господь бог не поможет нам сопоставить юзера группе. если есть такая корреляция, то её можно вычленить из атрибута по шаблону(отдельные шаблоны для юзера и группы). почему надо вычленять- потому, что мы не можем сравнивать напрямую атрибуты, выбранные под тэги, из-за худшего случая.
3. дабы реализовать возможность множественного вхождения юзера в группы, сравнивать тэги c произвольной точностью.
4. плюсы: частично отказавшись от услуг лдап, получаем ограниченные только функционалом шаблонов и регэкспов возможности для поиска и сравнения- вхождения юзеров в группы; избавляемся от любых потенциальных костылей(типа group_is_dn) частных случаев, а без костылей получаем прозрачность всех этапов
5. можно воспользоваться какими-то чужими наработками, реализующие функционал awk/sed и регэкспов.

зы: версия из 2.1.6-8 не понравилась, хотя тут скорее дело в отсутствии подробного описания алгоритма работы, который есть сейчас, ведь когда знаешь что происходит под капотом, то с этим можно не согласиться, но когда же это чёрный ящик- в этом случае бесит. видимо я не уникален в данном плане ^.^
и да, в предлагаемой схеме не нужно выбирать от групп к пользователям отбор делать или от юзеров к группам ;))

Насчёт предлагаемого

Насчёт предлагаемого алгоритма:

1. Мне не очень нравится существующий (реализованный) алгоритм, поскольку он довольно сложен в конфигурировании (однако проще оригинального). Однако предлагаемый Вами нравится ещё меньше. Это говорится не для того, чтобы просто отмахнуться от необходимости что-то переделывать, а для того, чтобы Вы попробовали подумать над другими путями, возможно, видоизменив Ваш вариант.

2. Для конкретизации возражений приведу конкретные примеры.

- Отсутствует параметр типа distinguishedName и отсутствует возможность лазить в домен задавать произвольные значения других параметров (в крупных организациях сплошь и рядом админ домена и админ системы мгновенных сообщений - разные люди, я сам много раз с этим встречался). Требуется построить ростер по Вашему типу (контейнер-группа и его содержимое - пользователи).
- Имеются группы LDAP, в которые входят пользователи. Требуется добиться, чтобы пользователь входил во все группы XMPP, соответствующие группам LDAP (т.е. множественное вхождение) при условии, что нет поля типа memberOf, а есть поле типа member у группы.
- Имеется объект с dn типа "cn=John Smith,containerName=\\ABC+containerType=XYZ,dc=example,dc=com". Имеется dn контейнера: "containerType=XYZ + containerName=\5САBC, dc=example, dc=com". Как определить, является ли объект членом контейнера?

Теперь формализация возражений.
- "отказаться от попытки всё сделать руками лдап. по той причине, что возможностей лдап для этого недостаточно" - необоснованное утверждение. То, что с помощью данного механизма может потребоваться (в некоторых случаях) большее число шагов - не значит, что это вообще невозможно.
- LDAP разрабатывался для структурированного представления данных о директориях объектов. Общий ростер на основе данных директории должен по определению мочь быть построенным на основе только данных LDAP (за исключением, возможно, только финальных преобразований, специфичных для построения JID - поскольку это собственная концепция XMPP).
- Отход от этого принципа, хотя и позволяет многое, но при этом, во-первых, практически разрывает связь с LDAP, поскольку тогда уже это хранилище данных не рассматривается как структурированное, а просто набор строк; во-вторых, требует практически дублирования механизмов LDAP на стороне ejabberd (хотя бы в части разбора/сравнения строк dn), причём в случае конкретного LDAP он знает, что использует только определённое подмножество стандарта, а вот ejabberd себе такого позволить не сможет - ему придётся реализовать все возможности стандарта, чтобы мочь работать с любым правильным сервером; в-третьих, это усложнить отладку конфигурации, поскольку мультивариантность обработки данных может приводить к неожиданным совпадениям/несовпадениям...
- ldap_group_is_dn - это не совсем "костыли": в случае AD Вы вообще можете этим не пользоваться, а пользоваться поиском по атрибуту. Это - скорее, возможность оптимизации и учёта специфических возможностей используемого механизма (в данном случае - LDAP).

Всё же приведите примеры ситуаций, когда может не хватить имеющегося функционала.
А вот над другой реализацией механизма на основе именно LDAP подумать стоит.

Извиняюсь, что вмешиваюсь, но

Извиняюсь, что вмешиваюсь, но мне вообще не нравится концепция построения модуля.
В нем одновременно решаются задачи получения, форматирования и отображения данных.
Также частично дублируется функционал уже существующего модуля (mod_shared_roster)

Считаю, что модуль должен быть разрезан на две части:
1 - коннектор для загрузки в базу информации о пользователях
2 - ростер, отвечающий только за отображение информации из заранее сформированного хранилища (т.е. практически унификация существующего shared rostera).

Функционал по загрузке данных должен быть выброшен в коннектор, который отвечает за частоту и состав обновления из указанного источника (необязательно LDAP)
В коннекторе предусмотреть возможность постобработки выбранных данных.
Т.е. предлагается такая схема работы
- коннектор вытягивает в базу данные, ограниченные простейшими правилами (недостаток - объем трафика, компенсируется кэшированием в базе сервера).
- коннектор на этапе постобработки форматирует данные при помощи механизмов работы с БД сервера (этим снижаются накладные расходы и повышается гибкость).
- коннектор удаляет записи, у которых истек срок существования
- коннектор оповещает модуль ростера, что данные были обновлены.
- коннектор переходит в режим ожидания

Считаю что такая схема организации ростера позволит
- упростить разработку
- создать универсальный механизм получения данных для ростера
- сделать представление данных в ростере более гибким

freebeer wrote: Извиняюсь,

freebeer wrote:

Извиняюсь, что вмешиваюсь

Добро пожаловать :)

Quote:

но мне вообще не нравится концепция построения модуля.
В нем одновременно решаются задачи получения, форматирования и отображения данных.

Насчёт форматирования - посмотрите мой вопрос, заданный 13 августа на эту тему. Насчёт остальных задач - напишу ниже.

Quote:

Также частично дублируется функционал уже существующего модуля (mod_shared_roster)

Какой?

Quote:

Считаю, что модуль должен быть разрезан на две части:
1 - коннектор для загрузки в базу информации о пользователях
2 - ростер, отвечающий только за отображение информации из заранее сформированного хранилища (т.е. практически унификация существующего shared rostera).

Функционал по загрузке данных должен быть выброшен в коннектор, который отвечает за частоту и состав обновления из указанного источника (необязательно LDAP)
В коннекторе предусмотреть возможность постобработки выбранных данных.
Т.е. предлагается такая схема работы
- коннектор вытягивает в базу данные, ограниченные простейшими правилами (недостаток - объем трафика, компенсируется кэшированием в базе сервера).
- коннектор на этапе постобработки форматирует данные при помощи механизмов работы с БД сервера (этим снижаются накладные расходы и повышается гибкость).
- коннектор удаляет записи, у которых истек срок существования
- коннектор оповещает модуль ростера, что данные были обновлены.
- коннектор переходит в режим ожидания

Считаю что такая схема организации ростера позволит
- упростить разработку
- создать универсальный механизм получения данных для ростера
- сделать представление данных в ростере более гибким

Porridge уже пытался ответить Вам. Но его ответ, по-видимому, Вас не убедил.
Попробую я :)

Вы неправильно представляете себе архитектуру этого модуля (а также mod_shared_roster и в целом ejabberd). Вообще-то как раз предлагаемая Вами архитектура и реализована.

Модуль, обозначенный Вами как "ростер" (пункт 2) - это mod_roster, который занимется (унифицированным) сбором ростера из разных источников, чтобы потом передать его клиенту.

Модуль-коннектор - это и есть один из модулей mod_shared_roster(_ldap). Этот модуль вызывается модулем mod_roster в момент, когда идёт формирование ростера пользователя, и mod_roster уже подготовил персональный список для данного пользователя. Задачей mod_shared_roster(_ldap) является соединение с "базой" для получения административно заданного дополнительного списка для данного (обратите на это внимание) пользователя. Причём разработчики ejabberd уже проделали большую работу по унификации получения данных - насколько это было возможно: если Вы посмотрите на структуру модулей версии 3, там Вы не найдёте mod_roster_odbc или mod_vcard_odbc: те хранилища, которые были концептуально сходны, были вынесены в слой подключаемых хранилищ. LDAP принципиально отличается от остальных back-ends, поддерживаемых ejabberd: здесь схема данных не определяется ejabberd; эта база только для чтения; эта база обычно занята более важными делами, чем обслуживание системы обмена мгновенными сообщениями; её использование практически гарантированно говорит о корпоративном использовании ejabberd. Поэтому попытка сключить её в общую схему привела бы к ненужному усложнению кода, призванного эффективно работать с "обычными" базами (Mnesia/ODBC), при этом не давая никакой выгоды.

Попытка требовать от любого модуля, наполняющего ростер, передавать всю необходимую информацию в модуль ростера привела бы фактически к необходимости хранить для каждого пользователя ejabberd свой дополнительный "общий ростер". Причина здесь в том, что процедура запроса общего ростера адресная - т.е. производится для каждого пользователя индивидуально. Технически возможно реализовать такую схему "общих" ростеров, чтобы у каждого он был свой. Так что, прикажете для (возможно) сотен тысяч пользователей хранить дополнительно в кэше списки из (возможно) сотен контактов?

При этом для "обычных" баз это не приведёт ни к каким преимуществам: они обычно имеют быстрые каналы связи с ejabberd, и их собственные методы кэширования гораздо лучше справятся с задачей оптимизации времени доступа. Для таких систем (а их - большинство) предложенная Вами схема с дополнительным уровнем кэширования лишь увеличит требования к памяти/CPU и ухудшит их работу.

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

Теперь про коннектор LDAP. Он как раз и реализует Ваши идеи. Он вообще работает по принципу, отличному от mod_shared_roster. Заявленное Вами "дублирование" функционала mod_shared_roster - вообще нонсенс: если Вы посмотрите исходный код этих модулей, Вы не найдёте ни одной функции, которая была бы идентичной для обоих модулей. Они все крайне специфичны и оптимизированы для выполняемых ими (разных для каждого модуля!) задач.

Кэширование в этом модуле полезно и эффективно: во-первых, здесь наполнение общего ростера гораздо более унифицировано для всех пользователей, чем в обычном mod_shared_roster, а во-вторых, нужно учитывать, что серьёзные админы доменов обычно не сильно радуются дополнительной нагрузке на свои контроллеры со стороны говорилок (естественно, имеются ввиду серьёзные и часто распределённые, в т.ч. географически, организации).
И в этой связи - замечание относительно "задач форматирования и отображения данных". Задача форматирования данных могла бы решаться аналогично тому, как это сделано в mod_shared_roster - т.е. с использованием специально предназначенного для этих целей mod_vcard(_ldap) (и да, это можно было бы тогда унифицировать) (см. мой вопрос, ссылку на который я дал выше). Но тогда, как я там написал, это приведёт к многократно растущей нагрузке на базу (что допустимо для "обычных" баз, но в случае LDAP сведёт на нет любые механизмы оптимизации, в т.ч. предлагаемые Вами). Так что можете рассматривать эту конкретную особенность данного модуля специфической оптимизацией, реализованной с использованием знания особенностей механизма работы с БД сервера.
А вот насчёт задачи отображения данных - это Вы, извините, путаете mod_shared_roster_ldap и программу-клиент :)

Так что я совершенно не согласен с тем, что есть необходимость в предлагаемых Вами изменениях. Повторюсь:
- mod_roster при формировании ростера пользователя унифицированно просит модули-коннекторы добавить в полусформированный ростер дополнительные записи;
- коннекторы индивидуально для каждого пользователя формируют дополнительный список;
- существует два вида коннекторов: "общего назначения", являющийся "универсальным механизмом получения данных для ростера", работающий по своей схеме и имеющий свои настройки, и специальный коннектор, заточенный под LDAP (очень сильно отличающийся по логике).

С учётом, что в работе этих коннекторов вообще ничего общего, не знаю, как можно было бы "упростить разработку" или "сделать представление данных в ростере более гибким".

Благодарю за подробный

Благодарю за подробный ответ!

С архитектурой действительно вглубь не лез.

Идея, которую пытался изложить, сводится к следующему:
- Вызовы LDAP дорогие. Язык построения запросов достаточно специфичен.
Используемые поля записей в LDAP зависят от реализации.
- Вызовы к локальной базе - относительно дешевые. Язык запросов более гибок. Структура может быть унифицирована.
- Изменение параметров пользователей в корпоративных системах происходят нечасто.
При такой стоимости запросов проще один раз выгрузить данные в промежуточное хранилище
(локальную базу) и по мере необходимости дергать ее (локальную базу). Таким образом LDAP отображается на "обычную" базу и далее, используя Ваши слова:

они обычно имеют быстрые каналы связи с ejabberd, и их собственные методы кэширования гораздо лучше справятся с задачей оптимизации времени доступа.

- при этом возникает возможность влиять на изменение информации в момент загрузки ее в промежуточное хранилище (это делает специальный коннектор) и в момент формирования списка для пользователя (а это делает коннектор общего назначения). Если при этом прикрутить скриптовую логику пре/пост обработки, то получиться веселый механизм...:)

- по поводу тезиса

а это может случиться очень нескоро, так что, вероятнее всего, кэш вообще не будет использован.

мы видимо работаем на значительно разных по качеству линиях связи. :)

- по поводу тезиса

А вот насчёт задачи отображения данных - это Вы, извините, путаете mod_shared_roster_ldap и программу-клиент :)

не путаю - информация в сформированном списке задает параметры его отображения: если у нас в клетке мышь, то показать мы можем только мышь. Ракурсы, конечно, могут быть разными ... :)

На необходимости изменений не настаиваю. Модуль в существующем варианте весьма функционален и значительно облегчает нам жизнь, за что Вам большое спасибо!!!
Но всегда хочется чего-то еще...:D

Таким образом, в предлагаемом

Таким образом, в предлагаемом решении LDAP отображается на "обычную" базу и далее, используя Ваши слова:

они обычно имеют быстрые каналы связи с ejabberd, и их собственные методы кэширования гораздо лучше справятся с задачей оптимизации времени доступа.

- при этом возникает возможность влиять на изменение информации в момент загрузки ее в промежуточное хранилище (это делает специальный коннектор) и в момент формирования списка для пользователя (а это делает коннектор общего назначения). Если при этом прикрутить скриптовую логику пре/пост обработки, то получиться веселый механизм...:)

- по поводу тезиса

а это может случиться очень нескоро, так что, вероятнее всего, кэш вообще не будет использован.

мы видимо работаем на значительно разных по качеству линиях связи. :)

- по поводу тезиса

А вот насчёт задачи отображения данных - это Вы, извините, путаете mod_shared_roster_ldap и программу-клиент :)

не путаю - информация в сформированном списке задает параметры его отображения: если у нас в клетке мышь, то показать мы можем только мышь. Ракурсы, конечно, могут быть разными ... :)

На необходимости изменений не настаиваю. Модуль в существующем варианте весьма функционален и значительно облегчает нам жизнь, за что Вам большое спасибо!!!
Но всегда хочется чего-то еще...:D

Благодарю за подробный

Благодарю за подробный ответ!

С архитектурой действительно вглубь не лез.
Однако задача mod_shared_roster и mod_shared_roster_ldap одна - сформировать список дополнительных контактов по некоторым правилам. Оба модуля для этого используют некоторый механизм доступа к внешним данным. Считаю, что разумно попытаться максимально унифицировать используемые механизмы.

Идея, которую пытался изложить, сводится к следующему:
- Вызовы LDAP дорогие. Язык построения запросов достаточно специфичен.
Используемые поля записей в LDAP зависят от реализации.
- Вызовы к локальной базе - относительно дешевые. Язык запросов более гибок. Структура может быть унифицирована.
- Изменение параметров пользователей в корпоративных системах происходят нечасто.
При такой стоимости запросов проще один раз на достаточно большом промежутке времени выгрузить данные в промежуточное хранилище (локальную базу) и по мере необходимости дергать ее (локальную базу).

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

Таким образом, в предлагаемом решении LDAP отображается на "обычную" базу и далее, используя Ваши слова:
"они обычно имеют быстрые каналы связи с ejabberd, и их собственные методы кэширования гораздо лучше справятся с задачей оптимизации времени доступа."

- при этом возникает возможность влиять на изменение информации в момент загрузки ее в промежуточное хранилище (это делает специальный коннектор) и в момент формирования списка для пользователя (а это делает коннектор общего назначения). Если при этом прикрутить скриптовую логику пре/пост обработки, то получиться веселый механизм...:) Причем скриптовая логика постобработки в хранилищах на основе SQL серверов практически уже реализована,а стоимость обработки в локальной базе будет приемлемой даже при достаточно сложных алгоритмах.

- по поводу тезиса
"а это может случиться очень нескоро, так что, вероятнее всего, кэш вообще не будет использован."

мы с Вами, видимо, работаем на значительно разных по качеству линиях связи. :)

- по поводу тезиса

"А вот насчёт задачи отображения данных - это Вы, извините, путаете mod_shared_roster_ldap и программу-клиент :)"

не путаю - информация в сформированном списке задает параметры его отображения: если у нас в клетке мышь, то показать мы можем только мышь. Ракурсы, конечно, могут быть разными ... :)

На необходимости изменений не настаиваю. Модуль в существующем варианте весьма функционален и значительно облегчает нам жизнь, за что Вам большое спасибо!!!
Но всегда хочется чего-то еще...:D

Если подытожить, Вы

Если подытожить, Вы предлагаете сделать альтернативный интерфейс управления (наполнения) mod_shared_roster (в дополнение к единственному на данный момент веб-интерфейсу).

В принципе, почему бы и нет.
Для этого потребуется отобразить схему LDAP на схему БД (ну, конечно, только её малой части :)).
Можете попробовать, это было бы интересно.

Абсолютно верно. Но в связи с

Абсолютно верно.
Но в связи с тем, что web уже вшит mod_shared_roster, это войдет в противоречие с изложенной выше концепцией.
IMHO, для реализации необходимо вынести в отдельный модуль часть mod_shared_rosterа, которая только отдает список из локального хранилища пользователю.
Хотя может быть имеет смысл оставить возможность управления через web правилами отображения?

Плюс реализовать хелпер, который будет скрывать доступ к локальному хранилищу (организовывать его через API).
В хелпер можно также добавить вызовы для процедур пре/постобработки(это позволит осуществить действия с информацией в локальном хранилище перед и после загрузки,например вызов хранимок).

Дальше для каждого источника данных реализуется специфичный модуль (коннектор), который через вызовы хелпера заполняет структуры в локальном хранилище (в том числе и через web). Для каждого источника данных запускается свой экземпляр коннектора. Такая архитектура (если я не ошибаюсь :)) позволяет решить проблему заполнения ростера от LDAP-серверов в разных каталогах (и вообще от разнородных источников).

Вы говорили, что в третьей версии были сделаны шаги по выделению в слоя подключаемых хранилищ.
Если не затруднит, концептуально в чем это заключается?

В плане реализации, признаюсь честно, проблемы три
- очень плотный рабочий график
- отсутствие опыта разработки на erlang и парадигме erlang
- просто страшно :D, т.к. в проекте есть ядро разработчиков и roadmap, и влезать со своим самоваром...

так есть какая-нибудь

так есть какая-нибудь возможность сделать реальное отображение презента, а не вечный оффлайн? может это и не "по правилам"(встречал такую точку зрения), но лично убеждён, что куда правильнее оставлять админу выбор следовать правилам или нет, особенно актуально в данном случае.

up: ростер не обновляется при логофф-логон клиента на ejabberd, пока не пнёшь сам сервер. насколько помню прежде обновлялся

:) Вообще-то как раз презенс

:) Вообще-то как раз презенс должен работать. Но вот - иногда не работает. Я столкнулся с этим как раз когда люди начали пробовать новую версию.
У меня самого FreeBSD, и там принято ставить из исходников. У меня никаких проблем не было. А вот у людей, воспользовавшихся бинарниками с офсайта ProcessOne, постоянно имеются разные проблемы, в т.ч. и с презенсом. Именно об этом создан https://support.process-one.net/browse/EJAB-1487.

Короче, я практически уверен, что у Вас установка из бинарника. Прежде чем дёргаться дальше, проверьте, останется ли проблема при установке из исходников.

Только одно замечание. По умолчанию (если всё работает правильно) презенс работает только между людьми из общего ростера. Люди извне ростера, хотя и будут видеть ростер, но презенс не получат. Это сделано не по соображениям "правил", а с целью не перегружать сервер. Есть параметр "ldap_subscribe_all", который позволяет рассылать презенс людей из общего ростера всем пользователям сервера (но, естественно, только сервера - не дальше).
К сожалению, складывается впечатление, что Вы совершенно не читаете https://support.process-one.net/browse/EJAB-1480, и мне приходится для Вас повторять то, что я постарался подробно там описать. Это невежливо.

А насчёт алгоритма - я ещё подумаю. Хотя я всё равно считаю, что фактически дублировать функции LDAP в этом модуле неправильно. С другой стороны, Вы могли бы попробовать набросать такой вариант модуля - я могу помочь, но вот времени на полную разработку у меня нет.

да, бинарник. попробую(займёт

да, бинарник. попробую(займёт время, навалилось работы)
не со зла, не знал о существовании EJAB-1480. наткнулся при поиске актуальной версии, не прочитал дальше нескольких верхних каментов- виноват, исправился сразу, как только поиск повторно, уже на тему презенса, привёл туда же, но было поздно -.-

не осилил. 2.1.x собирал на

не осилил. 2.1.x собирал на 2010 экспрессе(ну где в наш атомный век найти 6 версию?). раз сурьёзно ругнулся в src\tls\Makefile.win32 на невозможность исользования несколько сорцов в -Fo, разбил(ну как сообразил) на пару:
..
SOURCE1 = sha_drv.c
OBJECT1 = sha_drv.o
SOURCE2 = tls_drv.c
OBJECT2 = tls_drv.o
DLL1 = ..\sha_drv.dll
DLL2 = ..\tls_drv.dll
ALL : $(DLL1) $(DLL2) $(BEAMS)
..
$(DLL1) : $(OBJECT1)
$(LD) $(LD_FLAGS) -out:$(DLL1) $(OBJECT1)
$(DLL2) : $(OBJECT2)
$(LD) $(LD_FLAGS) -out:$(DLL2) $(OBJECT2)
$(OBJECT1) : $(SOURCE1)
$(CC) $(CC_FLAGS) -c -Fo$(OBJECT1) $(SOURCE1)
$(OBJECT2) : $(SOURCE2)
$(CC) $(CC_FLAGS) -c -Fo$(OBJECT2) $(SOURCE2)
в итоге они собрались, но сам(а что вообще должно собраться? %)) нет. последнее, что делает:
..
c:/EJABBE~1/src/web/mod_http_fileserver.erl:442: Warning: call to httpd_util:to_lower/1 will fail, since it was removed in R12B; use string:to_lower/1
cd ..\odbc
nmake -nologo -f Makefile.win32
erlc -W -I .. -pz .. -o .. ejabberd_odbc.erl
c:/EJABBE~1/src/odbc/ejabberd_odbc.erl:32: Warning: behaviour p1_fsm undefined
erlc -W -I .. -pz .. -o .. ejabberd_odbc_sup.erl
erlc -W -I .. -pz .. -o .. -Dgeneric odbc_queries.erl
cd ..
build target does not exist
ALL target does not exist
и конечно werl -s ejabberd -name ejabberd крашится.
тема непопулярная..:\ может какие-нибудь хинты? ;)

Боюсь, я под виндой не

Боюсь, я под виндой не компилил...

Попробовал, вроде, компилится под экспрессом 2010 (configure.bat; nmake /f Makefile.win32), правда, после некоторых плясок с правкой Makefile.inc, который генерится cofigure, но, похоже, это не то... что-то там хреново. Возможно, нужно собирать под mingw.

Может быть, попробовать отловить проблему без пересборки? (Каюсь, да, это я предложил пересобрать - но я не знал, что это винда; под *nixами всё ок).

Пришлите подробные логи работы (из режима live, с уровнем логов 5, когда не отображается презенс) мне на почту. Попробуем так.

отправил на пару адресов,

отправил на пару адресов, найденных при прохождении квеста "найди мыло" ;) не факт, что они верные и дойдёт

Поскольку мой развёрнутый

Поскольку мой развёрнутый ответ был очередной раз отправлен на модерирование, просто скажу, что есть, и на странице разработки модуля (EJAB-1480) об этом написано. Когда ответ одобрят, можно будет его прочитать здесь, но флудить повторной отправкой не буду. И ещё на эту тему - EJAB-1487.

из личного опыта, может

из личного опыта, может пригодится кому:
так и не удалось полностью без косяков собрать 2.1.8 под вин, но проблему презенса можно обойти, если заменить эрланг, который ставится инсталятором, на более новую(манипуляции с оригинальной r12b4 не дают эффекта) версию(пробовал r14b01(рекомендована), r14b03(не рекомендована)- разницы не заметил). для этого из более свежей(erlang\bin, erlang\erts-<версия>\bin) версии заменяем в старой(ejabberd\bin) одинаковые по имени файлы(или тупо закидываем всё сразу), кроме erl.ini. затем в ejabberd\lib переносим erlang\lib, оставляя из старых только отсутствующие в эрланге. после этого презенс заработает, но не будет запускаться сервис- для решения оного нужно перекомпилировать(уже новым компилятором erlc) ejabberd\lib\win_service-1.0\src\win_service.erl, поместив win_service.beam в ejabberd\lib\win_service-1.0\ebin

зы: благодарность mikekaganski за объяснения и помощь

Благодарю за такое подробное

Благодарю за такое подробное описание, наконец-то прояснил для себя как это работает! А можете привести пример рабочего конфига для AD-совместимого LDAP-сервера? Я уже какие только варианты не пробовал - никак не могу добиться подгрузки списка юзеров, сгруппированного по группам на ejabberd 16.08. Список пользователей без групп получаю нормально, а вот с группами - ну прям никак.

Я использую Zentyal 4.1 Server который эмулирует работу AD на базе Samba LDAP.
Пользователи там почему-то имеют CN вида "CN=Фамилия Имя,CN=Users,DC=example,DC=com" (да-да, прям вот так с пробелом и по-русски) и в группах тоже именно так указываются в поле member.

Поэтому на этапе получения списка пользователей для группы я из этой конструкции не могу получить нормальный jabber id. Помогите пожалуйста подобрать рабочие параметры для получения юзеров группы!

Вот выдержка из мего конфига

Вот выдержка из мего конфига с учетом Ваших рекомендаций:

  mod_shared_roster_ldap:
    ldap_auth_check: off
    ldap_rfilter: "(&(objectClass=group)(objectClass=posixAccount))"
    ldap_groupattr: "distinguishedName"
    ldap_group_is_dn: true

    ldap_gfilter: "*"
#    ldap_memberattr: "member"
    ldap_groupdesc: "name"
    ldap_member_selection_mode: "group_children"

    ldap_ufilter: "(&(objectClass=user))"
    ldap_useruid: "sAMAccountName"
    ldap_userdesc: "displayName"

Судя по verbose-логу - список групп он успешно получает из LDAP, т.е. rfilter срабатывает нормально.

Вторым этапом он делает запрос для поиска членов группы, в данном случае не совсем понятно зачем Вы предлагаете ldap_memberattr='memeber' заменить на ldap_member_selection_mode: "group_children" - ведь как раз в поле 'memeber' у группы находится список CN пользователей в формате "CN=Фамилия Имя,CN=Users,DC=example,DC=com", как же ejabberd поймёт с какого поля брать список пользователей?

Если я раскомменчиваю строку ldap_memberattr: "member" то ejabberd вроде как получает список пользователей группы, но он видит что там какая-то фигня вместо jabber id и игнорит полученные записи, даже если я ставлю ldap_auth_check: off, т.к. по логам дальше не видно попыток выполнить ldap_ufilter.

Подскажите пожалуйста как быть в такой ситуации? Может быть к версии 16.08 что-то поменялось уже? В комментариях к багу https://support.process-one.net/browse/EJAB-1480 предлагают использовать также ldap_member_selection_mode: "memberattr_dn" но с ним тоже не работает.

Syndicate content