revocationpolicy

package
v0.6.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 24, 2026 License: AGPL-3.0 Imports: 6 Imported by: 0

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

View Source
var ErrClockUnavailable = errors.New("revocationpolicy: shared clock did not answer")

ErrClockUnavailable — источник моментов не ответил. Несёт причину для журнала вызывающего; наружу не выходит.

View Source
var ErrLimitNotPositive = errors.New("revocationpolicy: per-call limit must be a positive duration")

ErrLimitNotPositive — предел на вызов, поданный обёртке, не положителен.

View Source
var ErrNoClock = errors.New("revocationpolicy: shared clock is not wired")

ErrNoClock — источник моментов не подан. Построение, получившее его, обязано отказать: полоса без источника не собирается, а не деградирует к часам процесса.

View Source
var ErrNoLookup = errors.New("revocationpolicy: revoke-all cutoff reader is not wired")

ErrNoLookup — читатель отсечки не подан.

View Source
var ErrPrincipalWithoutID = errors.New("revocationpolicy: principal is a person but carries no user id")

ErrPrincipalWithoutID — принципал назван человеком, но без идентификатора, по которому отсечка ключуется.

View Source
var ErrUnknownPrincipalKind = errors.New("revocationpolicy: principal kind is outside the closed dictionary")

ErrUnknownPrincipalKind — вид принципала вне словаря service.PrincipalKind.

Functions

func Anchor

func Anchor(p service.ResolvedPrincipal, sessionAuthTime time.Time) time.Time

Anchor — момент, от которого считается полномочие этого обмена.

У долговременного удостоверения (ключ, персональный токен) это момент его выдачи: оно не переаутентифицируется, и выданное до отсечки есть ровно то, что «выйти отовсюду» обязано прекратить. У интерактивной сессии — её собственный момент аутентификации: человек, вошедший заново после отсечки, доказал себя снова, и это то, что не даёт отсечке стать вечной блокировкой. Нулевой результат означает, что обмен не назвал ни того, ни другого.

func Forbids

func Forbids(cutoff, anchor time.Time) bool

Forbids — само сравнение: запрещает ли отсечка полномочие, возникшее в момент anchor.

Граница включительна: полномочие, возникшее РОВНО в момент отсечки, запрещено — «не позже отсечки» и есть то, что она называет. Нулевой момент — запрет: полномочие, не назвавшее своего момента, нельзя показать возникшим после отсечки.

func Moment

func Moment(ctx context.Context, c Clock) (time.Time, error)

Moment — момент источника для записи, сравниваемой с отсечкой.

Ошибка — всегда ErrClockUnavailable (с причиной внутри); нулевой момент без ошибки — тоже она: нулевой момент правило читает как «возникло не позже любой отсечки» (Forbids), и записать его значило бы отозвать выданное в момент выдачи. Неподанный источник — ErrNoClock.

func MomentFailureClass

func MomentFailureClass(err error) string

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

func ClockWithDeadline(inner Clock, timeout time.Duration) (Clock, error)

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

func WithDeadline(inner Lookup, timeout time.Duration) (Lookup, error)

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

func OwnerAdmission(ctx context.Context, marks admission.Marks, userID string) (Verdict, error)

OwnerAdmission — второй вопрос правила о владельце-человеке (kaname#456, Р5): подтверждён ли его текущий адрес. Allowed — подтверждён (либо идентификатор не называет строки человека); Unverified — не подтверждён; Undecidable — спросить не смогли. Полоса, судящая отсечку своим оператором (базовый секрет), спрашивает этот вопрос здесь же, а не копией.

func Verdicts

func Verdicts() []Verdict

Verdicts — закрытый словарь вердиктов, копией.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL