sso-nebus 0.1.6__tar.gz → 0.1.7__tar.gz
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.
- {sso_nebus-0.1.6 → sso_nebus-0.1.7}/PKG-INFO +1 -1
- {sso_nebus-0.1.6 → sso_nebus-0.1.7}/pyproject.toml +1 -1
- sso_nebus-0.1.7/sso_nebus/exmples/example_auto_role_scopes_additing.py +214 -0
- {sso_nebus-0.1.6 → sso_nebus-0.1.7}/LICENSE +0 -0
- {sso_nebus-0.1.6 → sso_nebus-0.1.7}/README.md +0 -0
- {sso_nebus-0.1.6 → sso_nebus-0.1.7}/sso_nebus/__init__.py +0 -0
- {sso_nebus-0.1.6 → sso_nebus-0.1.7}/sso_nebus/base.py +0 -0
- {sso_nebus-0.1.6 → sso_nebus-0.1.7}/sso_nebus/exceptions.py +0 -0
- {sso_nebus-0.1.6 → sso_nebus-0.1.7}/sso_nebus/exmples/example_admin.py +0 -0
- {sso_nebus-0.1.6 → sso_nebus-0.1.7}/sso_nebus/exmples/example_service.py +0 -0
- {sso_nebus-0.1.6 → sso_nebus-0.1.7}/sso_nebus/exmples/example_user.py +0 -0
- {sso_nebus-0.1.6 → sso_nebus-0.1.7}/sso_nebus/models.py +0 -0
- {sso_nebus-0.1.6 → sso_nebus-0.1.7}/sso_nebus/service_client.py +0 -0
- {sso_nebus-0.1.6 → sso_nebus-0.1.7}/sso_nebus/user_client.py +0 -0
|
@@ -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())
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|