По мере роста информационной системы (далее ИС) и количества ее пользователей в ней ощутимо увеличивается количество выданных разрешений. Усложнение ролевой модели, увеличение количества условий и объектов в ИС усложняет предоставление пользователям уникальных разрешений. Поэтому, в общей концепции анализа модели предоставления прав в ИС важную роль играет анализ предоставленных разрешений. Проведение этого анализа путем простой группировки правил и сравнения, например, косинусных расстояний векторов предоставленных разрешений, практически лишено смысла. Эти методы не дают репрезентативного результата, а косинусный анализ еще и сопряжен с высокой вычислительной нагрузкой, даже при использовании методов CountVectorizer и NearestNeighbors библиотеки sklearn. Однако, анализ возможен путем группировки сравниваемых правил по исходным правилами, пользовательским ролям, типам и состояниям карточек. Далее представлен подобный метод анализа на примере предоставленных разрешений в Tessa.
Выгрузка данных из БД Tessa¶
Далее представлен код MS SQL, предназначенный для выгрузки данных из базы данных (далее БД) Tessa, которые требуются для кластерного анализа модели предоставления прав в Tessa. Однако, в целях обеспечения безопасности эксплуатационных данных для разработки кластерного анализа предлагается использовать тестовые данные, сгенерированные с помощью генератора датафреймов, разработанного в Microsegment.ru.
-- Все правила доступа с русским переводом параметров
SELECT kp.ID AS [ID правила доступа],
kp.Caption AS [Название правила доступа],
kp.IsDisabled AS [Правило отключено],
kp.IsRequired AS [Всегда проверять правило],
kp.IsExtended AS [РПД],
kp.Conditions LIKE '%ConditionTypeID%' THEN 1 ELSE 0 END AS [Типы условий],
-- Разрешения из правил доступа
kp.CanCreateCard AS [Создание карточки],
kp.CanReadCard AS [Чтение карточки],
kp.CanEditCard AS [Редактирование карточки],
kp.CanEditFiles AS [Редактирование файлов],
kp.CanAddFiles AS [Добавление файлов],
kp.CanEditRoute AS [Редактирование маршрута],
kp.CanDeleteCard AS [Удаление карточки],
kp.CanStartProcess AS [Инициация типового процесса отправки задач],
kp.CanEditNumber AS [Ручное редактирование номера],
kp.CanCreateResolutions AS [Создание резолюций],
kp.CanDeleteFiles AS [Удаление всех файлов],
kp.CanEditOwnFiles AS [Редактирование собственных файлов].
kp.CanDeleteownFiles AS [Удаление собственных файлов],
kp.CansignFiles AS [Подписание файлов],
kp.CanAddTopics AS [Создание обсуждений].
kp.CanSuperModeratorMode AS [Права супермодератора],
kp.CanSubscribeForNotifications AS [Подписка на уведомления],
kp.CanCreateTemplateAndCopy AS [Создание шаблона и копирование],
kp.CanSkipStages AS [Пропуск этапов],
kp.CanFullRecalcRoute AS [Полный пересчет маршрута].
kp.CanEditMyMessages AS [Редактирование своих сообщений],
kp.CanEditAllMessages AS [Редактирование всех сообщений],
kp.CanReadAllTopics AS [Чтение обсуждений],
kp.CanReadAndSendMessageInAllTopics AS [Чтение и отправка сообщений]
FROM KrPermissions (NOLOCK) kp;
-- Обогащение правил доступа дополнительными данными о ролях
SELECT DISTINCT
-- Правила доступа
kpr.ID AS [ID правила доступа],
-- Роли
kpr.RoleID AS [ID роли],
kpr.RoleName AS [Название роли]
FROM krPermissionRoles (NOLOCK) kpr;
-- Обогащение правил доступа дополнительными данными о типах документов
SELECT DISTINCT
-- Правила доступа
kpt.ID AS [ID правила доступа],
-- Тип карточки
kpt.TypeCaption AS [ID ЛФО типа карточки]
dbo.Localize(kpt.TypeCaption, 25) AS [Тип карточки]
FROM KrPermissionTypes (NOLOCK) kpt;
-- Обогащение правил доступа дополнительными данными о состояниях документов
SELECT DISTINCT
-- Правила доступа
kpt.ID AS [ID правила доступа],
-- Состояние карточки
kps.StateName AS [ID ЛФО состояния карточки]
dbo.Localize(kps.StateName, 25) AS [Состояние карточки]
FROM KrPermissionStates (NOLOCK) kps
-- Расширенные правила доступа (РПД)
SELECT
DISTINCT
-- Правила доступа
kp.ID AS [ID правила доступа],
-- Параметры РПД
kpecr.SectionName AS [РПД. Секция карточки].
kpecrf.FieldName AS [РПД. Поле карточки],
kpecr.AccessSettingName AS [РПД. Разрешение],
dbo.Localize(kpecr.AccesssettingName, 25) AS [РПД. Разрешение (рус)]
FROM KrPermissions AS kp
LEFT JOIN KrPermissionExtendedcardRules (NOLOCK) kpecr ON kpecr.ID = kp. ID
LEFT JOIN KrPermissionExtendedcardRuleFields (NOLOCK) kpecrf ON kpecrf. ID = kp.ID;
-- Вхождение PersonalRoles в любые Roles, кроме ContextRoles
SELECT pr.ID AS [ID пользователя],
pr.Name AS [Имя пользователя],
pr.Position AS [Должность пользователя],
pr.AccessLevelID AS [Уровень доступа пользователя],
pr.LoginTypeID AS [Тип учетной записи пользователя],
pr.Login AS [Логин пользователя],
pr.Blocked AS [Блокировка учетной записи пользователя],
r.ID AS [ID роли],
r.Name AS [Название роли],
r.TypeID AS [Тип роли]
FROM Roles (NOLOCK)r
INNER JOIN RoleUsers (NOLOCK) ru ON ru.ID = r.ID
INNER JOIN PersonalRoles (NOLOCK) pr ON pr.ID - ru.UserID
WHERE (ru. IsDeputy = O OR ru.IsDeputy IS NULL)
ORDER BY pr.Name, r.Name;
-- ContextRoles
SELECT cr.ID AS [ID роли],
cr.SqlText AS [Код SQL]
FROM ContextRoles (NOLOCK) cr;
-- Для выявления вхождения идентификаторов ролей в коды контекстных ролей
-- (очень долгая операция, лучше реализовать локально на Python)
--LEFT JOIN Roles (NOLOCK) r ON cr.SqlText LIKE '%'+CONVERT(VARCHAR(50), .ID)+'%'
Загрузка библиотек¶
import pandas as pd
import numpy as np
# Работа с подстроками
import re
# Работа со временем
from datetime import datetime
import pytz
Загрузка данных¶
# Данные из таблицы KrPermissions, предварительно выгруженные из БД Tessa
kr_permissions = pd.read_csv('files/KrPermissions.csv').drop('Unnamed: 0', axis=1)#, sep=';', encoding='utf-8-sig')
print(kr_permissions.info())
kr_permissions.head(5)
<class 'pandas.core.frame.DataFrame'> RangeIndex: 209 entries, 0 to 208 Data columns (total 30 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 ID правила доступа 209 non-null object 1 Название правила доступа 209 non-null object 2 Правило отключено 209 non-null bool 3 Всегда проверять правило 209 non-null bool 4 РПД 209 non-null bool 5 Типы условий 209 non-null bool 6 Создание карточки 209 non-null bool 7 Чтение карточки 209 non-null bool 8 Редактирование карточки 209 non-null bool 9 Удаление карточки 209 non-null bool 10 Добавление файлов 209 non-null bool 11 Редактирование собственных файлов 209 non-null bool 12 Редактирование файлов 209 non-null bool 13 Удаление собственных файлов 209 non-null bool 14 Удаление всех файлов 209 non-null bool 15 Инициация типового процесса отправки задач 209 non-null bool 16 Редактирование маршрута 209 non-null bool 17 Пропуск этапов 209 non-null bool 18 Полный пересчет маршрута 209 non-null bool 19 Ручное редактирование номера 209 non-null bool 20 Создание резолюций 209 non-null bool 21 Подписание файлов 209 non-null bool 22 Создание обсуждений 209 non-null bool 23 Права супермодератора 209 non-null bool 24 Подписка на уведомления 209 non-null bool 25 Создание шаблона и копирование 209 non-null bool 26 Редактирование своих сообщений 209 non-null bool 27 Редактирование всех сообщений 209 non-null bool 28 Чтение обсуждений 209 non-null bool 29 Чтение и отправка сообщений 209 non-null bool dtypes: bool(28), object(2) memory usage: 9.1+ KB None
| ID правила доступа | Название правила доступа | Правило отключено | Всегда проверять правило | РПД | Типы условий | Создание карточки | Чтение карточки | Редактирование карточки | Удаление карточки | … | Создание резолюций | Подписание файлов | Создание обсуждений | Права супермодератора | Подписка на уведомления | Создание шаблона и копирование | Редактирование своих сообщений | Редактирование всех сообщений | Чтение обсуждений | Чтение и отправка сообщений | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 9c3612f2-42ad-4161-aff5-e40ae544a0c5 | Правило 0 | True | True | False | False | True | True | False | True | … | False | False | False | False | False | False | False | False | False | False |
| 1 | edcc653c-f93d-4b92-8aea-1219d670afe8 | Правило 1 | False | False | False | False | True | True | True | False | … | True | False | False | True | False | False | False | False | False | False |
| 2 | 48367274-4d95-49e9-a24a-ecef6ca99c4d | Правило 2 | True | False | False | False | True | True | True | False | … | False | True | False | False | False | False | False | False | False | False |
| 3 | 3b05d9bf-1def-49fb-bf8a-356cdb638267 | Правило 3 | True | False | True | False | False | True | True | False | … | False | False | False | False | False | False | False | False | False | False |
| 4 | 5533c7bb-854d-40a9-9882-9d01bdf9117b | Правило 4 | True | True | False | False | True | True | True | False | … | False | False | False | False | False | False | False | False | False | False |
5 rows × 30 columns
# Данные из таблицы KrPermissionRoles, предварительно выгруженные из БД Tessa
kr_permission_roles = pd.read_csv('files/KrPermissionRoles.csv').drop('Unnamed: 0', axis=1)#, sep=';', encoding='utf-8-sig')
print(kr_permission_roles.info())
kr_permission_roles.head(5)
<class 'pandas.core.frame.DataFrame'> RangeIndex: 897 entries, 0 to 896 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 ID правила доступа 897 non-null object 1 ID роли 897 non-null object 2 Название роли 897 non-null object dtypes: object(3) memory usage: 21.2+ KB None
| ID правила доступа | ID роли | Название роли | |
|---|---|---|---|
| 0 | 1512e0f2-fe6d-4e20-8b8b-1fbc0ae37ed3 | 6be734e0-7828-4eed-867d-b3964c07084a | Куликов И.К. |
| 1 | 3155720a-a0e1-414e-ad91-51355b943b9b | ed65640c-0b9b-444c-a592-b2e8a3be09c0 | Обработчик входящих документов |
| 2 | 3155720a-a0e1-414e-ad91-51355b943b9b | ed65640c-0b9b-444c-a592-b2e8a3be09c0 | Обработчик входящих документов |
| 3 | 3155720a-a0e1-414e-ad91-51355b943b9b | ed65640c-0b9b-444c-a592-b2e8a3be09c0 | Обработчик входящих документов |
| 4 | 3155720a-a0e1-414e-ad91-51355b943b9b | ed65640c-0b9b-444c-a592-b2e8a3be09c0 | Обработчик входящих документов |
# Данные из таблицы KrPermissionTypes, предварительно выгруженные из БД Tessa
kr_permission_types = pd.read_csv('files/KrPermissionTypes.csv').drop('Unnamed: 0', axis=1)#, sep=';', encoding='utf-8-sig')
print(kr_permission_types.info())
kr_permission_types.head(5)
<class 'pandas.core.frame.DataFrame'> RangeIndex: 263 entries, 0 to 262 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 ID правила доступа 263 non-null object 1 ID типа карточки 256 non-null object 2 Тип карточки 256 non-null object dtypes: object(3) memory usage: 6.3+ KB None
| ID правила доступа | ID типа карточки | Тип карточки | |
|---|---|---|---|
| 0 | 64c3bf3d-fa08-425c-93f1-9e3e73fa2d64 | f747e984-9dad-4d04-93d8-0508ad1419f5 | Служебная записка |
| 1 | be0f5a3e-d51c-45f7-b04a-32c0cbacc8f9 | a82f9ccc-d953-4797-bb77-ecde7e224160 | Дополнительное соглашение |
| 2 | 08622304-d149-4100-90bb-cfc916322c68 | ff3a9f22-41c9-4b92-a18f-6fc9ebbb26b7 | Договор |
| 3 | 8f2bd213-e68a-4c72-ada0-438455877fe8 | 6024cb6c-72eb-4f19-9b46-80e16d77eaf2 | МЧД |
| 4 | 5f64a5ac-0a18-4307-931f-310a6e5f8f8c | 15f0faf3-ddaa-4cf6-b34c-ad6a5c4010d0 | Исходящий |
# Данные из таблицы KrPermissionStates, предварительно выгруженные из БД Tessa
kr_permission_states = pd.read_csv('files/KrPermissionStates.csv').drop('Unnamed: 0', axis=1)#, sep=';', encoding='utf-8-sig')
print(kr_permission_states.info())
kr_permission_states.head(5)
<class 'pandas.core.frame.DataFrame'> RangeIndex: 241 entries, 0 to 240 Data columns (total 3 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 ID правила доступа 241 non-null object 1 ID состояния карточки 220 non-null object 2 Состояние карточки 220 non-null object dtypes: object(3) memory usage: 5.8+ KB None
| ID правила доступа | ID состояния карточки | Состояние карточки | |
|---|---|---|---|
| 0 | 1fec9e9d-f71f-4a21-8ea5-782f9aa7d9cd | a352a190-f4af-494a-b835-3b930fbe4651 | Отмена |
| 1 | d8186975-fec2-48ae-bda0-460b1978016e | a352a190-f4af-494a-b835-3b930fbe4651 | Отмена |
| 2 | f8fda4a1-1f0a-4b06-a6fc-5a4a10c6311c | d97f3b50-53cc-4c22-b7a0-a38c5589610c | На обработке |
| 3 | 08622304-d149-4100-90bb-cfc916322c68 | 1f528af1-84b9-4861-bc8c-88ed33ca09a1 | Зарегистрирован |
| 4 | 9a049833-96fe-4f6c-ace5-9d2dd7560f7e | a352a190-f4af-494a-b835-3b930fbe4651 | Отмена |
# Данные из таблицы KrPermissionExtendedcard, предварительно выгруженные из БД Tessa
kr_permission_extended_card_rules = pd.read_csv('files/KrPermissionExtendedCardRules.csv').drop('Unnamed: 0', axis=1)#, sep=';', encoding='utf-8-sig')
print(kr_permission_extended_card_rules.info())
kr_permission_extended_card_rules.head(5)
<class 'pandas.core.frame.DataFrame'> RangeIndex: 5 entries, 0 to 4 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 ID правила доступа 5 non-null object 1 РПД. Секция карточки 5 non-null object 2 РПД. Поле карточки 5 non-null object 3 РПД. Разрешение 5 non-null object 4 РПД. Разрешение (рус) 5 non-null object dtypes: object(5) memory usage: 332.0+ bytes None
| ID правила доступа | РПД. Секция карточки | РПД. Поле карточки | РПД. Разрешение | РПД. Разрешение (рус) | |
|---|---|---|---|---|---|
| 0 | 9c3612f2-42ad-4161-aff5-e40ae544a0c5 | Document | DocumentText | Write | Редактирование |
| 1 | edcc653c-f93d-4b92-8aea-1219d670afe8 | GeneralInformation | CreationDate | DenyWrite | Запрещено редактировать |
| 2 | 48367274-4d95-49e9-a24a-ecef6ca99c4d | EmployeeCard | Salary | Read | Чтение |
| 3 | 3b05d9bf-1def-49fb-bf8a-356cdb638267 | ApprovalStage | Comments | Append | Добавление комментариев |
| 4 | 5533c7bb-854d-40a9-9882-9d01bdf9117b | BasicData | Subject | DenyDelete | Запрещено удалять |
# Данные из таблицы RoleUsers, предварительно выгруженные из БД Tessa
role_users = pd.read_csv('files/RoleUsers.csv').drop('Unnamed: 0', axis=1)#, sep=';', encoding='utf-8-sig')
print(role_users.info())
role_users.head(5)
<class 'pandas.core.frame.DataFrame'> RangeIndex: 456 entries, 0 to 455 Data columns (total 10 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 ID пользователя 456 non-null object 1 Имя пользователя 456 non-null object 2 Должность пользователя 456 non-null object 3 Уровень доступа пользователя 433 non-null float64 4 Тип учетной записи пользователя 446 non-null float64 5 Логин пользователя 456 non-null object 6 Блокировка учетной записи пользователя 341 non-null object 7 ID роли 456 non-null object 8 Название роли 456 non-null object 9 Тип роли 456 non-null int64 dtypes: float64(2), int64(1), object(7) memory usage: 35.8+ KB None
| ID пользователя | Имя пользователя | Должность пользователя | Уровень доступа пользователя | Тип учетной записи пользователя | Логин пользователя | Блокировка учетной записи пользователя | ID роли | Название роли | Тип роли | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 554afab6-1d5b-41aa-809b-32ae4398e6ec | Алексеев Р.В. | Начальник отдела | 1.0 | 1.0 | alekseevrv | False | a44ab062-f574-44fe-bf5c-714c67409ec4 | Отдел кадров | 2 |
| 1 | 0e0eaf55-9fb6-4e7c-8631-ee50c699973e | Попова И.Б. | Начальник отдела | NaN | 2.0 | popovaib | False | a219a94f-b19a-49df-a73b-fbc3da990502 | IT-отдел | 2 |
| 2 | 7429bd03-3e7f-4800-b609-7e99d0934854 | Воробьёва Д.Л. | Начальник отдела | 0.0 | 1.0 | vorobeovadl | True | a5aa3f4f-ef3e-4486-a583-55632ce1fdec | Отдел продаж | 2 |
| 3 | 1293a828-e5b6-4101-b005-eb4db253db4e | Сорокин М.Р. | Менеджер проекта | 0.0 | 1.0 | sorokinmr | NaN | 18798bd5-7ad6-4fe0-946d-6a8dc3e7dc0f | Отдел продаж | 2 |
| 4 | a619e660-b6bc-4964-acf7-d919953c35ea | Волков А.В. | Менеджер проекта | 0.0 | 2.0 | volkovav | True | 5a382a70-9280-44dc-bdc6-79b5682e52f2 | Отдел продаж | 2 |
# Данные из таблицы ContextRoles, предварительно выгруженные из БД Tessa
context_roles = pd.read_csv('files/ContextRoles.csv').drop('Unnamed: 0', axis=1)#, sep=';', encoding='utf-8-sig')
print(context_roles.info())
context_roles.head(5)
<class 'pandas.core.frame.DataFrame'> RangeIndex: 5 entries, 0 to 4 Data columns (total 2 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 ID роли 5 non-null object 1 Код SQL 5 non-null object dtypes: object(2) memory usage: 212.0+ bytes None
| ID роли | Код SQL | |
|---|---|---|
| 0 | 5efb6cc8-3817-4250-b200-16c8f47637ec | \nSELECT u.UserID, u.Name\nFROM Users u\nJOIN … |
| 1 | ea1fabb9-76d4-412c-87c1-02ba1b2d4eae | \nSELECT u.UserID, u.Name\nFROM Users u\nJOIN … |
| 2 | 1c75b493-3e5b-4231-a8f1-0344652b35a2 | \nSELECT u.UserID, u.Name\nFROM Users u\nJOIN … |
| 3 | 1ce434a5-b048-47b8-b865-acfc9e32b3b3 | \nSELECT u.UserID, u.Name\nFROM Users u\nJOIN … |
| 4 | bf7f2167-23a2-424c-95e9-6d64c9835e5d | \nSELECT u.UserID, u.Name\nFROM Users u\nJOIN … |
Предобработка данных¶
# Уникальные пользователи
user_data = role_users[[
'ID пользователя',
'Имя пользователя'
]].dropna().drop_duplicates().sort_values('Имя пользователя').reset_index(drop=True)
print(user_data.info())
user_data.head(5)
<class 'pandas.core.frame.DataFrame'> RangeIndex: 199 entries, 0 to 198 Data columns (total 2 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 ID пользователя 199 non-null object 1 Имя пользователя 199 non-null object dtypes: object(2) memory usage: 3.2+ KB None
| ID пользователя | Имя пользователя | |
|---|---|---|
| 0 | 11111111-1111-1111-1111-111111111111 | System |
| 1 | 7c6ee0cb-21c5-4524-a2f4-962662b93bd0 | Алексеев Л.Р. |
| 2 | 69ffa31d-c876-4657-94f1-1190bc8d0e7e | Алексеев Л.Р. |
| 3 | 5c45994d-530c-4552-b365-9ef9381b99a9 | Алексеев Р.В. |
| 4 | 1de27c1e-ea4c-4e91-a1aa-9b92ef4f828d | Алексеев Р.В. |
Кластеризация правил¶
# NEW
# Кластеризация правил по пользователям, типам и состояниям карточек
# с подсчетом количества совпадающих разрешений в правилах
# Итерационная переменная для подсчета
# количества чанок сотрудников
n = 0
# Чанки пользователей
for u_index, u in user_data.iterrows():
# Создание датафрейма сотрудника.
# Объединение данных о пользователях и ролях
# с ID правил доступа, где используются роли
df = pd.merge(
role_users[role_users['ID пользователя'] == u['ID пользователя']].sort_values([
'Имя пользователя',
'Название роли'
])[[
'ID пользователя',
'Имя пользователя',
'ID роли',
'Название роли',
'Тип роли'
]],
kr_permission_roles[['ID роли', 'ID правила доступа']].sort_values('ID роли'),
on='ID роли',
how='inner'
)
# Выход из цикла, если датафрейм пустой
if len(df) == 0: continue
# Объединение данных о ролях из текущей чанки сотрудника
# с данными о правилах, в которых есть эти роли
df = pd.merge(
df,
kr_permission_types,
on='ID правила доступа',
how='inner'
)
df = pd.merge(
df,
kr_permission_states,
on='ID правила доступа',
how='inner'
)
df = pd.merge(
df,
kr_permissions,
on='ID правила доступа',
how='inner'
)
# Выход из цикла, если датафрейм пустой
if len(df) == 0: continue
# Изменение порядка столбцов
df = df.iloc[:,[
0, 1, 2, 3, 4, 5, 10, 6, 7, 8, 9,
11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32, 33, 34, 35, 36, 37, 38
]]
# Приведение типа параметров разрешений с bool к int
temp_df = df.iloc[:, 11:].astype('int8')
df = pd.concat([
df.iloc[:, :11],
temp_df
], axis=1)
# Выделение параметров разрешений
permissions_locs = df.iloc[:, 15:].columns.tolist()
# Выделение всех параметров, кроме содержащих данные о пользователе
locs = df.iloc[:, 2:].columns.tolist()
# Выделение параметров по которым будет производиться объединение
cross_locs = df.iloc[:, 7:10].columns.tolist()
# Кросс-джойн через временный ключ
df_locs = df[locs]
df = df_locs.merge(df_locs, on=cross_locs, suffixes=('_x', '_y'))
del df_locs
# Удаление кросс-джойна с самим собой
df = df[
(df['ID правила доступа_x'] != df['ID правила доступа_y']) &
(df['ID роли_x'] != df['ID роли_y'])
].copy()
# Удаление обратных пар правило-роль
df['x_pairs'] = df[['ID правила доступа_x', 'ID роли_x']].agg(frozenset, axis=1)
df['y_pairs'] = df[['ID правила доступа_y', 'ID роли_y']].agg(frozenset, axis=1)
df['xy_pairs'] = df[['x_pairs', 'y_pairs']].agg(frozenset, axis=1)
df['yx_pairs'] = df[['y_pairs', 'x_pairs']].agg(frozenset, axis=1)
df = df[
~df['xy_pairs'].duplicated() &
~df['yx_pairs'].duplicated()
].drop(columns=['x_pairs', 'y_pairs', 'xy_pairs', 'yx_pairs'])
# Перемножение параметров разрешений
for col in permissions_locs:
df[f'{col}_prod'] = df[f'{col}_x'] * df[f'{col}_y']
df = df.drop(columns=[f'{col}_x', f'{col}_y'])
# Суммирование всех одинаковых разрешений
df['Количество одинаковых разрешений'] = df[[f'{c}_prod' for c in permissions_locs]].sum(axis=1)
# Сортировка объектов по количеству одинаковых разрешений
df = df.sort_values('Количество одинаковых разрешений', ascending=False)
# Удаление объектов без одинаковых разрешений
df = df[df['Количество одинаковых разрешений'] > 0].reset_index(drop=True)
# Выход из цикла, если датафрейм пустой
if len(df) == 0: continue
# Изменение порядка столбцов
df = df.iloc[:,[
5, 6, 7, 0, 1, 2, 3, 4, 8, 9,
10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
40, 41, 42, 43, 44, 45, 46, 47
]]
# Корректировка названий полей датафрейма
for col in df.columns:
if '_x' in col:
df.rename(columns={col: col.replace('_x', '')}, inplace=True)
if '_y' in col:
df.rename(columns={col: col.replace('_y', ' (сопоставлено)')}, inplace=True)
if '_prod' in col:
df.rename(columns={col: col.replace('_prod', '')}, inplace=True)
# Вывод с демонстрацией всех полей датафрейма
'''with pd.option_context('display.max_columns', None):
print(df.info())
display(df.head(5))'''
# Вывод результатов кластеризации правил
if 'df' in locals():
if len(df) > 0:
# Подсчет количества чанок сотрудников
n += 1
# Определение текущей даты и времени по Мск
timestamp_now = datetime.now(pytz.timezone('Europe/Moscow')).strftime('%Y-%m-%d %H:%M:%S')
# Очистка имен пользователей для безошибочного сохранения файлов
# с использованием их в названии
safe_name = re.sub(r'[<>:"/\\|&*?]', '', u['Имя пользователя'])
# Пошаговое сохранение данных об одинаковых разрешениях
# в отдельный файл для каждой чанки сотрудника
df.to_excel(f'permissions_clusters/{safe_name}.xlsx', sheet_name='Sheet1')
# Вывод результата обработки чанки соттрудника на экран
print(f"{n}. {timestamp_now} у сотрудника {u['Имя пользователя']} \
обнаружено {len(df)} дублей с максимальным количеством \
одинаковых разрешений {df['Количество одинаковых разрешений'].max()}")
# Вывод каждой чанки сотрудика в виде отдельной таблицы на экране
# с демонстрацией всех полей датафрейма
'''with pd.option_context('display.max_columns', None):
#display(permissions_clusters.head(5))'''
# Создание и наполнение итогового датафрейма
# с основной информацией о результатах анализа разрешений
if 'total' not in locals():
total = pd.DataFrame(columns=['Дата и время', 'Сотрудники', 'Всего дублей', 'Максимум одинаковых разрешений'])
total = pd.concat([
total,
pd.DataFrame({
'Дата и время':[timestamp_now],
'Сотрудники':[u['Имя пользователя']],
'Всего дублей':[len(df)],
'Максимум одинаковых разрешений':[df['Количество одинаковых разрешений'].max()]
})
], axis=0).reset_index(drop=True)
# Принудительная остановка цикла для теста
#if n > 5: break
1. 2026-05-29 14:28:54 у сотрудника Алексеев Л.Р. обнаружено 5 дублей с максимальным количеством одинаковых разрешений 5 2. 2026-05-29 14:28:55 у сотрудника Андреев Л.К. обнаружено 1 дублей с максимальным количеством одинаковых разрешений 1 3. 2026-05-29 14:28:57 у сотрудника Виноградов Т.Г. обнаружено 1 дублей с максимальным количеством одинаковых разрешений 2 4. 2026-05-29 14:28:57 у сотрудника Волков А.В. обнаружено 1 дублей с максимальным количеством одинаковых разрешений 4 5. 2026-05-29 14:28:57 у сотрудника Герасимов С.А. обнаружено 1 дублей с максимальным количеством одинаковых разрешений 2 6. 2026-05-29 14:28:58 у сотрудника Григорьева Т.Д. обнаружено 2 дублей с максимальным количеством одинаковых разрешений 7 7. 2026-05-29 14:28:58 у сотрудника Зайцев Р.С. обнаружено 1 дублей с максимальным количеством одинаковых разрешений 4 8. 2026-05-29 14:29:00 у сотрудника Козлова К.Р. обнаружено 1 дублей с максимальным количеством одинаковых разрешений 1 9. 2026-05-29 14:29:00 у сотрудника Кудрявцева С.М. обнаружено 1 дублей с максимальным количеством одинаковых разрешений 6 10. 2026-05-29 14:29:01 у сотрудника Куликов Р.А. обнаружено 1 дублей с максимальным количеством одинаковых разрешений 5 11. 2026-05-29 14:29:03 у сотрудника Новиков В.М. обнаружено 2 дублей с максимальным количеством одинаковых разрешений 5 12. 2026-05-29 14:29:03 у сотрудника Новиков Г.Л. обнаружено 1 дублей с максимальным количеством одинаковых разрешений 4 13. 2026-05-29 14:29:04 у сотрудника Семёнов Г.Т. обнаружено 1 дублей с максимальным количеством одинаковых разрешений 6 14. 2026-05-29 14:29:05 у сотрудника Соловьёв А.Е. обнаружено 4 дублей с максимальным количеством одинаковых разрешений 3 15. 2026-05-29 14:29:07 у сотрудника Фёдорова А.Е. обнаружено 2 дублей с максимальным количеством одинаковых разрешений 5
Подведение итогов анализа существующих разрешений в Tessa¶
# Приведение строки к типу даты и времени и подсчет времени проведения анализа
total['Дата и время'] = pd.to_datetime(total['Дата и время'])
print('Продолжительность анализа', total['Дата и время'].max() - total['Дата и время'].min())
Продолжительность анализа 0 days 00:00:13
# Итоговые данные результатов анализа существующих разрешений в Tessa
print('Датафрейм с итоговыми данными результатов анализа существующих разрешений в Tessa:')
display(total)
Датафрейм с итоговыми данными результатов анализа существующих разрешений в Tessa:
| Дата и время | Сотрудники | Всего дублей | Максимум одинаковых разрешений | |
|---|---|---|---|---|
| 0 | 2026-05-29 14:28:54 | Алексеев Л.Р. | 5 | 5 |
| 1 | 2026-05-29 14:28:55 | Андреев Л.К. | 1 | 1 |
| 2 | 2026-05-29 14:28:57 | Виноградов Т.Г. | 1 | 2 |
| 3 | 2026-05-29 14:28:57 | Волков А.В. | 1 | 4 |
| 4 | 2026-05-29 14:28:57 | Герасимов С.А. | 1 | 2 |
| 5 | 2026-05-29 14:28:58 | Григорьева Т.Д. | 2 | 7 |
| 6 | 2026-05-29 14:28:58 | Зайцев Р.С. | 1 | 4 |
| 7 | 2026-05-29 14:29:00 | Козлова К.Р. | 1 | 1 |
| 8 | 2026-05-29 14:29:00 | Кудрявцева С.М. | 1 | 6 |
| 9 | 2026-05-29 14:29:01 | Куликов Р.А. | 1 | 5 |
| 10 | 2026-05-29 14:29:03 | Новиков В.М. | 2 | 5 |
| 11 | 2026-05-29 14:29:03 | Новиков Г.Л. | 1 | 4 |
| 12 | 2026-05-29 14:29:04 | Семёнов Г.Т. | 1 | 6 |
| 13 | 2026-05-29 14:29:05 | Соловьёв А.Е. | 4 | 3 |
| 14 | 2026-05-29 14:29:07 | Фёдорова А.Е. | 2 | 5 |
# Статистические данные результатов анализа разрешений
print('Статистические данные результатов:')
for col in total.columns:
print()
display(pd.DataFrame(total[col].describe()))
Статистические данные результатов:
| Дата и время | |
|---|---|
| count | 15 |
| mean | 2026-05-29 14:28:59.933333248 |
| min | 2026-05-29 14:28:54 |
| 25% | 2026-05-29 14:28:57 |
| 50% | 2026-05-29 14:29:00 |
| 75% | 2026-05-29 14:29:03 |
| max | 2026-05-29 14:29:07 |
| Сотрудники | |
|---|---|
| count | 15 |
| unique | 15 |
| top | Алексеев Л.Р. |
| freq | 1 |
| Всего дублей | |
|---|---|
| count | 15 |
| unique | 4 |
| top | 1 |
| freq | 10 |
| Максимум одинаковых разрешений | |
|---|---|
| count | 15 |
| unique | 7 |
| top | 5 |
| freq | 4 |
# Данные о количестве разрешений у сотрудника с максимальным количеством дублей
total_max = total[total['Всего дублей'] == total['Всего дублей'].max()]
display(total_max)
print(f"У сотрудников, перечисленных выше, максимальное количество правил доступа, \
дублирующих разрешения в других правилах доступа. Максимальное количество дублей \
увеличивает вероятность их пересечения с дублями разрешений у других сотрудников, \
поэтому оптимизацию существующих разрешений в Tessa предлагается начать c этого сотрудника.")
| Дата и время | Сотрудники | Всего дублей | Максимум одинаковых разрешений | |
|---|---|---|---|---|
| 0 | 2026-05-29 14:28:54 | Алексеев Л.Р. | 5 | 5 |
У сотрудников, перечисленных выше, максимальное количество правил доступа, дублирующих разрешения в других правилах доступа. Максимальное количество дублей увеличивает вероятность их пересечения с дублями разрешений у других сотрудников, поэтому оптимизацию существующих разрешений в Tessa предлагается начать c этого сотрудника.
Выводы¶
В результате анализа разрешений из имеющихся файлов, выгруженных из базы данных Tessa (в данном случае, сгенерированных с помощью генератора датафрейма тестовых данных Tessa), с разделением данных по чанкам по сотрудникам выявлено 10 сотрудников, с наиболее распространенным количеством объектов равным 2 и количеством одинаковых разрешений равным 5. У сотрудника Алексеева Л.Р. наибольшее количество дублей разрешений. Это означает:
- 10 сотрудникам были предоставлены дублями разрешения в разных правилах доступа.
- Большинству сотрудников предоставлено по 2 дубля разрешений.
- В большинстве дублей по 5 разрешений.
- Оптимизацию существующих разрешений в Tessa предлагается начать с Алексеева Л.Р., т.к. у него максимальное количество правил доступа, дублирующих разрешения, что увеличивает их пересечение с аналогичными дублями у других сотрудников.

