Заставляем 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.


Упрощённая схема, чтобы понять что происходит

575acd882a686e17294316908a3d2a89.png

 — 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

5bb8a53edf677edfd9a99453f431b211.png

Во вкладке Roles у нас имеется вкладка Work Order (где собрано всё с ArcheType, который мы назначили ролям WO заявкам).

Списком видны все роли WO заявки с информативным Description. Выполненные заявки заблокированы, так же они уже не участвуют в синхронизации с Resource и в системе помечено, что они выполнены (Resource фильтром берёт из системы только записи без пометки «исполнено»). А роли WO заявки с ERROR ждут редактирования админом, или может при следующей синхронизации такой пользователь или группа появится, тогда они сработают.

Можем зайти в пользователя из последней заявки в History и посмотреть что с ним делалось

66695efdd7483e053de7b936eb8e874c.png

Видим, что некто 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

dafb1e2236fed6abf01147308a3f10e1.png

Перед 

Вставляем


  Block
  strong
  
    c:documentation
  
  
    
  
  
    c:activation/c:administrativeStatus
  
  
    
  
  afterAssignments

Если в роли WO заявки заполняется атрибут Documentation словом DONE, то роль деактивируется.

Вот так это же выглядит в GUI

23322552e38310236dd113678926938f.png

3. Создаём архетип под роли WO заявки

Идём в CONFIGURATION\ArcheTypes и создаём ArcheTypes под названием WO ArcheType Заходим в него. Попробуем всё сделать кнопками. Заходим во вкладку Archetype Policy. Midpoint все незаполненные атрибуты прячет по умолчанию. Заполняем как на картинке

009ccd9545bb0016d2483e955fa98d41.png

И в этом есть смысл в 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.

Заполняем как на картинке

f2f072b3fae2e3d24165015a0893879f.png

5. Роль WO Core

В ADMINISTRATION\Role\All Roles создаём просто роль (чёрную) под названием WO Core. Заходим в неё.

Нажимаем Edit Raw


Вставляем перед

следующий code


  
    WO Core enigine
    
      
        yes
      
    
    
      
        some script
        
          
            c:RoleType
          
        
        
          
            
              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

7d3f72302e6f838096347d29280d0567.png

Путь к файлу /opt/midpoint/var/wo.csv

bd5c727ad017abb060403d7ebb4bb748.png

Указываем уникальный идентификатор в системе wo_num Midpoint«у он очень нужен

899b63eb18b81512ae1cb05bc1eded97.png

И всё, далее далее

Заходим в созданный ресурс, нажимаем 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
                    
                        $focus/documentation
                    
                    
                        
                    
                
            
            
                
                    
                        
                            c:identifier
                        
                    
                
            
            
                
                    unmatched
                    
                        
                    
                
                
                    linked
                    
                        
                    
                
                
                    unlinked
                    
                        
                    
                
            
        
    

Это наш schemaHandling

 — прописан filter, по которому мы берём записи у которых wo_status не DONE

 — прописано, что мы будем делать роли и назначать им архетип, созданный ранее

 — прописано, что Midpoint берёт и куда кладёт, он группирует маппинги по атрибуту, здесь могут быть и inbound и outbound

 — по какому атрибуту происходит привязка

 — и описано что делать при синхронизации, мы если у нас нет заявки её создаём:

                
                    unmatched
                    
                        
                    
                


Всё это есть в GUI, посмотрим на Mappings, вот тут GUI не всё понимает

fc8dbedcad8bd6b306a9056ef5a769bc.png

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 через ресурс, прячутся тут же в правой вкладке

492c552c8bb6447358a4a4cd2db9d7d5.png

Я пишу то, что в Midpoint в documentation в атрибут в ресрусе wo_status, но не всегда, а когда в documentation слово DONE. Если нажать на карандашик там это прописано в Сondition

2a0cd89e7e8f5100fa79c027b605258c.png

Практически всё, осталось настроить запуск синхронизации, она не прописана в RAW коде ресурса, так что пойдём по картинкам.


В ресурcе идём в Defined Task и нажимаем + и выбираем Reconcilation task

Заполняем как на картинке

051ad6105e8641b1098fb63ebff9ecfb.png

Тут обратите, что я поставил в Owner пользователя WO_administrator, все заявки созданные при синхронизации будут от него. Можно оставить тут пусто, а можете создать тоже WO_administrator и дать ему роль Superuser (авторизация в Midpoint отдельная удивительная история).

7092d6d3b3cad2d5384a588a0e0a77c9.png51ff4222d743d1fe9d558c3eb42a5544.png

Тут можете поставить interval будет запускать каждый цать секунд или Cron-like вида »5 5 21 * * ?» на 5 секунде 5 минуте в 21 часа.

Далее далее в конце нажимаем Save & Run

И это всё, вот так вот просто можно настроить Midpoint под свои legacy системы заявок!

В Roles\Work Orders вот такая картинка теперь — по тестовым данным!

9972ed8c93a43143ed6a889996e23148.png

© Habrahabr.ru