Documentation
¶
Overview ¶
Package revocationpolicy — ОДНО правило «отсечка отзыва-всех человека запрещает полномочие, возникшее не позже неё», общее для всех полос удостоверений человека: чеканящих ему токен и принимающих его базовый секрет (задача kaname#379).
Почему общий источник, а не одинаковая проверка в каждой полосе ¶
Токен человеку выдают несколько полос: обратный вызов прежнего провайдера на выпуске, он же на обновлении и наш собственный токен-эндпоинт. Правило судит ЛЮБУЮ записанную отсечку владельца — каким бы действием, выводящим человека отовсюду, она ни была записана: строка на человека одна, и кто её пишет, правилу не важно и перечнем здесь не закрывается (перепись путей записи держит проба `client_token_revoke_all_integration_test.go`). Вопрос «вышел ли этот человек отовсюду после того, как возникло предъявленное полномочие» у всех полос ОДИН. Копия правила в каждой полосе разошлась бы молча: каждая полоса по отдельности выглядела бы исправной, неверной была бы их РАЗНИЦА. Здесь копия одна, и расхождение невозможно by construction — тот же приём, что у `audiencepolicy`. Одна здесь и обёртка предела времени на чтение отсечки отдельным запросом (WithDeadline): предел — тоже часть того, как полоса отвечает.
Базовый секрет человека принимает полоса предъявления — резолв и вопрос о живости открытого соединения. Отсечку она читает тем же оператором, что строку удостоверения, а судит её тем же сравнением (Forbids) с якорем в момент выдачи строки — тем, что Anchor называет для долговременного удостоверения. Отдельного запроса отсечки у неё нет, поэтому и обёртки нет: предел у неё — предел самого оператора, поданный авторитету при построении той же величиной, которую корень подаёт обёртке.
Оба сравниваемых момента — от одного источника (задачи kaname#388, kaname#589) ¶
Правило сравнивает моменты, а не события: точность его границы — это точность того, что оба момента поставлены ОДНИМ источником времени. Отсечку «сейчас», момент выдачи долговременного удостоверения (вместе со сроком), момент аутентификации сессии и `iat` подписанта ставит один источник — Clock, поданный корнем всем их писателям. Перепись писателей держит `TestEveryMomentComparedWithTheCutoffComesFromTheSharedClock`, поведение на двух репликах со сдвинутыми часами — `user_tokens.TestRevokeAll_IssuanceAndCutoffShareOneClockOnEveryRuleLane`.
Почему часы базы, а не часы процесса. Реплик службы больше одной, и часы процесса у каждой свои: момент, поставленный одной репликой, судился бы отсечкой, поставленной другой, и граница правила была бы их расхождением. Первичная база одна на все реплики, и её же часами (`now()`) отсечку ставят схемные писатели — триггеры снятия клиента и деактивации владельца. Прежний выбор (часы процесса — потому что ими держались прочие моменты, а `iat` в базу не уходит) снимал расхождение двух источников только внутри одной реплики; теперь `iat` тоже ставит база, и довод отпал.
Источник не ответил — глагол отказывает (ErrClockUnavailable); запасного пути к часам процесса нет ни у одного писателя, и запись момента выдачи без момента — отказ записи, а не подстановка умолчания столбца.
Что остаётся вне этого правила и названо: после смены первичной базы источник — часы новой первичной, и расхождение её часов со старой — расхождение синхронизации времени хостов базы, одно на все сравнения. Момент берётся до транзакции писателя, поэтому выданное между чтением источника и фиксацией отсечки ею не покрыто — то же окно, что было при часах процесса. Исключение одно — сброс ключей доступа (`user.ResetAccessKeysUseCase`, kaname#638): записей сессии он не снимает, и его отсечка читает тот же источник соединением своей транзакции после захвата строки личности.
Что здесь решается, а что — у вызывающего ¶
Решается ВЕРДИКТ: по принципалу, моменту его полномочия и отсечке. Что вызывающий делает с вердиктом — его дело и его словарь: хук отвечает провайдеру своим кодом и пишет свою причину в аудит, эндпоинт считает свой исход закрытого словаря. Словари разные, вердикт один.
Index ¶
Constants ¶
This section is empty.
Variables ¶
ErrClockUnavailable — источник моментов не ответил. Несёт причину для журнала вызывающего; наружу не выходит.
var ErrLimitNotPositive = errors.New("revocationpolicy: per-call limit must be a positive duration")
ErrLimitNotPositive — предел на вызов, поданный обёртке, не положителен.
var ErrNoClock = errors.New("revocationpolicy: shared clock is not wired")
ErrNoClock — источник моментов не подан. Построение, получившее его, обязано отказать: полоса без источника не собирается, а не деградирует к часам процесса.
var ErrNoLookup = errors.New("revocationpolicy: revoke-all cutoff reader is not wired")
ErrNoLookup — читатель отсечки не подан.
var ErrPrincipalWithoutID = errors.New("revocationpolicy: principal is a person but carries no user id")
ErrPrincipalWithoutID — принципал назван человеком, но без идентификатора, по которому отсечка ключуется.
var ErrUnknownPrincipalKind = errors.New("revocationpolicy: principal kind is outside the closed dictionary")
ErrUnknownPrincipalKind — вид принципала вне словаря service.PrincipalKind.
Functions ¶
func Anchor ¶
Anchor — момент, от которого считается полномочие этого обмена.
У долговременного удостоверения (ключ, персональный токен) это момент его выдачи: оно не переаутентифицируется, и выданное до отсечки есть ровно то, что «выйти отовсюду» обязано прекратить. У интерактивной сессии — её собственный момент аутентификации: человек, вошедший заново после отсечки, доказал себя снова, и это то, что не даёт отсечке стать вечной блокировкой. Нулевой результат означает, что обмен не назвал ни того, ни другого.
func Forbids ¶
Forbids — само сравнение: запрещает ли отсечка полномочие, возникшее в момент anchor.
Граница включительна: полномочие, возникшее РОВНО в момент отсечки, запрещено — «не позже отсечки» и есть то, что она называет. Нулевой момент — запрет: полномочие, не назвавшее своего момента, нельзя показать возникшим после отсечки.
func Moment ¶
Moment — момент источника для записи, сравниваемой с отсечкой.
Ошибка — всегда ErrClockUnavailable (с причиной внутри); нулевой момент без ошибки — тоже она: нулевой момент правило читает как «возникло не позже любой отсечки» (Forbids), и записать его значило бы отозвать выданное в момент выдачи. Неподанный источник — ErrNoClock.
func MomentFailureClass ¶
MomentFailureClass — класс отказа источника для журнала: «deadline», «canceled», «unwired» или «store». Текст причины в журнал не идёт: ошибка драйвера несёт координаты соединения.
Types ¶
type Clock ¶
type Clock interface {
// Now — момент источника. Ошибка не сворачивается в нулевой момент.
Now(ctx context.Context) (time.Time, error)
}
Clock — ОДИН источник моментов, которые сравниваются с отсечкой отзыва-всех (задача kaname#589): отсечки «сейчас», выдачи долговременного удостоверения, аутентификации сессии. `iat` ставит подписант тем же источником через свой порт той же формы (`tokensigner.Clock`).
Реплик службы больше одной. Часы процесса у каждой свои, и правило, судящее момент одной реплики отсечкой, записанной другой, судило бы их расхождение, а не порядок событий. Источник один на все реплики by construction — первичная база (`kanamepg.SharedClock`). Её же часами отсечку ставят схемные писатели (триггеры снятия клиента и деактивации владельца), поэтому второго источника у сравнения не остаётся.
Вызов сетевой и потому может не ответить. Не ответил — это ОТКАЗ записи момента, а не подстановка часов процесса: момент от другого источника и есть дефект, который этот порт снимает. Запасного пути нет.
func ClockWithDeadline ¶
ClockWithDeadline оборачивает источник собственным пределом на КАЖДЫЙ вызов — той же величиной, что читатель отсечки (WithDeadline): момент и отсечка читаются одним видом запроса к одной базе.
Неположительный предел — отказ построения (ErrLimitNotPositive); неподанный источник — отказ построения (ErrNoClock): у источника моментов, в отличие от читателя отсечки, «не провязан» законным состоянием не бывает.
type Lookup ¶
type Lookup interface {
// UserRevokedBefore возвращает отсечку человека и признак её наличия.
// Ошибка не сворачивается в «отсечки нет»: недоступное хранилище — не
// ответ «нет».
UserRevokedBefore(ctx context.Context, userID string) (time.Time, bool, error)
// PersonMarks — второй вопрос правила (kaname#456, Р5): строки людей среди
// названных идентификаторов и подтверждён ли их текущий адрес. Тот же
// предикат допуска, что у двери решения и рубежа слушателей
// (`admission`); своего чтения отметки у правила нет.
admission.Marks
}
Lookup — отсечка отзыва-всех человека.
Форма совпадает с портами полос дословно, и это требование, а не совпадение: каждая полоса подаёт сюда читатель одной и той же строки — адаптер одного типа, обёрнутый WithDeadline с одним объявленным пределом.
func WithDeadline ¶
WithDeadline оборачивает читатель отсечки собственным пределом времени на КАЖДЫЙ вызов.
Одна обёртка на все полосы, а не своя у каждой: чтение одной строки одним запросом, несущее разный предел в зависимости от полосы, — то же расхождение полос, которое этот пакет снимает для вердикта. Каждая сборка полос (токен-эндпоинт, церемония, базовый секрет) оборачивает свой читатель ровно один раз этой функцией и объявленным пределом.
Предел — у обёртки, а не у вызывающего: вызывающих у читателя больше одного, и предел, выставленный у каждого по отдельности, у одного из них рано или поздно не появится — молча. Истёкший предел — ошибка чтения, то есть Undecidable, а не «отсечки нет».
Неположительный предел — ОТКАЗ ПОСТРОЕНИЯ (ErrLimitNotPositive), а не обёртка: контекст с таким сроком истёк в момент вызова, каждое чтение кончалось бы ошибкой, и полоса отказывала бы в выдаче всем — на первом запросе, а не на старте. Предел судится раньше читателя: величина неверна независимо от того, что оборачивается.
Неподанный читатель остаётся неподанным: обёртка над nil вернула бы непустое значение, и «читатель не провязан» перестал бы быть различимым.
type Verdict ¶
type Verdict string
Verdict — исход сверки. Закрытый словарь из ЧЕТЫРЁХ значений.
Не двух: «выдавать», «не выдавать — человек вышел отовсюду», «не выдавать — адрес владельца-человека не подтверждён» и «не выдавать — ответить на вопрос нечем» чинятся разными людьми и разными действиями (войти заново против подтвердить адрес против починить хранилище), и слитый счётчик не сказал бы оператору, что чинить. Нулевое значение типа НЕ является ни одним из четырёх: вызывающий, получивший его, обязан отказать — это закрытый словарь, а не корзина «прочее».
const ( // Allowed — отсечка выдаче не мешает. Allowed Verdict = "allowed" // Revoked — полномочие возникло не позже отсечки: это и есть то, что // отсечка называет. Revoked Verdict = "revoked" // Undecidable — вопрос задать нечем либо не у кого: хранилище не ответило, // читатель не подан, принципал-человек не назван идентификатором. Отказ, // а не разрешение: правило авторитетно и закрывается на неизвестном. Не // ответивший о любом из двух вопросов — этот вердикт. Undecidable Verdict = "undecidable" // Unverified — владелец-человек с неподтверждённым адресом (kaname#456, // Р5): удостоверение человеку не выдаётся, пока адрес не подтверждён. Unverified Verdict = "unverified" )
func AtIssuance ¶
func AtIssuance(ctx context.Context, cutoffs Lookup, p service.ResolvedPrincipal, sessionAuthTime time.Time) (verdict Verdict, cutoff time.Time, err error)
AtIssuance — вердикт отсечки для выдачи токена этому принципалу.
Вид принципала судится ЗАКРЫТЫМ словарём. Ключ служебной учётки — не сессия человека, а неразрешённый субъект не назван никем в этой службе: отсечка человека о них ничего не говорит, и читать её для них нечего. Для человека отсечка читается по его идентификатору в таблицах этой службы, а момент полномочия — по Anchor. Вид вне словаря — Undecidable: вид, заведённый позже рядом с тремя, иначе получал бы выдачу молча — тем же путём, что машина.
Ошибка возвращается ровно при Undecidable и несёт причину для журнала; наружу она не выходит — это забота вызывающего.
cutoff — стоящая отсечка человека, по которой вынесен вердикт Allowed (нулевая — отсечки нет либо принципал не человек). Выпуск обязан положить `iat` СТРОГО позже неё (`tokensigner.Request.IssuedAfter`, kaname#684): правило выдачи судит якорь в микросекундах, а `iat` — целые секунды, округлённые вниз, и токен, выпущенный в секунду отсечки после неё, правило отзыва на предъявлении сочло бы выпущенным не позже отсечки. При прочих вердиктах cutoff нулевая: выпуска нет.
func OwnerAdmission ¶
OwnerAdmission — второй вопрос правила о владельце-человеке (kaname#456, Р5): подтверждён ли его текущий адрес. Allowed — подтверждён (либо идентификатор не называет строки человека); Unverified — не подтверждён; Undecidable — спросить не смогли. Полоса, судящая отсечку своим оператором (базовый секрет), спрашивает этот вопрос здесь же, а не копией.