локальный вход задаётся конфигом: заголовки подставляет сам сервис
- в конфиг добавлены секция [auth.test_headers] и предохранитель [server] debug: заголовки входа подставляет слой транспорта, второго процесса локальный запуск больше не требует - подкоманда devtools proxy удалена целиком: всё, ради чего её поднимали, делает сам сервис - адресного предохранителя нет по решению владельца — цена названа в ADR и в модели угроз
This commit is contained in:
@@ -0,0 +1,508 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Отладочный запуск называет пришедшего настройками
|
||||
|
||||
Сервис SHALL подставлять запросу заголовки входа значениями из секции
|
||||
`[auth] test_headers`, когда включён предохранитель `[server] debug`, и MUST
|
||||
делать это так, чтобы узнавание не отличало подставленный заголовок от
|
||||
поставленного обратным прокси. Ветка кода, которой узнаётся пришедший, обязана
|
||||
быть той же, что работает в бою: отладке подлежит боевой путь, а не его
|
||||
отладочный двойник.
|
||||
|
||||
Подстановка MUST происходить только при **отсутствии** заголовка `Remote-User` в
|
||||
запросе. Заголовок, пришедший в любом числе значений, MUST оставаться нетронутым:
|
||||
узнавание отвергает запрос с более чем одним значением, и подстановка, дописавшая
|
||||
второе, превратила бы законный отказ в проход.
|
||||
|
||||
Подстановка MUST происходить только тогда, когда адрес соединения попадает в
|
||||
перечень `[auth] trusted_proxies`, и адрес этот MUST судиться **той же
|
||||
функцией**, какой судит его узнавание: и разбор адреса пира, и сверка с
|
||||
перечнем берутся из одного дома, а не пишутся вторым списком. Второй перечень
|
||||
разошёлся бы с первым молча — так же, как разошлись бы два списка имён
|
||||
заголовков. Второго барьера у подстановки нет и не заводится: круг тех, кто
|
||||
вправе назвать пришедшего, уже очерчен этим перечнем.
|
||||
|
||||
Подстановка MUST действовать только при **непустой** секции имитации. Пустая
|
||||
секция значит «подставлять нечего»: цепочка слоёв при ней та же, что при
|
||||
выключенном предохранителе, и ни одного заголовка запроса слой не трогает.
|
||||
Иначе включённый предохранитель при пустой секции срезал бы `Remote-Email` у
|
||||
запроса, пришедшего без `Remote-User`, — поведение, которого в бою нет.
|
||||
|
||||
Подставляя, сервис MUST распоряжаться всей тройкой заголовков `Remote-*`
|
||||
целиком: названные секцией ставятся её значением, не названные удаляются.
|
||||
Запрос без `Remote-User`, но с `Remote-Email` иначе собрал бы личность из двух
|
||||
источников — логин свой, почту чужую.
|
||||
|
||||
Подстановка MUST действовать только под корнем приложения — там же, где действует
|
||||
узнавание. Проба здоровья, метрики и раздача приложения её не видят.
|
||||
|
||||
#### Scenario: Запрос без заголовка входа получает имя из настроек
|
||||
|
||||
- **GIVEN** предохранитель включён, секция имитации называет `Remote-User`
|
||||
- **WHEN** запрос приходит с доверенного адреса без единого заголовка `Remote-*`
|
||||
- **THEN** сервис узнаёт пришедшего под именем из секции
|
||||
- **AND** учётная запись заводится, если её не было
|
||||
|
||||
#### Scenario: Пришедший заголовок не подменяется
|
||||
|
||||
- **GIVEN** предохранитель включён, секция имитации называет `Remote-User`
|
||||
- **WHEN** запрос приходит с доверенного адреса с заголовком `Remote-User`
|
||||
- **THEN** узнавание получает значение из запроса, а не из настроек
|
||||
|
||||
#### Scenario: Два значения заголовка остаются отказом
|
||||
|
||||
- **GIVEN** предохранитель включён, секция имитации называет `Remote-User`
|
||||
- **WHEN** запрос приходит с двумя значениями заголовка `Remote-User`
|
||||
- **THEN** сервис не подставляет ничего, и запрос остаётся неузнанным
|
||||
|
||||
#### Scenario: Недоверенный адрес имени не получает
|
||||
|
||||
- **GIVEN** предохранитель включён, секция имитации называет `Remote-User`
|
||||
- **WHEN** запрос без заголовка приходит с адреса вне перечня доверенных
|
||||
- **THEN** сервис не подставляет ничего, и запрос остаётся неузнанным
|
||||
|
||||
#### Scenario: Чужой заголовок почты не смешивается со своим логином
|
||||
|
||||
- **GIVEN** предохранитель включён, секция имитации называет `Remote-User` и не
|
||||
называет `Remote-Email`
|
||||
- **WHEN** запрос приходит без `Remote-User`, но с заголовком `Remote-Email`
|
||||
- **THEN** узнавание получает логин из настроек и не получает адреса почты вовсе
|
||||
|
||||
#### Scenario: Пустая секция имитации заголовков не трогает
|
||||
|
||||
- **GIVEN** предохранитель включён, секция имитации пуста
|
||||
- **WHEN** запрос приходит с доверенного адреса без `Remote-User`, но с
|
||||
заголовком `Remote-Email`
|
||||
- **THEN** слой заголовков не трогает, и `Remote-Email` доходит до узнавания
|
||||
нетронутым
|
||||
|
||||
#### Scenario: Выключенный предохранитель имён не раздаёт
|
||||
|
||||
- **GIVEN** предохранитель выключен, секция имитации пуста
|
||||
- **WHEN** запрос приходит с доверенного адреса без заголовка `Remote-User`
|
||||
- **THEN** запрос остаётся неузнанным
|
||||
|
||||
#### Scenario: Наблюдение подстановки не видит
|
||||
|
||||
- **GIVEN** предохранитель включён, секция имитации называет `Remote-User`
|
||||
- **WHEN** запрос приходит на `GET /health` без заголовка
|
||||
- **THEN** ответ тот же, что и при выключенном предохранителе
|
||||
|
||||
### Requirement: Настройка, открывающая вход всем, роняет старт
|
||||
|
||||
Сервис SHALL отказываться подниматься на настройках, при которых отладочный вход
|
||||
становится открытым входом, и MUST называть в отказе имя ключа. Проверка идёт на
|
||||
старте, до приёма трафика: настройка, отданная на честность выкладки, проверяется
|
||||
только тем, что чужой архив уже уехал не тому.
|
||||
|
||||
Умолчания названы нормой, а не образцом конфига. Отсутствие ключа `[server]
|
||||
debug` MUST читаться как выключенный предохранитель, а отсутствие секции
|
||||
`[auth] test_headers` — как пустая секция: конфиг сегодняшнего дня, не тронутый
|
||||
ни на байт, обязан вести себя ровно как вёл. Ошибка разбора значения MUST
|
||||
кончаться отказом старта, а не прочтением «включено».
|
||||
|
||||
Секция имитации MUST считаться **непустой**, как только в ней назван хотя бы
|
||||
один ключ, каким бы ни было его значение. `Remote-User = ""` — заполненная
|
||||
секция, а не пустая: человек, написавший ключ, имитацию завёл, и пустое значение
|
||||
у него — вторая поломка, а не отсутствие первой.
|
||||
|
||||
Отказом MUST быть каждый из четырёх случаев.
|
||||
|
||||
Первый — секция имитации заполнена при выключенном предохранителе. Состояние это
|
||||
не читается никак: либо человек забыл включить предохранитель и будет искать
|
||||
поломку везде, кроме одного ключа, либо забыл убрать имитацию из боевого файла — и
|
||||
тогда до открытого архива остаётся одно слово. Молчаливое игнорирование и
|
||||
предупреждение в журнале оба оставляют вторую поломку жить.
|
||||
|
||||
Второй — секция имитации называет ключ, не совпадающий ни с одним заголовком
|
||||
входа, который сервис читает. Перечень принимаемых имён MUST порождаться теми же
|
||||
именами заголовков, которыми пользуется узнавание, а не перечисляться вторым
|
||||
списком; сравнение MUST идти по каноническому виду имени, потому что в HTTP имя
|
||||
нечувствительно к регистру, а ключ конфига чувствителен. Опечатка в имени иначе
|
||||
кончается сервисом, который никого не узнаёт, без единого следа.
|
||||
|
||||
Третий — секция имитации непуста, а годного `Remote-User` в ней нет. Ключ
|
||||
логина MUST быть назван: секция, называющая один `Remote-Email`, поднимает
|
||||
сервис, который подставит почту, удалит логин и не узнает никого. Значение
|
||||
логина MUST проходить **тот же приём**, каким узнавание судит пришедшее
|
||||
значение (`internal/entity.AcceptProviderLogin`): пустое, из одних пробельных
|
||||
знаков, сверх предела длины и с управляющими знаками — отказ старта. Иначе
|
||||
сервис поднимается, ставит заголовок, получает отказ приёма и отвечает
|
||||
неузнанным на всё, оставляя за собой одну отладочную строку. Оба состояния — то
|
||||
самое «не читается никак», ради которого заведён первый случай.
|
||||
|
||||
Четвёртый — два ключа секции дают одно каноническое имя заголовка. `Remote-User`
|
||||
и `remote-user` для TOML — два ключа, для HTTP — одно имя: сервис подставил бы
|
||||
одно значение, а второе потерял бы молча, и человек искал бы поломку в значении,
|
||||
которого сервис не читал вовсе. Отказ MUST называть секцию и причину, а значений
|
||||
MUST не называть.
|
||||
|
||||
Включённый предохранитель при пустой секции имитации отказом MUST не быть: сам по
|
||||
себе он ничего не включает.
|
||||
|
||||
#### Scenario: Имитация без предохранителя не поднимается
|
||||
|
||||
- **GIVEN** секция имитации заполнена, предохранитель выключен
|
||||
- **WHEN** сервис запускают
|
||||
- **THEN** старт отказывает и называет имя ключа предохранителя
|
||||
|
||||
#### Scenario: Неизвестное имя заголовка не поднимается
|
||||
|
||||
- **GIVEN** предохранитель включён, секция имитации называет ключ, которого нет
|
||||
среди читаемых заголовков входа
|
||||
- **WHEN** сервис запускают
|
||||
- **THEN** старт отказывает и называет принимаемые имена
|
||||
|
||||
#### Scenario: Имитация без ключа логина не поднимается
|
||||
|
||||
- **GIVEN** предохранитель включён, секция имитации называет только
|
||||
`Remote-Email`
|
||||
- **WHEN** сервис запускают
|
||||
- **THEN** старт отказывает и называет имя недостающего ключа логина
|
||||
|
||||
#### Scenario: Два ключа одного заголовка не поднимаются
|
||||
|
||||
- **GIVEN** предохранитель включён, секция имитации называет `Remote-User` и
|
||||
`remote-user`
|
||||
- **WHEN** сервис запускают
|
||||
- **THEN** старт отказывает и называет секцию, в которой два ключа дали одно имя
|
||||
заголовка
|
||||
|
||||
#### Scenario: Негодное значение логина не поднимается
|
||||
|
||||
- **GIVEN** предохранитель включён, секция имитации называет `Remote-User`
|
||||
значением в 300 знаков
|
||||
- **WHEN** сервис запускают
|
||||
- **THEN** старт отказывает и называет имя ключа, а не его значение
|
||||
|
||||
#### Scenario: Пустое значение логина считается заполненной имитацией
|
||||
|
||||
- **GIVEN** предохранитель выключен, секция имитации называет `Remote-User = ""`
|
||||
- **WHEN** сервис запускают
|
||||
- **THEN** старт отказывает и называет имя ключа предохранителя
|
||||
|
||||
#### Scenario: Конфиг без ключа предохранителя ведёт себя как с выключенным
|
||||
|
||||
- **GIVEN** конфиг не называет ни `[server] debug`, ни секции имитации
|
||||
- **WHEN** сервис запускают
|
||||
- **THEN** сервис поднимается, подстановки не заводит и никого сам не называет
|
||||
|
||||
#### Scenario: Предохранитель без имитации поднимается
|
||||
|
||||
- **GIVEN** предохранитель включён, секция имитации пуста
|
||||
- **WHEN** сервис запускают
|
||||
- **THEN** сервис поднимается
|
||||
|
||||
### Requirement: Отладочный запуск виден в журнале
|
||||
|
||||
Сервис SHALL сообщать владельцу о включённой подстановке заголовков строкой
|
||||
журнала при старте и MUST не печатать при этом ни одного подставляемого значения.
|
||||
Сервис, называющий пришедшего сам, — это ровно то «может стать проблемой», ради
|
||||
которого заведён предупреждающий уровень: строка старта идёт на `WARN`.
|
||||
|
||||
Строка старта MUST нести имена подставляемых заголовков и MUST не нести их
|
||||
значений. Логин — ключ к чужому архиву, и запрет на печать значения, дающего
|
||||
доступ, действует на подставленное значение наравне с пришедшим.
|
||||
|
||||
Запрос, которому заголовки подставлены, SHALL писаться на уровне `INFO`:
|
||||
этой строкой «узнан подстановкой» отличается от «узнан прокси». Боевой журнал
|
||||
она не топит по построению — пишется только там, где подстановка работает, а в
|
||||
бою предохранитель выключен умолчанием; на отладочном уровне её не увидел бы
|
||||
никто, потому что настройки под уровень журнала у сервиса нет и он зашит `INFO`.
|
||||
Строка MUST нести адрес пира и не нести подставленных значений.
|
||||
|
||||
Запрос, которому подставить нельзя из-за недоверенного адреса, MUST оставлять
|
||||
**свою** строку предупреждающим уровнем. Узнавание на этом месте предупреждения
|
||||
не пишет: подстановка работает при отсутствии `Remote-User`, а отсутствие
|
||||
заголовка узнавание считает случаем штатным и пишет о нём отладочную строку;
|
||||
предупреждение о недоверенном адресе оно бережёт для заголовка, который
|
||||
**пришёл**. Без своей строки самый частый локальный отказ — браузер пришёл
|
||||
с `::1`, а перечень называет `127.0.0.1` — не отличим от поломки узнавания:
|
||||
человек видит `401` на всём приложении, заполненную секцию имитации и
|
||||
включённый предохранитель. Строка MUST нести адрес пира и MUST не нести
|
||||
подставляемых значений.
|
||||
|
||||
#### Scenario: Старт с подстановкой предупреждает владельца
|
||||
|
||||
- **GIVEN** предохранитель включён, секция имитации заполнена
|
||||
- **WHEN** сервис поднимается
|
||||
- **THEN** журнал несёт предупреждение с именами подставляемых заголовков
|
||||
|
||||
#### Scenario: Подставленного значения нет в журнале
|
||||
|
||||
- **GIVEN** предохранитель включён, секция имитации называет логин и адрес почты
|
||||
- **WHEN** сервис поднимается и принимает запрос без заголовка
|
||||
- **THEN** ни логин, ни адрес почты не встречаются ни в одной журнальной записи
|
||||
|
||||
#### Scenario: Отказ подстановки на недоверенном адресе виден в журнале
|
||||
|
||||
- **GIVEN** предохранитель включён, секция имитации заполнена
|
||||
- **WHEN** запрос без заголовка `Remote-User` приходит с адреса вне перечня
|
||||
доверенных
|
||||
- **THEN** журнал несёт предупреждение с адресом пира
|
||||
- **AND** подставляемых значений в строке нет
|
||||
|
||||
#### Scenario: Подстановка на запросе видна при боевом уровне журнала
|
||||
|
||||
- **GIVEN** предохранитель включён, секция имитации заполнена
|
||||
- **WHEN** запрос с доверенного адреса получает подставленные заголовки
|
||||
- **THEN** строка об этом имеет уровень `INFO` и видна журналу, настроенному
|
||||
по-боевому
|
||||
|
||||
### Requirement: Предохранитель отладки включает только подстановку заголовков
|
||||
|
||||
Предохранитель `[server] debug` SHALL менять одно поведение сервиса — подстановку
|
||||
заголовков входа — и MUST не менять никакого другого. Перечень следствий закрыт, и
|
||||
новое следствие вешается на этот ключ только отдельным требованием спеки: ключ,
|
||||
названный общим словом, иначе обрастает всем подряд, и выключить его перестаёт
|
||||
означать «сервис ведёт себя как в бою».
|
||||
|
||||
Сам по себе включённый предохранитель не включает и подстановки: она MUST
|
||||
действовать только при непустой секции имитации, и при пустой цепочка слоёв MUST
|
||||
быть та же, что при выключенном предохранителе, — требование «Отладочный запуск
|
||||
называет пришедшего настройками».
|
||||
|
||||
Включённый предохранитель MUST не менять уровень журнала, не добавлять в ответы
|
||||
тексты внутренних отказов и следы стека, не выводить тела запросов и ответов
|
||||
внешних сервисов, не снимать и не ослаблять ограничитель частоты, не подменять
|
||||
распознаватель, не ослаблять ни одной проверки старта и не открывать неузнанному
|
||||
ни одного адреса.
|
||||
|
||||
#### Scenario: Включённый предохранитель без имитации ничего не меняет
|
||||
|
||||
- **GIVEN** предохранитель включён, секция имитации пуста
|
||||
- **WHEN** сервис принимает запросы
|
||||
- **THEN** он ведёт себя ровно так же, как с выключенным предохранителем
|
||||
|
||||
#### Scenario: Ограничитель частоты работает и в отладочном запуске
|
||||
|
||||
- **GIVEN** предохранитель включён, секция имитации заполнена
|
||||
- **WHEN** запросы идут чаще дозволенного
|
||||
- **THEN** ограничитель частоты режет их так же, как при выключенном
|
||||
предохранителе
|
||||
|
||||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Кого пускать, решает провайдер
|
||||
|
||||
Сервис SHALL пускать всякого, кого назвал доверенный источник, и своей проверки
|
||||
допуска MUST не делать. Кто допущен, определяет правило провайдера на домен
|
||||
сервиса — настройка выкладки, лежащая вне репозитория.
|
||||
|
||||
Требование записано именно как решение с ценой, а не как умолчание: провайдер
|
||||
общий для контура, и правило, настроенное слишком широко, открывает сервис
|
||||
всякому, у кого есть учётная запись у провайдера. Проверить это по коду нельзя,
|
||||
поэтому граница названа здесь и повторена в модели угроз.
|
||||
|
||||
Цена сдвинулась в нашу пользу: провайдер судит **каждый** запрос, а не только
|
||||
первый. Прежде сервис спрашивал провайдера однажды и потом верил выданному
|
||||
значению до его истечения — отзыв доступа доходил до сервиса с задержкой в срок
|
||||
жизни этого значения. Теперь отзыв действует со следующего запроса.
|
||||
|
||||
Обратная сторона у этого одна, и она названа прямо: **весь барьер держится на
|
||||
том, что прокси ставит заголовок сам, а не пропускает пришедший**. Прокси,
|
||||
пропускающий чужой заголовок, открывает сервис всякому под любым именем.
|
||||
Требование к контуру записано в модели угроз; репозиторием оно не проверяется.
|
||||
|
||||
**Изъятие одно — отладочный запуск.** При включённом предохранителе
|
||||
`[server] debug` доверенным источником становятся настройки сервиса: сервис
|
||||
называет пришедшего сам, никого не спросив. Это единственное место, где имя
|
||||
берётся не от провайдера. Требования к нему стоят выше отдельными строками, и
|
||||
снимать их поодиночке нельзя: барьер держится всеми разом.
|
||||
|
||||
Держится изъятие **умолчанием, а не машиной**: предохранитель по умолчанию
|
||||
выключен, а заполненная имитация без него роняет старт. Боевой перечень
|
||||
доверенных адресов включению предохранителя не мешает — сервис, поднятый в бою с
|
||||
включённым предохранителем и заполненной имитацией, назовёт своим именем всякого,
|
||||
чей запрос пришёл через обратный прокси, то есть всякого, кто пришёл обычным
|
||||
путём.
|
||||
|
||||
#### Scenario: Названный провайдером получает доступ
|
||||
|
||||
- **WHEN** запрос приходит с доверенного адреса с заголовком, поставленным
|
||||
прокси
|
||||
- **THEN** доступ открывается, а учётная запись заводится, если её не было
|
||||
- **AND** сервис не спрашивает у заголовков ничего сверх имени, имени для показа
|
||||
и адреса почты
|
||||
|
||||
#### Scenario: Отзыв у провайдера действует со следующего запроса
|
||||
|
||||
- **GIVEN** человек работал в сервисе, и провайдер закрыл ему доступ
|
||||
- **WHEN** приходит следующий его запрос
|
||||
- **THEN** прокси заголовка не ставит, и запрос получает отказ
|
||||
|
||||
#### Scenario: Без отладочного запуска имя приходит только от провайдера
|
||||
|
||||
- **GIVEN** предохранитель отладки выключен
|
||||
- **WHEN** запрос приходит с доверенного адреса без заголовка
|
||||
- **THEN** сервис никого не узнаёт и своего имени пришедшему не назначает
|
||||
|
||||
### Requirement: Пришедшего называет доверенный источник
|
||||
|
||||
Сервис SHALL узнавать пришедшего по заголовку `Remote-User`, который ставит
|
||||
обратный прокси, сходивший к провайдеру, и MUST не вести собственного входа: ни
|
||||
адреса, уводящего к провайдеру, ни адреса возврата, ни куки, ни выхода у сервиса
|
||||
не остаётся.
|
||||
|
||||
**Изъятие одно — отладочный запуск.** При включённом предохранителе
|
||||
`[server] debug` заголовок входа ставит не прокси, а сам сервис значением из
|
||||
секции `[auth] test_headers`; узнавание при этом остаётся тем же и подставленного
|
||||
заголовка от пришедшего не отличает. Условия, при которых источник этот законен,
|
||||
и проверки старта, которыми он держится, стоят требованиями «Отладочный запуск
|
||||
называет пришедшего настройками», «Настройка, открывающая вход всем, роняет
|
||||
старт», «Отладочный запуск виден в журнале» и «Предохранитель отладки включает
|
||||
только подстановку заголовков». Держится изъятие умолчанием предохранителя
|
||||
«выключено» и отказом старта при заполненной имитации без него; боевой перечень
|
||||
доверенных адресов включению предохранителя не мешает.
|
||||
|
||||
Заголовку сервис MUST верить только тогда, когда запрос пришёл с адреса из
|
||||
объявленного перечня доверенных, и адрес этот MUST браться у самого соединения,
|
||||
а не из пересылаемого заголовка: значением пересылаемого распоряжается тот, кто
|
||||
шлёт запрос, и барьер, подделываемый той же строкой, которой он обходится, не
|
||||
барьер вовсе.
|
||||
|
||||
Заголовок, пришедший с недоверенного адреса, MUST не узнавать никого. Отказа при
|
||||
этом MUST не наступать в самом узнавании: проба здоровья, метрики и разметка
|
||||
приложения открыты неузнанному, и отказ на них закрыл бы наблюдение за сервисом
|
||||
всякому, кто пришлёт заголовок. Отказ приходит там, где приходил и раньше, —
|
||||
требованием учётной записи на адресах приложения.
|
||||
|
||||
**Узнавание идёт после ограничителя частоты, а не до него.** Оно читает базу, а
|
||||
на новом имени ещё и пишет в неё; выполненное раньше ограничителя, оно работало
|
||||
бы на запросах, которые тот уже отверг, и поток отвергнутых обращений заводил бы
|
||||
учётные записи, которые потом не убираются ничем.
|
||||
|
||||
**Узнавание действует на объявленной области, а не на всей поверхности сервиса.**
|
||||
Область — корень приложения; она MUST выводиться из объявленного адресного
|
||||
пространства сервиса, а не перечисляться вторым списком. Прежде область была
|
||||
шире на один адрес — тот, которым хранилище выдавало короткий токен файла; ни
|
||||
адреса, ни токена не осталось. Прежде область была и уже: собственную поверхность
|
||||
хранилища требовалось из неё вычитать, потому что ключ учётной записи лежал в
|
||||
коллекции обычной колонкой, а правило правки было библиотечным. Поверхности этой
|
||||
нет, и вычитать больше нечего.
|
||||
|
||||
Сужение области закрывает вещь, которая от смены хранилища не зависит: узнавание
|
||||
MUST не срабатывать на пробе здоровья, на метриках и на ресурсах приложения.
|
||||
Иначе запрос за каждой картинкой стоил бы обращения к базе, а первый такой запрос
|
||||
с новым именем — записи в неё.
|
||||
|
||||
**Значение заголовка принимается, а не берётся как есть.** Пустое значение и
|
||||
значение из одних пробельных знаков MUST не узнавать никого и MUST не заводить
|
||||
учётной записи: прокси штатно шлёт пустой заголовок там, где никого не назвал, и
|
||||
без этой нормы все неназванные собрались бы в одну учётную запись с общим
|
||||
архивом. Запрос, несущий **более одного** значения `Remote-User`, MUST не
|
||||
узнавать никого: прокси, настроенный добавлять заголовок вместо замены, оставляет
|
||||
рядом со своим значением присланное анонимом, и выбор «первое попавшееся» отдал
|
||||
бы вход анониму. Значение сверх объявленного предела длины и значение с
|
||||
управляющими знаками MUST не узнавать никого. Сравнение при поиске MUST быть
|
||||
точным, знак в знак: приведение регистра склеило бы двух разных людей по правилу,
|
||||
которого у провайдера нет. Обрамляющие пробелы при этом MUST срезаться до
|
||||
сравнения: они не часть имени, и заголовок с ведущим пробелом называет того же
|
||||
человека. Предел длины MUST считаться в **знаках** — той же единицей, что
|
||||
считает колонка.
|
||||
|
||||
Отказ базы при узнавании MUST кончаться отказом сервиса, а не молчаливым
|
||||
проходом неузнанным: иначе человек увидит отказ входа там, где легла база.
|
||||
|
||||
Исход узнавания MUST оставлять строку журнала — и когда заголовок пришёл с
|
||||
недоверенного адреса, и когда заголовок пришёл **более чем одним значением**, и
|
||||
когда учётная запись заведена. Уровень первых двух MUST быть виден при боевой
|
||||
настройке журнала: обе строки означают поломку контура, а поломка, записанная
|
||||
уровнем, который в бою выключен, не записана вовсе. Без неё владелец, у
|
||||
которого никто не может войти, не отличит своей поломки (перечень доверенных
|
||||
адресов) от поломки контура (прокси заголовка не ставит), а это разные поломки в
|
||||
разных местах. Строка несёт адрес пира и идентификатор учётной записи и MUST не
|
||||
нести значения заголовка.
|
||||
|
||||
Имя, пригодное к показу, сервис SHALL брать из заголовка `Remote-Name`, адрес
|
||||
почты — из `Remote-Email`. Имена всех трёх заголовков нормативны: смена имени
|
||||
молча перестаёт узнавать всех, а проверка, которая сама ставит и сама читает своё
|
||||
имя, этого не замечает. Контур уже пишет эти имена соседним сервисам.
|
||||
|
||||
Узнавание MUST идти на каждом запросе, и значения, переживающего запрос, сервис
|
||||
MUST не выдавать вовсе — ни куки, ни токена сессии, ни короткого токена файла.
|
||||
Исключений у этого правила больше нет: файл записи отдаётся тому же узнаванию,
|
||||
что и всё прочее, и отзыв доступа доходит до него сразу.
|
||||
|
||||
Смысл именно таков: отзыв доступа судит провайдер на каждом обращении, а не
|
||||
однажды выданный срок.
|
||||
|
||||
Собственных токенов сервис не принимает: значения, предъявленного запросом и
|
||||
дающего доступ помимо заголовка, у него не существует. Прежде такое значение
|
||||
било заголовок — им пользовался владелец панели; панели нет, и правило приоритета
|
||||
осталось бы правилом без предмета.
|
||||
|
||||
Значение заголовка MUST не попадать ни в журнал, ни в ответ, ни в метку метрики.
|
||||
Оно приходит строкой запроса и целиком задаётся тем, кто её шлёт, а с
|
||||
недоверенного адреса — анонимом; сверх того имя принадлежит человеку наравне с
|
||||
адресом его почты.
|
||||
|
||||
#### Scenario: Заголовок с доверенного адреса узнаёт человека
|
||||
|
||||
- **GIVEN** адрес источника стоит в перечне доверенных
|
||||
- **WHEN** запрос к адресу приложения приходит с заголовком `Remote-User`
|
||||
- **THEN** запрос идёт от имени учётной записи с этим значением
|
||||
|
||||
#### Scenario: Заголовок с недоверенного адреса не узнаёт никого
|
||||
|
||||
- **GIVEN** адреса источника в перечне доверенных нет
|
||||
- **WHEN** запрос к адресу приложения приходит с тем же заголовком
|
||||
- **THEN** ответ имеет код `401`
|
||||
- **AND** учётной записи с этим значением не появляется
|
||||
|
||||
#### Scenario: Предъявленного значения сервис не признаёт
|
||||
|
||||
- **GIVEN** запрос несёт заголовок `Remote-User` и постороннее значение доступа
|
||||
в заголовке или в параметре
|
||||
- **WHEN** сервис решает, кто пришёл
|
||||
- **THEN** пришедшим считается названный заголовком
|
||||
|
||||
#### Scenario: Пустой заголовок не узнаёт никого
|
||||
|
||||
- **GIVEN** адрес источника стоит в перечне доверенных
|
||||
- **WHEN** запрос к адресу приложения приходит с пустым `Remote-User`
|
||||
- **THEN** ответ имеет код `401`
|
||||
- **AND** учётной записи не появляется
|
||||
|
||||
#### Scenario: Два значения одного заголовка не узнают никого
|
||||
|
||||
- **GIVEN** адрес источника стоит в перечне доверенных
|
||||
- **WHEN** запрос к адресу приложения несёт два значения `Remote-User`
|
||||
- **THEN** ответ имеет код `401`
|
||||
- **AND** учётной записи не появляется
|
||||
|
||||
#### Scenario: Значение сверх предела длины не узнаёт никого
|
||||
|
||||
- **GIVEN** адрес источника стоит в перечне доверенных
|
||||
- **WHEN** запрос несёт `Remote-User` длиннее объявленного предела
|
||||
- **THEN** ответ имеет код `401`
|
||||
- **AND** учётной записи не появляется
|
||||
|
||||
#### Scenario: Область узнавания — корень приложения
|
||||
|
||||
- **GIVEN** сервис поднялся
|
||||
- **WHEN** смотрят, на каких адресах срабатывает узнавание
|
||||
- **THEN** это адреса под корнем приложения, и второго списка адресов нет
|
||||
|
||||
#### Scenario: Проба здоровья учётной записи не заводит
|
||||
|
||||
- **GIVEN** учётной записи с этим значением ещё нет
|
||||
- **WHEN** запрос с заголовком приходит на `GET /health` с доверенного адреса
|
||||
- **THEN** учётной записи не появляется
|
||||
|
||||
#### Scenario: Недоверенный источник виден в журнале
|
||||
|
||||
- **WHEN** запрос с заголовком приходит с недоверенного адреса
|
||||
- **THEN** журнал несёт строку об этом исходе с адресом пира
|
||||
- **AND** значения заголовка в ней нет
|
||||
|
||||
#### Scenario: Сервис не ставит браузеру куки
|
||||
|
||||
- **GIVEN** адрес источника стоит в перечне доверенных
|
||||
- **WHEN** запрос к адресу приложения проходит с заголовком
|
||||
- **THEN** ответ не ставит браузеру ни куки сессии, ни иного значения доступа
|
||||
|
||||
#### Scenario: Значения заголовка нет в журнале
|
||||
|
||||
- **WHEN** запрос с заголовком `Remote-User` проходит через сервис
|
||||
- **THEN** значение заголовка не встречается ни в одной журнальной записи
|
||||
Reference in New Issue
Block a user