- пришедшего называет заголовок Remote-User от прокси, и верят ему только с адреса из перечня trusted_proxies; своего входа у сервиса не осталось — ни корня /auth, ни кук, ни срока сессии, ни секрета клиента в конфиге и в базе - учётная запись заводится первым обращением: EnsureUser в пакете хранилища, шаг схемы 202608220001 с колонкой provider_login и снятыми правилами users - cmd/oidcstub заменён на cmd/devtools с подкомандой proxy; заодно закрыт унаследованный DL3066 — пользователь образа назван числом
112 lines
9.0 KiB
Markdown
112 lines
9.0 KiB
Markdown
## MODIFIED Requirements
|
||
|
||
### Requirement: Приём записи по HTTP
|
||
|
||
Сервис SHALL принимать запись запросом `POST /app/audiorecords` с телом
|
||
`multipart/form-data` и полем `audio` **только от узнанного отправителя**.
|
||
Запрос от неузнанного MUST получать код `401`, и по нему MUST не заводиться ни файл,
|
||
ни аудиозапись. Принятая запись от узнанного отправителя MUST быть сохранена и
|
||
получить заведённую под неё аудиозапись на рубеже `uploaded`.
|
||
|
||
Приём стоит тем же адресом, что и список записей, и отличается от него только
|
||
методом: он **заводит аудиозапись**, а не кладёт файл. Прежнее имя называло
|
||
содержимое запроса, и по нему приём читался как отдельная от записи вещь — хотя
|
||
запись он и создаёт.
|
||
|
||
Ответ MUST нести **список** заведённых записей и место под признак повторного
|
||
файла у каждой, даже когда файл в запросе один. Форма согласована один раз и
|
||
вперёд: приём, отдающий одну запись, пришлось бы переписывать вместе с приёмом
|
||
нескольких файлов и с распознаванием повтора по содержимому, а экран загрузки —
|
||
переделывать под вторую форму. Число файлов в запросе при этом остаётся прежним:
|
||
меняется форма ответа, не число файлов.
|
||
|
||
Элемент списка MUST нести те же поля, что и карточка записи, плюс признак
|
||
повторного файла полем `duplicate`: две формы одной вещи разошлись бы молча.
|
||
Состав карточки нормирует capability `archive`.
|
||
|
||
Прежние имена полей ответа — `job_id` и `status` — MUST не употребляться: адрес
|
||
опроса убран целиком, и идентификатор записи зовётся `id`. Это объявленная ломка
|
||
публичного контракта: стадия проекта — стройка, на сервере данных нет, а внешней
|
||
программы на прежнем контракте не существует — своего токена у неё не было.
|
||
|
||
Значение рубежа в ответе MUST принадлежать перечню рубежей конвейера и MUST не
|
||
перечисляться этой нормой порознь: рубеж объявлен одним дескриптором, и
|
||
перечисленный здесь второй раз он разошёлся бы с ним молча. Рубеж называет
|
||
достигнутое, а не предстоящее, и `created` в перечне отсутствует вовсе.
|
||
|
||
Запись сверх потолка размера MUST отвергаться до заведения файла и аудиозаписи,
|
||
и код с телом такого отказа нормирует capability `archive` наравне с прочими
|
||
ветвями. Потолок применяется уже сегодня, а ответ на его срабатывание —
|
||
самый частый отказ у человека на мобильной сети — прежде не был нормирован
|
||
ничем и уходил телом ограничителя тела, мимо единой формы.
|
||
|
||
Отказ неузнанному наступает **раньше** чтения тела: запись, за которую
|
||
не заплатит узнанный отправитель, не должна попасть даже в память.
|
||
|
||
Приём не судит о годности записи сам: расширение он берёт из имени файла, а
|
||
пригодность содержимого узнаёт у источника метаданных.
|
||
|
||
Куда именно ложится принятая запись, приёму не принадлежит: раскладку выбирает
|
||
хранилище, и нормирует её capability `storage`.
|
||
|
||
Владельцем принятой записи приём SHALL назначать узнанного предъявителя. Обязательность
|
||
владельца при этом MUST держаться и схемой хранилища: колонка владельца пустого
|
||
значения не принимает вовсе, и норму эту держит capability `storage`. Проверка в
|
||
приёме от этого не лишняя — она отвечает отправителю понятным отказом до того, как
|
||
запись попадёт в память, а схема отвечала бы отказом сохранения после укладки
|
||
файла.
|
||
|
||
Предъявитель, узнанный без учётной записи пользователя, MUST получать
|
||
отказ `403` и MUST получать его **до чтения тела** — там же, где стоит отказ
|
||
неузнанному. Владелец панели, предъявивший собственный токен хранилища, — именно
|
||
такой случай: узнан он всё же узнан, а записи в коллекции пользователей у него
|
||
нет, и владельцем записи он стать не может.
|
||
|
||
Код здесь другой, чем у запроса от неузнанного, и это не оплошность: `401` значит
|
||
«предъяви себя», а предъявитель себя предъявил. Утечки по разнице кодов нет —
|
||
оба ответа говорят о самом спрашивающем, а не о том, какие записи заведены.
|
||
|
||
Отказ **после** укладки записи потребовал бы убрать уже сохранённый файл, а
|
||
уборки файлов сервис не умеет вовсе: норма, обязывающая к недостижимому, не
|
||
пишется.
|
||
|
||
#### Scenario: Запись принята
|
||
|
||
- **GIVEN** источник метаданных читает запись и отдаёт её длительность
|
||
- **AND** отправитель узнан
|
||
- **WHEN** программа шлёт `POST /app/audiorecords` с полем `audio`
|
||
- **THEN** ответ имеет код `201`, а в теле лежит список из одного элемента
|
||
- **AND** элемент несёт непустой `id`, поле `state` со значением `uploaded` и
|
||
место под признак повторного файла
|
||
- **AND** содержимое записи целиком лежит в хранилище одним файлом
|
||
- **AND** владельцем заведённой аудиозаписи стоит узнанный предъявитель
|
||
|
||
#### Scenario: Узнанный без учётной записи пользователя
|
||
|
||
- **GIVEN** предъявлен собственный токен владельца панели
|
||
- **WHEN** он шлёт `POST /app/audiorecords` с полем `audio`
|
||
- **THEN** ответ имеет код `403`
|
||
- **AND** ни файла, ни аудиозаписи не заводится
|
||
|
||
#### Scenario: Пришедший не узнан
|
||
|
||
- **WHEN** программа шлёт `POST /app/audiorecords` с полем `audio` неузнанной
|
||
- **THEN** ответ имеет код `401`
|
||
- **AND** ни файла, ни аудиозаписи не заводится
|
||
- **AND** тело ответа не несёт данных записи
|
||
|
||
#### Scenario: Поля с записью нет
|
||
|
||
- **GIVEN** отправитель узнан
|
||
- **WHEN** программа шлёт `POST /app/audiorecords` без поля `audio`
|
||
- **THEN** ответ имеет код `400` и сообщение об отсутствии записи
|
||
- **AND** ни файла, ни аудиозаписи не заводится
|
||
|
||
#### Scenario: Размеру записи приём не судья
|
||
|
||
- **GIVEN** источник метаданных читает запись и отдаёт её длительность
|
||
- **AND** отправитель узнан
|
||
- **WHEN** программа шлёт запись нулевой длины
|
||
- **THEN** ответ имеет код `201`: собственного порога по размеру у приёма нет
|
||
|