sso-nebus 0.1.6__py3-none-any.whl → 0.1.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,214 @@
1
+ """
2
+ Скрипт создания ролей, скоупов (разрешений) и привязки их к клиенту сервиса в SSO Nebus.
3
+
4
+ Работает от имени пользователя-админа (UserClient): логин/пароль → токен с sso.admin.* → вызовы API.
5
+ Сервис не создаёт записи сам — все действия идут от профиля админа.
6
+
7
+ Соответствует RBAC в приложении:
8
+ - Роли: UserRole (admin, accountant, lawyer, manager, head_of_fin_econom_depart, head_of_legal_depart)
9
+ - Скоупы: RoleScopes (create, edit, read, delete)
10
+ - Формат скоупа в токене: {SERVICE_NAME}.{role}.{scope} (например reporting_calendar.accountant.read)
11
+
12
+ Переменные окружения:
13
+ SSO_BASE_URL, SSO_CLIENT_ID — для подключения к SSO
14
+ SSO_ADMIN_LOGIN, SSO_ADMIN_PASSWORD — учётные данные пользователя с правами sso.admin (read, create, edit)
15
+
16
+ Запуск (из каталога src):
17
+ cd src && python -m app.scripts.seed_rbac
18
+ """
19
+ import asyncio
20
+ import os
21
+ import sys
22
+ from pathlib import Path
23
+
24
+ # Добавляем src в PYTHONPATH для импорта app
25
+ _src_root = Path(__file__).resolve().parent.parent.parent
26
+ if _src_root not in sys.path:
27
+ sys.path.insert(0, str(_src_root))
28
+
29
+ # Подгружаем .env из корня репозитория
30
+ _repo_root = _src_root.parent
31
+ _env_file = _repo_root / ".env"
32
+ if _env_file.exists():
33
+ from dotenv import load_dotenv
34
+ load_dotenv(_env_file)
35
+
36
+ from sso_nebus import UserClient
37
+
38
+ from app.core.settings import config # Ваша конфигурация
39
+ from app.schemas.enums import RoleScopes, UserRole # Ваши enum-значения ролей и скоупов
40
+
41
+
42
+ # Человекочитаемые названия ролей для SSO
43
+ ROLE_DISPLAY_NAMES = {
44
+ UserRole.ADMIN: "Администратор",
45
+ UserRole.ACCOUNTANT: "Бухгалтер",
46
+ UserRole.LAWYER: "Юрист",
47
+ UserRole.MANAGER: "Менеджер",
48
+ UserRole.HEAD_OF_FIN_ECONOM_DEPART: "Руководитель фин.-экон. отдела",
49
+ UserRole.HEAD_OF_LEGAL_DEPART: "Руководитель юридического отдела",
50
+ }
51
+
52
+ # Scope для админ-операций в SSO (роли/скоупы создаёт только пользователь с этими правами)
53
+ ADMIN_SCOPE = "sso.admin.read sso.admin.create sso.admin.edit"
54
+
55
+
56
+ async def get_admin_token(client: UserClient) -> str:
57
+ """Получить access token пользователя-админа (логин + пароль)."""
58
+ login = os.environ.get("SSO_ADMIN_LOGIN", "admin").strip()
59
+ password = os.environ.get("SSO_ADMIN_PASSWORD", "SecretPassword123!").strip()
60
+ if not login or not password:
61
+ raise SystemExit(
62
+ "Задайте SSO_ADMIN_LOGIN и SSO_ADMIN_PASSWORD в .env или в окружении. "
63
+ "Скрипт работает от имени пользователя-админа с правами sso.admin.*"
64
+ )
65
+ token_response = await client.full_auth_flow(
66
+ login=login,
67
+ password=password,
68
+ scope=ADMIN_SCOPE,
69
+ )
70
+ return token_response.access_token
71
+
72
+
73
+ async def ensure_roles(client: UserClient, token: str) -> list[int]:
74
+ """Создать все роли сервиса с привязкой к client_id. Возвращает список role_id для assign_roles_to_client."""
75
+ client_id = config.sso_cfg.CLIENT_ID
76
+ existing = await client.get_roles(client_id=client_id, limit=100, access_token=token)
77
+ items = existing if isinstance(existing, list) else (existing.get("items") or existing.get("data") or [])
78
+ by_name = {r.get("name"): r.get("id") for r in items if isinstance(r, dict) and r.get("name")}
79
+
80
+ for role in UserRole:
81
+ name = role.value
82
+ display_name = ROLE_DISPLAY_NAMES.get(role, name)
83
+ if name in by_name:
84
+ print(f" Роль уже существует: {name} (id={by_name[name]})")
85
+ continue
86
+ try:
87
+ result = await client.create_role(
88
+ name=name,
89
+ display_name=display_name,
90
+ description=f"Роль сервиса отчётности: {display_name}",
91
+ client_id=client_id,
92
+ access_token=token,
93
+ )
94
+ data = result.get("data")
95
+ rid = result.get("id") or (data.get("id") if isinstance(data, dict) else None)
96
+ if rid is not None:
97
+ by_name[name] = int(rid)
98
+ print(f" Создана роль: {name} ({display_name}), id={rid}")
99
+ except Exception as e:
100
+ print(f" Ошибка создания роли {name}: {e}")
101
+ raise
102
+
103
+ # Собрать актуальный список id ролей (после созданий нужно перечитать, т.к. by_name мог быть неполный)
104
+ existing2 = await client.get_roles(client_id=client_id, limit=100, access_token=token)
105
+ items2 = existing2 if isinstance(existing2, list) else (existing2.get("items") or existing2.get("data") or [])
106
+ role_ids = [r["id"] for r in items2 if isinstance(r, dict) and r.get("id") is not None]
107
+ return role_ids
108
+
109
+
110
+ async def ensure_scopes(client: UserClient, token: str) -> None:
111
+ """
112
+ Создать все скоупы для сервиса (client + role + action).
113
+ Вызывается после ensure_roles. К клиенту привязываются роли (assign_roles_to_client), не скоупы.
114
+ """
115
+ client_id = config.sso_cfg.CLIENT_ID
116
+ service_name = config.app_cfg.SERVICE_NAME
117
+ existing = await client.get_scopes(limit=500, access_token=token)
118
+ items = existing if isinstance(existing, list) else (existing.get("items") or existing.get("data") or [])
119
+ # Ключ для проверки существования: (client, role, action) или name, в зависимости от ответа API
120
+ seen = set()
121
+ for s in items:
122
+ if not isinstance(s, dict):
123
+ continue
124
+ name = s.get("name")
125
+ if name:
126
+ seen.add(name)
127
+ else:
128
+ key = (s.get("client"), s.get("role"), s.get("action"))
129
+ if all(key):
130
+ seen.add(key)
131
+
132
+ for role in UserRole:
133
+ for scope_enum in RoleScopes:
134
+ name = f"{service_name}.{role.value}.{scope_enum.value}"
135
+ if name in seen:
136
+ print(f" Scope уже существует: {name}")
137
+ continue
138
+ key = (client_id, role.value, scope_enum.value)
139
+ if key in seen:
140
+ print(f" Scope уже существует: {key}")
141
+ continue
142
+ try:
143
+ result = await client.create_scope(
144
+ name=name,
145
+ client=client_id,
146
+ role=role.value,
147
+ action=scope_enum.value,
148
+ description=f"Reporting: роль {role.value}, действие {scope_enum.value}",
149
+ access_token=token,
150
+ )
151
+ data = result.get("data")
152
+ sid = result.get("id") or (data.get("id") if isinstance(data, dict) else None)
153
+ print(f" Создан scope: {name}" + (f" (id={sid})" if sid is not None else ""))
154
+ seen.add(name)
155
+ seen.add(key)
156
+ except Exception as e:
157
+ print(f" Ошибка создания scope {name}: {e}")
158
+ raise
159
+
160
+
161
+ async def assign_roles_to_service_client(client: UserClient, token: str, role_ids: list[int]) -> None:
162
+ """Привязать роли к клиенту сервиса (assign_roles_to_client)."""
163
+ client_id = config.sso_cfg.CLIENT_ID
164
+ if not role_ids:
165
+ print(" Нет ролей для привязки к клиенту.")
166
+ return
167
+ await client.assign_roles_to_client(
168
+ client_id=client_id,
169
+ role_ids=role_ids,
170
+ access_token=token,
171
+ )
172
+ print(f" К клиенту {client_id} привязано ролей: {len(role_ids)}")
173
+
174
+
175
+ async def main() -> None:
176
+ print("Конфигурация:")
177
+ print(f" SSO BASE_URL: {config.sso_cfg.BASE_URL}")
178
+ print(f" CLIENT_ID: {config.sso_cfg.CLIENT_ID}")
179
+ print(f" SERVICE_NAME: {config.app_cfg.SERVICE_NAME}")
180
+ print(f" Логин админа: {os.environ.get('SSO_ADMIN_LOGIN', '(не задан)')}")
181
+ print()
182
+
183
+ client = UserClient(
184
+ base_url=config.sso_cfg.BASE_URL,
185
+ client_id=config.sso_cfg.CLIENT_ID,
186
+ )
187
+
188
+ try:
189
+ print("1. Авторизация под пользователем-админом (sso.admin.*)...")
190
+ token = await get_admin_token(client)
191
+ print(" Токен получен.\n")
192
+
193
+ print("2. Создание ролей...")
194
+ role_ids = await ensure_roles(client, token)
195
+ print(f" Всего ролей у клиента: {len(role_ids)}\n")
196
+
197
+ print("3. Создание скоупов (scope)...")
198
+ await ensure_scopes(client, token)
199
+ print()
200
+
201
+ print("4. Привязка ролей к клиенту сервиса (assign_roles_to_client)...")
202
+ await assign_roles_to_service_client(client, token, role_ids)
203
+ print()
204
+
205
+ print("Готово. Роли и скоупы созданы/обновлены в SSO.")
206
+ except Exception as e:
207
+ print(f"Ошибка: {e}")
208
+ raise
209
+ finally:
210
+ await client.close()
211
+
212
+
213
+ if __name__ == "__main__":
214
+ asyncio.run(main())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sso-nebus
3
- Version: 0.1.6
3
+ Version: 0.1.7
4
4
  Summary: Python клиент для взаимодействия с MS Auth Service API
5
5
  License: LICENSE
6
6
  License-File: LICENSE
@@ -2,12 +2,13 @@ sso_nebus/__init__.py,sha256=B7X9F074caljXhvE8Fpq1SwO-gVxzFUurOdbIkNLxp0,872
2
2
  sso_nebus/base.py,sha256=daTvDeQdFkVdzvzJ_zCkq6XSP-KISpeAC7h1ETfQda0,25202
3
3
  sso_nebus/exceptions.py,sha256=XHrvfGGvs3eKMzkpwRxXyDngjpMSp7X7E8EUXwv26NA,1262
4
4
  sso_nebus/exmples/example_admin.py,sha256=eAqkS4J5YyJneP4EPUrBlvNbGlK7rHEO39kCklt_WQM,1822
5
+ sso_nebus/exmples/example_auto_role_scopes_additing.py,sha256=Q9NjHHQKqyfQJmE03lIniv4ZndgsSRUI-M_ciJXCw1k,9901
5
6
  sso_nebus/exmples/example_service.py,sha256=19NNTmbaRSaWLpDQffGWNDmXkDJXdLOgD2WkV32xUu8,1778
6
7
  sso_nebus/exmples/example_user.py,sha256=c_Uf36FDHIXvv_vdqsO5XvXkvu1YOUsqaTO44_8DqVM,1878
7
8
  sso_nebus/models.py,sha256=0xIE5RvnxV2sAxO7_GTCes6HlHbdzGwHIjWVe1X7-ks,3024
8
9
  sso_nebus/service_client.py,sha256=jMvY_6GLmyb-vkQYIy7Bnh4PVD55aG3TLJiYgCbnBjA,7361
9
10
  sso_nebus/user_client.py,sha256=-3pJmvZxJgyEo3moGQvZVAOhJMOIC6NuE8NgJ33Fyfs,12976
10
- sso_nebus-0.1.6.dist-info/licenses/LICENSE,sha256=dCbOm3zpH8T7vLDC2K7QJLu-LEl2zqaSuyARbqfGsEY,1863
11
- sso_nebus-0.1.6.dist-info/METADATA,sha256=EDtGOh3UzlRIALXPB7m-NhVQ-KetX4j0_TaI92hcu9U,8589
12
- sso_nebus-0.1.6.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
13
- sso_nebus-0.1.6.dist-info/RECORD,,
11
+ sso_nebus-0.1.7.dist-info/licenses/LICENSE,sha256=dCbOm3zpH8T7vLDC2K7QJLu-LEl2zqaSuyARbqfGsEY,1863
12
+ sso_nebus-0.1.7.dist-info/METADATA,sha256=MbVxgoou-sopLiEUFmexF4kWqvGrqnBOewkSdSAyQLg,8589
13
+ sso_nebus-0.1.7.dist-info/WHEEL,sha256=3ny-bZhpXrU6vSQ1UPG34FoxZBp3lVcvK0LkgUz6VLk,88
14
+ sso_nebus-0.1.7.dist-info/RECORD,,