AURORA-1888: исполнитель видит только свои этапы, 404 вместо 500 на чужую форму/проект #261
Open
aep
wants to merge 1 commits from
feat/250-aurora-1888-stages-role-filter into test
pull from: feat/250-aurora-1888-stages-role-filter
merge into: RSHB:test
RSHB:main
RSHB:feat/266-aurora-1897-ahr-limit-period-vat-units
RSHB:feat/265-aurora-1909-ahr-manual-row-capture
RSHB:feat/264-aurora-1895-formula-localstorage
RSHB:feat/254-aurora-1906-spod-column
RSHB:feat/255-aurora-1938-transfer-econ-calc
RSHB:feat/253-aurora-1905-form1-form4-export-layout
RSHB:feat/252-aurora-1902-form2-export-layout
RSHB:feat/251-aurora-1901-smeta-rf-column-order
RSHB:feat/249-aurora-1936-row-delete-confirm
RSHB:feat/248-aurora-1877-token-refresh
RSHB:feat/246-aurora-1944-wheel-scroll-step
RSHB:feat/236-aurora-1843-svod2-server-paging-perf
RSHB:feat/236-aurora-1843-svod2-server-paging-web
RSHB:feat/236-aurora-1843-svod2-server-paging
RSHB:feat/214-aurora-1841-dict-vsp-virtual
RSHB:feat/220-aurora-1842-svod2-column-virtual
RSHB:feat/216-aurora-1846-form4-sheet-sum
RSHB:feat/235-aurora-1850-upd-form-cells-subtree
RSHB:feat/233-aurora-1848-sheet-json-no-pydantic-b
RSHB:feat/234-aurora-1839-cell-memo
RSHB:feat/233-aurora-1848-sheet-json-no-pydantic
RSHB:feat/237-aurora-1845-ws-connection-cache
RSHB:feat/215-aurora-1844-upd-form-cells-full-rows
RSHB:feat/226-aurora-1837-cell-styles-css
RSHB:feat/207-aurora-1831-project-tn-race
RSHB:feat/208-aurora-1832-phase-dates-tz
RSHB:feat/209-aurora-1833-greenlet-dev
RSHB:feat/206-aurora-1830-users-me-local-email
RSHB:feat/212-aurora-1838-ws-immutable-update
RSHB:feat/211-aurora-1836-row-measure
RSHB:feat/205-aurora-1922-pg15-migration-0012
RSHB:feat/213-aurora-1840-sheet-modals
RSHB:feat/210-aurora-1835-svod1-formatter
RSHB:test
RSHB:svod2-filter-fix2
RSHB:indent-export
RSHB:export-hierarchy
RSHB:svod2-filter-fix
RSHB:fix-id-column
RSHB:export-year-fix
RSHB:form3-source
RSHB:columns-svod-2
RSHB:export-optimize-refresh
RSHB:last-column
RSHB:svod-fix
RSHB:fix-columns
RSHB:form3-expense
RSHB:sattelite-fix
RSHB:delete-sattelite-rows
RSHB:form3-pages
RSHB:form1-sub_sub-fix
RSHB:sort-fix
RSHB:fixes-form
RSHB:new-expenses
RSHB:new-rows
RSHB:fix-style
RSHB:phase-template-edit
RSHB:fixes
RSHB:add-btn-reset-filter
RSHB:vsp-choice-fix
RSHB:project-filters-fix
RSHB:width-column-header-name
RSHB:task-filter-navigation
RSHB:auth
RSHB:icons-program-project
RSHB:export-optimize
RSHB:org-unit-limits
RSHB:auth-v2
RSHB:tasks-filters
RSHB:insert-row-fix
RSHB:svod-roles
RSHB:new-role-fix
RSHB:phase-edit-fix
RSHB:new-role
RSHB:delete-from-cell
RSHB:svod-cahe-and-table-direction
RSHB:direction-stage-fix
RSHB:backslash-fix
RSHB:svod
RSHB:front-svod-form-1
RSHB:projects-filters
RSHB:remove-row-fix
RSHB:computed-fix
RSHB:modal-add-year-project
RSHB:name-fix
RSHB:audit-logs-projects
RSHB:meta-form-3
RSHB:project-back-fix
RSHB:fix-sticky-columns
RSHB:fixies-3-form
RSHB:tech-num-fix
RSHB:fix-logic-limitation
RSHB:projects-fix
RSHB:3-form-project
RSHB:project-add-year-handler
RSHB:projects-summary-handler
RSHB:AURORA-1247
RSHB:projects-handlers
RSHB:vsp-choose-form2-2
RSHB:directions-fix
RSHB:add-validation
RSHB:add-checkbox-export-vsp
RSHB:fix-update-column-config
RSHB:fix-editable-columns
RSHB:add-biome-front
RSHB:vsp-choose-form2
RSHB:export-form-4
RSHB:booking-fix
RSHB:fix-front-tables
RSHB:phase-directions
RSHB:AURORA-1225
RSHB:fix-mapping-form-2-oper
RSHB:AURORA-1076-fix
RSHB:fix-add-row-program-4-form
RSHB:form2-limit-migrations
RSHB:delete-projects
RSHB:AURORA-1076
RSHB:form-4-add-row
RSHB:fix-logs
RSHB:AURORA-1206
RSHB:AURORA-894
RSHB:add-line-log-hotfix
RSHB:form-export-fix
RSHB:form-4-sql
RSHB:table-optimization-fix
RSHB:header_forms
RSHB:form-4-expense-item-filters
RSHB:entity-type-hotfix
RSHB:form-3-phases-timezones
RSHB:form-4-handlers
RSHB:export-fix
RSHB:close-date-vsp-fix
RSHB:deleted-user-auth-fix
RSHB:smeta-fix
RSHB:front-fixes
RSHB:line_id_code_full
RSHB:line_id_code
RSHB:timezones
RSHB:filenames-fix
RSHB:AURORA-1174-73
RSHB:project-sheets
RSHB:AURORA-1166
RSHB:websocket-projects
RSHB:project-front
RSHB:form3-permissions
RSHB:location_from_to_placement_type
RSHB:fix-vsp-filters
RSHB:fix-tests-and-functions
RSHB:mig2-fix
RSHB:vsp_fixes
RSHB:fix-ssp-title
RSHB:AURORA-1148
RSHB:fix-vsp-form-edit
RSHB:sql-funcs2-fix
RSHB:AURORA-1132
RSHB:logs
RSHB:AURORA-1147
RSHB:export-optimization
RSHB:AURORA-1131
RSHB:sql-funcs-fix
RSHB:vsp-dropdown-form
RSHB:form_2-back
RSHB:migrations
RSHB:websocket-self-flag
RSHB:reload-fix
RSHB:test-stand-fix
RSHB:excel-empty-fix
RSHB:requirements-fix
RSHB:AURORA-1096-back
RSHB:AURORA-1098
RSHB:AURORA-1096
RSHB:ws-user-id-fix
RSHB:expense-item-handler
RSHB:fix-date-phase
RSHB:excel-fix
RSHB:vsp-edit-field-fix
RSHB:vsp-fix-back
RSHB:vsp-back
RSHB:create-form-back
RSHB:export-direction-fix
RSHB:change-role-fix
RSHB:org-unit-users-fix
RSHB:audit-fix
RSHB:org-archive
RSHB:del_row_fix
RSHB:websocket_locks
RSHB:new_line_id
RSHB:admin-back
RSHB:form-fix
RSHB:ws
RSHB:ssp-fix
RSHB:stages-back
RSHB:form_124_write
RSHB:form_124
RSHB:new-model
RSHB:test-tsygankov
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
No description provided.
Delete Branch "feat/250-aurora-1888-stages-role-filter"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #250.
Проблема
GET /stages/form/{form_id} и GET /stages/project/{project_id} возвращали этапы всех ролей (DFIP, EXECUTOR_RF, DURRS) любому авторизованному пользователю, без фильтрации по роли. Доступ к форме/проекту проверялся только после загрузки этапов и только если они нашлись — для чужой формы/проекта (или формы без этапов)
bf_service.get/project_service.get_instanceвозвращалиNone, обращение к.org_unit_idпадалоAttributeError→ 500 вместо 404.На фронте подпись роли в таблице этапов показывалась всем, хотя у исполнителя теперь всегда будет своя единственная роль.
Что сделано
api/src/services/form_phase_service.py— добавлена функцияrole_code_for_user(user)(и словарьROLE_CODE_BY_USER_ROLE): единственное место сопоставления роли пользователя (UserRoleEnum.EXECUTOR_DFIP/EXECUTOR_RF/EXECUTOR_DURRS) коду роли этапа (v3.role.code:DFIP/EXECUTOR_RF/DURRS). Для ADMIN возвращаетNone— без фильтра.FormPhaseService.get_listпередаётrole=role_code_for_user(user)в репозиторий.api/src/services/form3_phase_service.py—Form3PhaseService.get_listполучил параметрuser, импортирует и использует ту жеrole_code_for_user.api/src/repository/form_phase_repository.py,api/src/repository/form3_phase_repository.py—get_listполучил необязательный параметрrole; если задан —.where(<Model>.role == role).api/src/api/v1/form_phases.py:get_form_phases— сначалаbf_service.get(user=current_user, budget_form_id=form_id), приNone→HTTPException(404, "Форма не найдена"), только потом загрузка этапов (с ролевым фильтром). Убрана веткаif phases: ... else: result = [];count = len(result).get_form3_phases— аналогично: сначалаproject_service.get_instance(user=current_user, project_id=project_id), приNone→ 404, потом отчёты и этапы с ролевым фильтром. Сопоставлениеphases[i] → reportsпоrf_project_report_idсохранено (фильтр стоит в самом SQL-запросе).web/src/components/Stages/StagesTable/StagesTable.jsx— подпись роли этапа (STAGE_ROLE_RUSSIAN_NAME) показывается только админу (isAdmin &&). Кнопка «Этапы» не тронута.Тесты
api/tests/unit/test_form_phase_service.py(новый) —FormPhaseService.get_listс моком репозитория:EXECUTOR_DFIP→ репозиторий вызван сrole='DFIP';ADMIN→role=None.api/tests/integration/test_form_phases_api_smoke.py(дополнен):test_form_phases_role_filter_for_executor— под isp1 (role_id=2=DFIP) на своей форме (budget_form 4) все этапы сrole == 'DFIP'; в той же транзакции админ добавляет этапEXECUTOR_RFна этой же форме — isp1 его не видит (список не меняется по составу ролей), админ видит оба этапа (список не урезан).test_form_phases_foreign_org_404— isp1 →GET /stages/form/1(форма чужого org_unit) → 404 (было 500).test_form3_phases_foreign_org_404— isp1 →GET /stages/project/2(проект чужого org_unit) → 404 (было 500).Прогон (venv
/Users/andrey.epifancev/projects/fomichev_claude/.venvs/dfip-api, Postgres 16perf-pg:55432, БДdfip_1888,pytest --init_dbодин раз для накатки миграций+сида, затем обычный прогон):Все 3 падения — подтверждённо пред-существующие на
testи не связаны с этой веткой (проверено прогоном тех же тестов на коде до правки,git checkout <tip test> -- api/src api/tests, тот же результат):test_db_functions_integration.py::test_backend_calls_form3_add_project_and_add_line_functions,test_export_service_query_params.py::test_export_full_form_ignores_non_applicable_params_for_other_sheets,test_export_service_query_params.py::test_export_form_payload_forwards_ignore_flag. Все тесты, затронутые этой веткой (form_phases/form3_phases), зелёные.Rulings (решения без обсуждения)
role_code_for_user) разместил вform_phase_service.pyи импортировал вform3_phase_service.py— тикет предлагал это как один из двух равноценных вариантов («либо функция в form_phase_service, либо читать Role.code по user.role_id»). Выбрал хардкод-словарь вместо похода в БД заRole.code:AppUserне хранит загруженной связи наRole(толькоrole_id), а коды ролей зафиксированы в сидах миграций (0003_initial_data.sql,0025_new_role.py) и уже захардкожены в этом же файле какLiteral["DFIP", "EXECUTOR_RF", "ADMIN", "DURRS"](FormPhaseRoleвdomain/schemas.py) — дополнительный асинхронный запрос не добавляет корректности, но добавляет round-trip.get_form3_phases, по аналогии сget_form_phases(п.4 тикета), убрал веткуif phases: ... else: result = []целиком, а не только переставил проверку проекта наверх — тикет явно просил это только дляget_form_phases(п.4), дляget_form3_phases(п.5) формулировка менее явная («сначала project_service.get_instance..., потом загрузить отчёты и этапы»). Применил ту же упрощённую структуру для единообразия: раз проект проверен в начале и гарантированно неNone, отдельная ветка не нужна.get_form3_phases— тикет не задавал точную формулировку. Использовал"Проект не найден"(по аналогии с остальными русскоязычными 404 в этом файле), а не"Project not found", как вprojects.py:238— последнее выглядит точечной опечаткой/несогласованностью в существующем коде, а не стилем, который стоит повторять.client/admin_tokens/isp_tokens— per-test, общая БД-сессия только внутри одного теста) — это ровно то, что требует сценарий тикета («в той же транзакции под админом создать этап... и убедиться, что isp1 его не видит»).Form3PhaseService.get_listотдельный unit-тест с моками не писал — тикет явно требовал unit-тест только дляFormPhaseService.get_list; ролевая фильтрация для Form3 покрыта интеграционным путём (черезGET /stages/project/{id}и косвенно через отсутствие побочных эффектов на существующих form3-тестах).🤖 Generated with Claude Code
- FormPhaseRepository.get_list и Form3PhaseRepository.get_list: новый необязательный параметр role для фильтрации по v3.role.code. - FormPhaseService.get_list и Form3PhaseService.get_list: для не-админа передают в репозиторий role по коду роли пользователя (единая функция role_code_for_user в form_phase_service.py), для ADMIN фильтр не нужен. - GET /stages/form/{form_id} и GET /stages/project/{project_id}: сначала проверяют доступ к форме/проекту (BudgetFormService.get / ProjectService.get_instance), при отсутствии доступа — 404 вместо AttributeError → 500. - StagesTable.jsx: подпись роли этапа показывается только админу. Closes #250. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>Checkout
From your project repository, check out a new branch and test the changes.