Заставляем IDM Midpoint работать с внешними заявками
IDM от Evolveum Midpoint обещает много, делает только то, что считает нужным. Крайне редкое и малоописанное решение, но в России его любят. СберТех построил на Midpoint свой Platform V IDM и не скрывает этого, по некоторым данным даже обновляет его до последней версии, но demo нет, так что не проверить. По слухам есть ещё две российские IDM, которые тоже выстроены на Midpoint, но на старых версиях, и они это не афишируют.
Evolvеum в своих презенташках упирает на то, что Midpoint всё может, а что не может во-первых не нужно, а во-вторых это в старых IDM так было, а мы новые — мы лучше! Так, например, отлично проработана выдача ролей, а вот их отъём в принципе нет, официальный ответ «Не думайте о том как отобрать роль, думайте как её не выдать». Midpoint работает с состояниями, забирает их в себя, хочет быть посредником (это прям в его названии), какие-то команды получать не хочет, хотя есть REST API и можно управлять ими командами, но для этого надо писать внешний сервис для внешних заявок — посредника для посредника!
Но всё равно работу в самом Midpoint c внешними заявками можно реализовать имеющимися инструментами.
Evolveum называет Midpoint Open Source проектом, по мне так он истерично коммерческий, хотя брать бесплатно пользоваться можно да, но есть нюансы:
Документация: docs.evolveum.com
Частично или нет. Худший пример — описание в разделе Midpoint\Features\Current Features\Parametric Role docs.evolveum.com/midpoint/features/current/parametric-role просто заголовок и пусто.
А единственное официальное (да и просто единственное) руководство заканчивается на самом интересном месте фразой «дайте денег, а то дальше писать не буду» docs.evolveum.com/book/practical-identity-management-with-midpoint.html#98-to-be-continued Автор ждёт с 22-го года…
Примеры: github.com/Evolveum/midpoint-samples
Частично или нет или очень старые
Поддержка сообщества:
Нет сообщества
Поддержка разработчиков: support.evolveum.com/projects/midpoint/
Только разработка новых функций и баги, отвечают только платным подписчикам. Типовые ответы: посылание в документацию, посылание к интеграторам, обещание исправить в следующих релизах.
Обучение: evolveum.com/services/training-and-certification/? target=training-courses-offer
Есть платные курсы, может там показывают настоящую документацию! Бесплатно есть несколько видео по некоторым функциям на www.youtube.com/@Evolveum/videos больше презентации, чем обучение.
Но Midpoint разгрызть можно и без нормальной документации и поддержки, что продемонстрирую ниже, с примерами и всем, чтобы можно было воспроизвести проверить.
Исходные данные: Midpoint 4.8.4 в версии 4.8.3 не работает, не основная, но нужная функция — фильтр в Resource в Schema Handler.
Упрощённая схема, чтобы понять что происходит
— Midpoint подключён к системам 1,2,3 через Resource. На входе в Resource стоит Connector решает вопросы как подключаться к системам и забирает данные и отдаёт их Sсhema Handler, который решает что и как делать с ними в Midpoint.
— Resource в результате синхронизации создаёт в Midpoint — 4 профиль user. 5 роль для выдачи AD группы, 6 роль WO заявку.
— При создании роль WO заявки в её атрибуты записывают данные заявки, ей назначается ArcheType (источник свойств, функционала), из которого назначается Object Template (источник обработки атрибутов). Также ей назначается роль WO Core, в которой прописана Policy Role, которая будет запускать скрипт.
— Скрипт запускается при любом событии с ролью WO заявка, если она не заблокирована. Скрипт ищет есть ли пользователь и роль. Если есть выполняет с ними действие из атрибутов роли WO заявка, в конце пишет комментарий в роль WO заявка и проставляет атрибут, что всё сделано (Object Template на этом основание блокирует роль WO заявку). Если же роли или пользователя нет, то он пишет в роль WO заявку, что есть ошибка, и она остаётся не сделанной, то есть не заблокированной, поэтому скрипт опять сработает при изменении в роли или при синхронизации ресурса.
Как это выглядит в Midpoint
Во вкладке Roles у нас имеется вкладка Work Order (где собрано всё с ArcheType, который мы назначили ролям WO заявкам).
Списком видны все роли WO заявки с информативным Description. Выполненные заявки заблокированы, так же они уже не участвуют в синхронизации с Resource и в системе помечено, что они выполнены (Resource фильтром берёт из системы только записи без пометки «исполнено»). А роли WO заявки с ERROR ждут редактирования админом, или может при следующей синхронизации такой пользователь или группа появится, тогда они сработают.
Можем зайти в пользователя из последней заявки в History и посмотреть что с ним делалось
Видим, что некто WO_administrator выдал assigned пользователю роль! Под WO_administrator запускается синхронизация в ресурсе.
Настройка
1. В примере участвуют дополнительные атрибуты, надо их добавить в Midopint
Кладём в /opt/midpoint/var/schema файл some.xsd с содержимым
true
DROLER OWNER
136
ToDo
true
DROLER ROLE
138
ToDo
Чтобы появились новые атрибуты Midpoint надо перезагрузить. Если у вас уже есть такой файл, то добавьте только то, что в тегах complexType
2. Создадим Object Template
Идём в CONFIGURATION\Object Templates и создаём Object Template под названием WO Object Template. Заходим в него. Всё, что касается Object Template хорошо прописано в GUI Midpoint и можно сделать его кнопками, но для наглядности (и скорости) полезем в RAW Code
Нажимаем Edit Raw
Перед
Вставляем
Block
strong
c:activation/c:administrativeStatus
afterAssignments
Если в роли WO заявки заполняется атрибут Documentation словом DONE, то роль деактивируется.
Вот так это же выглядит в GUI
3. Создаём архетип под роли WO заявки
Идём в CONFIGURATION\ArcheTypes и создаём ArcheTypes под названием WO ArcheType Заходим в него. Попробуем всё сделать кнопками. Заходим во вкладку Archetype Policy. Midpoint все незаполненные атрибуты прячет по умолчанию. Заполняем как на картинке
И в этом есть смысл в Object template reference вам надо ткнуть в свой WO Object Template, так как у него отличается OID и перенести его кодом с другого Midpoint не получится.
Да, совсем без залезания в code не получится, нам надо ещё сказать, что этот архетип относится к Role, кнопки для этого в Assigments нет, так что
Нажимаем Edit Raw
Перед (а Midpoint сам потом поставит куда ему нужно, вот только id сам поменять не сможет, если ругается на них или удалить или сменить номер)
Вставляем code
holderType
enabled
RoleType
4. Добавляем вьюху для ролей WO заявку
Тут всё просто, тыканьем удалось найти место, где это задаётся
CONFIGURATION\System\Admin GUI Configuration\Object collection view
Добавляем вьюху с названием WO VIEW
Тут нам подойдёт минимальное заполнение. А можно ещё например в Column расписать что хотим видеть, а вот в Display ничего писать нельзя, сразу ошибка 500.
Заполняем как на картинке
5. Роль WO Core
В ADMINISTRATION\Role\All Roles создаём просто роль (чёрную) под названием WO Core. Заходим в неё.
Нажимаем Edit Raw
Вставляем перед
следующий code
WO Core enigine
yes
some script
execute-script
script
import com.evolveum.midpoint.xml.ns._public.common.common_3.*
import com.evolveum.midpoint.prism.delta.builder.*
import com.evolveum.midpoint.model.api.*
import static com.evolveum.midpoint.schema.constants.SchemaConstants.C_ORG_TYPE
import javax.xml.namespace.QName
//get host role WO info
role = midpoint.getObject(RoleType.class, input.oid)
actionId = basic.stringify(role.costCenter)
userId = basic.stringify(basic.getExtensionPropertyValue(role, "http://example.com/xml/ns/mySchema", "DROLER_owner"))
roleId = basic.stringify(basic.getExtensionPropertyValue(role, "http://example.com/xml/ns/mySchema", "DROLER_role"))
//find and get user from role WO
query_user = midpoint.queryFor(UserType.class, "personalNumber = '$userId'")
result_USER = midpoint.searchObjects(query_user)
//find and get role from role WO
query_role_ass = midpoint.queryFor(RoleType.class, "identifier = '$roleId'")
result_ROLE_ASS = midpoint.searchObjects(query_role_ass)
//if role and user exists
if (result_USER && result_ROLE_ASS && basic.stringify(role.activation.administrativeStatus) != "DISABLED")
{
user_oid = basic.stringify(result_USER.oid)
user = midpoint.getObject(UserType.class, user_oid)
if (actionId == 'DELETE')
{
def assignmentsToDelete = []
for (a in user.assignment) {
if (a.targetRef?.oid == basic.stringify(result_ROLE_ASS.oid)) {
def removeAssignment = new AssignmentType()
removeAssignment.id = a.id
assignmentsToDelete.add removeAssignment.asPrismContainerValue()
}
}
if (!assignmentsToDelete.empty) {
//log.info "Assignments to delete: " + assignmentsToDelete
delta = prismContext.deltaFor(UserType.class).item(UserType.F_ASSIGNMENT).delete(assignmentsToDelete).asObjectDelta(user.oid)
//log.info "Deleting"
midpoint.modifyObject(delta, ModelExecuteOptions.createRaw())
roleDescription = 'OK: Deleted role identfier:' + roleId + ' from user personalNumber:' + userId
delta = prismContext.deltaFor(RoleType.class).item(RoleType.F_DESCRIPTION).replace(roleDescription).asObjectDelta(input.oid)
midpoint.modifyObject(delta, ModelExecuteOptions.createRaw())
roleDescription = 'DONE'
delta = prismContext.deltaFor(RoleType.class).item(RoleType.F_DOCUMENTATION).replace(roleDescription).asObjectDelta(input.oid)
midpoint.modifyObject(delta, null)
} else {
roleDescription = 'WARNING: User with personalNumber:' + userId + ' did not have assigned role with identfier:' + roleId
delta = prismContext.deltaFor(RoleType.class).item(RoleType.F_DESCRIPTION).replace(roleDescription).asObjectDelta(input.oid)
midpoint.modifyObject(delta, ModelExecuteOptions.createRaw())
roleDescription = 'DONE'
delta = prismContext.deltaFor(RoleType.class).item(RoleType.F_DOCUMENTATION).replace(roleDescription).asObjectDelta(input.oid)
midpoint.modifyObject(delta, null)
}
}
if (actionId == 'ADD')
{
orgUnit = new ObjectReferenceType()
orgUnit.setOid(result_ROLE_ASS.oid)
orgUnit.setType(RoleType.COMPLEX_TYPE)
addAssignment = new AssignmentType()
addAssignment.setTargetRef(orgUnit)
def delta = []
delta = prismContext.deltaFor(UserType.class).item(FocusType.F_ASSIGNMENT).add(addAssignment.asPrismContainerValue()).asObjectDelta(user.oid)
midpoint.modifyObject(delta, ModelExecuteOptions.createRaw())
roleDescription = 'OK: Added role identfier:' + roleId + ' to user personalNumber:' + userId
delta = prismContext.deltaFor(RoleType.class).item(RoleType.F_DESCRIPTION).replace(roleDescription).asObjectDelta(input.oid)
midpoint.modifyObject(delta, ModelExecuteOptions.createRaw())
roleDescription = 'DONE'
delta = prismContext.deltaFor(RoleType.class).item(RoleType.F_DOCUMENTATION).replace(roleDescription).asObjectDelta(input.oid)
midpoint.modifyObject(delta, null)
}
} else {
//if role or user does not exists
if (!result_USER && result_ROLE_ASS && basic.stringify(role.activation.administrativeStatus) != "DISABLED")
{
roleDescription = 'ERROR: User personalNumber:' + userId + ' not present in Midpoint!'
owdelta = prismContext.deltaFor(RoleType.class).item(RoleType.F_DESCRIPTION).add(roleDescription).asObjectDelta(input.oid)
midpoint.modifyObject(owdelta, ModelExecuteOptions.createRaw())}
if (result_USER && !result_ROLE_ASS && basic.stringify(role.activation.administrativeStatus) != "DISABLED")
{roleDescription = 'ERROR: Role identfier:' + roleId + ' not present in Midpoint!'
owdelta = prismContext.deltaFor(RoleType.class).item(RoleType.F_DESCRIPTION).replace(roleDescription).asObjectDelta(input.oid)
midpoint.modifyObject(owdelta, ModelExecuteOptions.createRaw())}
if (!result_USER && !result_ROLE_ASS && basic.stringify(role.activation.administrativeStatus) != "DISABLED")
{roleDescription = 'ERROR: Role identfier:' + roleId + ' and User personalNumber:' + userId + ' not present in Midpoint!'
owdelta = prismContext.deltaFor(RoleType.class).item(RoleType.F_DESCRIPTION).replace(roleDescription).asObjectDelta(input.oid)
midpoint.modifyObject(owdelta, ModelExecuteOptions.createRaw())}
}
policyConstraints — прописано, что скрипт запускается всегда но
в condition — прописано, что назначение этой Policy Rule активно только если роль WO заявка не заблокирована
6. Настраиваем Resource
Нам нужен файл данных для симуляции системы заявок WO
В папку /opt/midpoint/var/ кладем файл wo.csv с содержимым
wo_num;wo_user;wo_role;wo_action;wo_satus
1;47128562bad;ROLE2144255;ADD;todo
2;47128562;ROLE2144255;ADD;todo
3;47128562;ROLE2144255;DELETE;todo
4;47128562;ROLE2144255;DELETE;todo
wo_num — уникальный номер заявки в системе WO
wo_user — это номер сотрудника в Midpoint в атрибуте personalNumber
wo_role — это номер роли в Midpoint в атрибуте identifier
wo_action — это действие или ADD или DELETE
wo_satus — это статус если он не DONE Midpoint будет пытаться исполнить эту заявку
Создайте своих пользователей и роли и заполните соответствующие атрибуты
Создаём ресурс, идём в ADMINISTRATION\Resources\New Resorce\From Scratch выбираем CsvConnector
Далее как на картинках, пишем своё имя ресурса WO ticket system from CSV
Путь к файлу /opt/midpoint/var/wo.csv
Указываем уникальный идентификатор в системе wo_num Midpoint«у он очень нужен
И всё, далее далее
Заходим в созданный ресурс, нажимаем Edit Raw
и добавляем перед
следующий code
entitlement
intent WO
WO
true
ri:AccountObjectClass
attributes/wo_satus not contains 'DONE'
c:RoleType
ri:wo_num
01 inbound
strong
identifier
02 inbound
strong
name
03 add POLICY to WO
active
strong
c:RoleType
d489d8d8-b0e5-4e9e-a078-46e2be7f6de0
$focus/assignment
ri:wo_role
04 inbound
weak
extension/DROLER_role
ri:wo_user
05 inbound
weak
extension/DROLER_owner
ri:wo_action
06 inbound
weak
costCenter
ri:wo_satus
01 outbound
strong
-
c:identifier
unmatched
linked
unlinked
Это наш schemaHandling
— прописан filter, по которому мы берём записи у которых wo_status не DONE
— прописано, что мы будем делать роли и назначать им архетип, созданный ранее
— прописано, что Midpoint берёт и куда кладёт, он группирует маппинги по атрибуту, здесь могут быть и inbound и outbound
— по какому атрибуту происходит привязка
— и описано что делать при синхронизации, мы если у нас нет заявки её создаём:
unmatched
Всё это есть в GUI, посмотрим на Mappings, вот тут GUI не всё понимает
03 add POLICY to WO — тут захаркодено назначение всем ролям WO, заявкам роль с WO core, GUI этого не понимает. Прописан OID WO Core у вас будет другой, его можно найти в CONFIGURATION\Repository Object\All object\Выбрать type Role ну или покапаться в RAW в WO Core
03 add POLICY to WO
active
strong
c:RoleType
d489d8d8-b0e5-4e9e-a078-46e2be7f6de0
$focus/assignment
02 inbound — берёт номер из системы wo_num и кладёт в name в Midpoint. Ему главное чтобы этот namе уникален в Midpoint поэтому дописываем ему WO: и нулей через Script
"WO:" + input.padLeft(14,'0')
04 inbound, 05 inbound, 06 inbound — у этих mapping«ов если нажать справа на них, у мусорного ведра, карандашик с бумажкой, откроет окно с ещё настройками. Я не хочу, чтобы Resource мне всегда перезаписывал эти атрибуты как в себе, я планирую их редактировать в Midpoint поэтому Strength поставлен Weak, если они уже заполнены в роли WO заявки ресурс не будет их менять.
Outbound маппинги, которые пишут в систему WO через ресурс, прячутся тут же в правой вкладке
Я пишу то, что в Midpoint в documentation в атрибут в ресрусе wo_status, но не всегда, а когда в documentation слово DONE. Если нажать на карандашик там это прописано в Сondition
Практически всё, осталось настроить запуск синхронизации, она не прописана в RAW коде ресурса, так что пойдём по картинкам.
В ресурcе идём в Defined Task и нажимаем + и выбираем Reconcilation task
Заполняем как на картинке
Тут обратите, что я поставил в Owner пользователя WO_administrator, все заявки созданные при синхронизации будут от него. Можно оставить тут пусто, а можете создать тоже WO_administrator и дать ему роль Superuser (авторизация в Midpoint отдельная удивительная история).
Тут можете поставить interval будет запускать каждый цать секунд или Cron-like вида »5 5 21 * * ?» на 5 секунде 5 минуте в 21 часа.
Далее далее в конце нажимаем Save & Run
И это всё, вот так вот просто можно настроить Midpoint под свои legacy системы заявок!
В Roles\Work Orders вот такая картинка теперь — по тестовым данным!