1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
// SPDX-License-Identifier: MIT OR Apache-2.0
// Copyright (C) 2026 Matthew Jackson
//! RFC 9396 rich authorization requests: the `authorization_details` parameter, which says what a
//! client is asking for as STRUCTURE rather than as a scope string.
//!
//! # Why this exists
//!
//! A scope is a bare token. "payments" cannot say "transfer 50 euros to IBAN X, once, today", so
//! any deployment doing payments, or transaction-level consent of any kind, has to express the
//! transaction somewhere. RFC 9396 section 2 is that somewhere: a JSON array of objects, each with
//! a REQUIRED `type` that names whose vocabulary the rest of the object is written in.
//!
//! # The two rules this module is really about
//!
//! 1. **Unknown members are PRESERVED, not dropped.** Section 2 makes `type` the thing that
//! defines every other member, so an AS that keeps the members it recognises and silently
//! discards the rest has granted something OTHER than what was requested, and neither the
//! client nor the resource owner is told. [`AuthorizationDetail::other`] is where everything
//! this crate has no opinion about is kept, byte for byte, through storage, through
//! introspection and into the RFC 9068 token.
//! 2. **An unknown `type` is REFUSED, not ignored.** Section 5: "The AS MUST refuse to process any
//! unknown authorization details type or authorization details not conforming to the respective
//! type definition", answering `invalid_authorization_details`. Ignoring the element instead
//! would issue a token that says nothing about a permission the client believes it obtained.
//!
//! # Bounds, because this is attacker-supplied JSON
//!
//! `authorization_details` arrives unauthenticated at the authorization endpoint, before any user
//! interaction, in a parameter whose grammar is "some JSON". An AS that parses whatever it is sent
//! is a denial of service with extra steps, so four limits are applied. They are constants rather
//! than configuration on purpose: a host cannot accidentally raise them, and nothing in RFC 9396
//! asks for values larger than these.
//!
//! WHERE each one runs is stated precisely, because it decides what the limit actually buys:
//!
//! - [`MAX_AUTHORIZATION_DETAILS_BYTES`], on the RAW parameter, before `serde_json` is handed
//! anything. This is the only one that runs before a structure is built, and it is the one that
//! makes every cost below it finite, including the parser's own.
//! - [`MAX_AUTHORIZATION_DETAILS_ELEMENTS`], on the PARSED array,
//! - [`MAX_DETAIL_LIST_ENTRIES`], on each section 2.2 list member of a parsed element, which is
//! what bounds the quadratic narrowing at the token endpoint (the element cap alone does not:
//! see that constant), and
//! - [`MAX_AUTHORIZATION_DETAILS_DEPTH`], on nesting inside a parsed element.
//!
//! The last three run on an already-built tree. That is a deliberate choice and not an oversight:
//! applying them earlier would mean a second JSON scanner, written here, that has to agree with
//! `serde_json` about strings and escapes in order to count brackets correctly, and a second
//! scanner that disagrees with the first is a parser differential, which is a worse class of bug
//! than the one it would prevent. What makes it safe to let the parser run first is the byte bound
//! above it, and the arithmetic is worth writing down: 4096 bytes of raw JSON cannot express more
//! than about 2047 levels of nesting, since each level costs at least one byte, so the parser's
//! recursion and the resulting tree's recursive `Drop` are both bounded at a few hundred kilobytes
//! of stack whatever the input says. That argument depends on nothing but this crate's own
//! constant. `serde_json` also refuses past its own limit near 128, which would refuse such input
//! sooner, but that limit is a dependency's implementation detail rather than a contract and
//! nothing here is allowed to rest on it.
//!
//! See each constant for the number and why it is that number.
//!
//! # What this module does NOT decide
//!
//! Whether the resource owner should approve a detail. That is the host's consent screen, and RFC
//! 9396 section 7.1 explicitly allows the granted detail to differ from the requested one. This
//! module decides only what is well formed, what type names are admissible, and (the security core
//! of the slice) whether a later leg of the same grant is asking for MORE than was granted.
use ;
use ;
use crate;
/// The largest `authorization_details` parameter this server will parse, in bytes of raw JSON.
///
/// 4096 is chosen against the two places the parameter actually travels. At the authorization
/// endpoint it is a query parameter, so it is percent-encoded and lands somewhere near three times
/// this in the request line, which is the region where deployed proxies and browsers start
/// refusing URLs: a host fronted by one of those will refuse before this does, and that is fine,
/// because both answers are "no". At the token endpoint it is a form field, where nothing forces a
/// limit at all, which is exactly why one is imposed here.
///
/// It is generous against real payloads: every example in RFC 9396 (including the appendix A
/// payment initiation ones, which are the largest published) is a few hundred bytes. A request
/// that needs more than an order of magnitude over the RFC's own worst example is not a request
/// this server has to be able to serve.
pub const MAX_AUTHORIZATION_DETAILS_BYTES: usize = 4096;
/// The largest number of elements in the array (RFC 9396 section 2).
///
/// 16 distinct authorization details in ONE request is already beyond anything the RFC
/// illustrates; the point of the parameter is that a request names the specific things it needs.
/// The bound matters because narrowing at the token endpoint compares every requested element
/// against every granted one, which is quadratic in the element count: bounded at 16 that is at
/// most 256 calls to [`AuthorizationDetail::is_narrowing_of`], and unbounded it is a CPU denial of
/// service that a single request can aim at this server.
///
/// # 256 element PAIRS is not 256 comparisons
///
/// This constant used to claim it was, and that was wrong by three orders of magnitude.
/// `is_narrowing_of` is not O(1): it runs a subset check over four section 2.2 lists, and a subset
/// check is `subset.len() * superset.len()` string comparisons in the worst case. Until
/// [`MAX_DETAIL_LIST_ENTRIES`] existed, nothing bounded those lengths, so the element count factored
/// out entirely and the byte cap was the only thing left holding the line.
///
/// The arithmetic, counted rather than estimated. `[{"type":"t","actions":["a","a",...]}]` is 27
/// bytes of wrapper, so 4069 remain, and `"a",` is 4 bytes with no trailing comma on the last:
/// 1017 entries fit inside [`MAX_AUTHORIZATION_DETAILS_BYTES`]. Note that the entries do NOT have
/// to be distinct, and that is what makes the worst case reachable rather than merely describable:
/// the granted side is 1016 copies of `"z"` followed by one `"a"`, the requested side is 1017
/// copies of `"a"`, and every one of the 1017 `any` scans runs the whole superset before it
/// matches at the end. 1017 x 1017 = 1,034,289 `Box<str>` comparisons for ONE token request,
/// recurring on every refresh because the granted details ride the chain. (A fixture built from
/// DISTINCT entries cannot get there: three-digit entries are 6 bytes, and only 832 distinct
/// entries of any length fit at all. The distinctness was never the point; the position of the
/// match is.)
///
/// Splitting the budget across several elements or across the four lists cannot beat that, because
/// the total is the sum over lists of `requested_len * granted_len` against fixed sums of lengths,
/// which is largest when it is all in one list, and every extra element spends bytes on another
/// wrapper.
///
/// With both caps the product is finite from the constants alone: at most 16 x 16 element pairs,
/// each at most 4 x 16 x 16 comparisons, so at most 262,144. The REACHABLE figure is lower, because
/// an element carrying four full sixteen-entry lists is 323 bytes and only 12 of those fit in
/// [`MAX_AUTHORIZATION_DETAILS_BYTES`]: 12 x 12 x 4 x 16 x 16 = 147,456. So what the per-list cap
/// bought is a factor of seven on the worst case, and, more to the point, a worst case that both
/// factors of are constants in this file rather than a length the request chooses.
pub const MAX_AUTHORIZATION_DETAILS_ELEMENTS: usize = 16;
/// The largest number of entries in any ONE of the RFC 9396 section 2.2 list members
/// (`locations`, `actions`, `datatypes`, `privileges`) of a single element.
///
/// # Why this exists
///
/// [`MAX_AUTHORIZATION_DETAILS_ELEMENTS`] bounds how many elements a request may carry and nothing
/// else. The quadratic work at the token endpoint is over LIST ENTRIES, not elements, so without
/// this cap a request could put its whole byte budget into one element's `actions` and pay the
/// 1,034,289 comparisons the element cap was introduced to prevent. See that constant for the
/// arithmetic, which is counted rather than estimated.
///
/// # Why 16
///
/// The largest published RFC 9396 example lists three actions. Sixteen is the number this crate
/// already uses for every other repeatable thing a request carries
/// ([`MAX_AUTHORIZATION_DETAILS_ELEMENTS`], [`crate::server::MAX_RESOURCE_INDICATORS`]), so a
/// reader does not have to hold a fourth number, and a deployment naming more than sixteen actions
/// on one resource in one detail has an element that has stopped describing a transaction.
///
/// # It refuses, it does not truncate
///
/// Dropping the entries past the cap would grant a detail OTHER than the one requested, with
/// nothing told to anyone, which is the failure the whole of this module's `other` handling exists
/// to avoid. `invalid_authorization_details` (section 5) is the answer.
///
/// Applied by [`AuthorizationDetails::parse`], which is the wire. It is NOT applied by
/// [`AuthorizationDetails::from_elements`], for the reason given there: that is the host's own data.
/// The cap on the REQUESTED side is what bounds the narrowing regardless, since every subset check
/// is `requested.len() * granted.len()` and the requested side always came off the wire.
pub const MAX_DETAIL_LIST_ENTRIES: usize = 16;
/// The deepest JSON nesting allowed, counting the array itself as level 1 and an element object as
/// level 2.
///
/// 8 leaves six levels inside an element, which is more than any published RFC 9396 type uses
/// (appendix A's deepest is three).
///
/// It is enforced on the PARSED tree, so it is not what keeps the parser off the stack: read the
/// module docs for what does, which is [`MAX_AUTHORIZATION_DETAILS_BYTES`] and an argument that
/// rests on this crate's own constant rather than on `serde_json`'s recursion limit. What this
/// bound buys is that every recursive walk THIS module performs afterwards, over a value that will
/// be stored on an authorization code, carried across a rotation, and compared element by element
/// at every narrowing, is shallow by contract rather than by whatever the parser happened to
/// accept.
pub const MAX_AUTHORIZATION_DETAILS_DEPTH: usize = 8;
/// The depth budget left for a member VALUE inside an element: the total, minus the array and the
/// element object that necessarily enclose it.
const MAX_MEMBER_DEPTH: usize = MAX_AUTHORIZATION_DETAILS_DEPTH - 2;
/// One element of `authorization_details` (RFC 9396 section 2).
///
/// Every field is WRITE ONCE, so each is a `Box<str>` or a `Box<[Box<str>]>` rather than a `String`
/// or a `Vec`: nothing here is ever pushed to or truncated after parsing, and the capacity word a
/// growable type carries would be 8 bytes of nothing, per field, in every stored authorization
/// code, access token and refresh record for the life of the grant. That is 48 bytes an element
/// against roughly 190, and a store holds one of these per live grant, not one per process.
///
/// `type` is REQUIRED (section 2) and is what gives every other member its meaning. The named
/// fields below are the section 2.2 COMMON data fields, which are common precisely because their
/// meaning does not depend on the type; everything else lands in [`AuthorizationDetail::other`]
/// and is carried unchanged.
///
/// # Why `other` is not a convenience
///
/// It is the correctness of the whole feature. Section 2 says the type defines the object, so a
/// member this crate does not recognise is a member this crate is not entitled to have an opinion
/// about, least of all the opinion "it did not matter". Dropping it would mean the token that
/// comes back describes a different authorization from the one that was asked for and approved.
/// Every value in `subset` also appears in `superset`.
///
/// A linear scan rather than a set: these lists are bounded by [`MAX_DETAIL_LIST_ENTRIES`] and are
/// in practice two or three entries, so building a `BTreeSet` per comparison would allocate more
/// than it saved.
///
/// The bound named here used to be [`MAX_AUTHORIZATION_DETAILS_BYTES`], and that was the defect:
/// a byte cap of 4096 admits 1017 entries in one list, so this scan was
/// `subset.len() * superset.len()` over a length a request chose. See
/// [`MAX_AUTHORIZATION_DETAILS_ELEMENTS`] for the arithmetic. The cost is quadratic either way;
/// what changed is that both factors are now constants.
/// `skip_serializing_if` for the section 2.2 list members. A named function rather than
/// `<[_]>::is_empty` because `skip_serializing_if` takes a path and the `Box<[T]>` receiver has to
/// reach `&[T]` by deref coercion, which only happens at a call site.
/// The `authorization_details` array (RFC 9396 section 2).
///
/// Empty is the ordinary case and is what "the client sent no `authorization_details`" means; an
/// empty `Vec` allocates nothing, so a deployment that never uses RAR pays for the field and
/// nothing else. That mirrors [`crate::authorization::AuthorizationCodeRecord::resource`], which
/// made the same choice for RFC 8707 for the same reason.
;
/// The nesting depth of one JSON value: 1 for a scalar, 1 + the deepest child for a container.
///
/// Recursive, and safely so, but NOT because of `serde_json`'s recursion limit, which is a
/// dependency's implementation detail. The tree this walks came from at most
/// [`MAX_AUTHORIZATION_DETAILS_BYTES`] of raw JSON, and a level of nesting costs at least a byte,
/// so it is at most about 2047 deep however the parser was configured. The check exists to make
/// the bound THIS crate promises ([`MAX_AUTHORIZATION_DETAILS_DEPTH`]) an actual refusal rather
/// than a comment.