• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

namib-project / dcaf-rs / 11935120896

20 Nov 2024 02:11PM UTC coverage: 86.555% (+1.3%) from 85.242%
11935120896

Pull #27

github

web-flow
Merge d2b3d706b into 383248641
Pull Request #27: ci: update grcov to latest stable version

6116 of 7066 relevant lines covered (86.56%)

167.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.65
/src/common/scope/mod.rs
1
/*
2
 * Copyright (c) 2022, 2024 The NAMIB Project Developers.
3
 * Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4
 * https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5
 * <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6
 * option. This file may not be copied, modified, or distributed
7
 * except according to those terms.
8
 *
9
 * SPDX-License-Identifier: MIT OR Apache-2.0
10
 */
11

12
//! Contains data types and methods for working with OAuth scopes.
13
//!
14
//! The main use case of this module is creating [Scope] instances for either text-,
15
//! binary-, or AIF-encoded scopes,
16
//! whose elements can then be extracted using the `elements()` method.
17
//!
18
//! # Example
19
//! For example, you could first create a text-, binary-, or AIF-encoded scope:
20
//! ```
21
//! # use std::error::Error;
1✔
22
//! # use dcaf::common::scope::{AifEncodedScopeElement, AifRestMethod, AifRestMethodSet, BinaryEncodedScope, TextEncodedScope};
23
//! # use dcaf::error::{InvalidBinaryEncodedScopeError, InvalidTextEncodedScopeError};
24
//! # use dcaf::{AifEncodedScope, Scope};
25
//! // Will be encoded with a space-separator.
26
//! # #[cfg(feature = "std")] {
27
//! let text_scope = TextEncodedScope::try_from(vec!["first_client", "second_client"])?;
28
//! assert_eq!(text_scope.to_string(), "first_client second_client");
1✔
29
//! assert!(text_scope.elements().eq(vec!["first_client", "second_client"]));
1✔
30
//!
1✔
31
//! // Separator is only specified upon `elements` call.
32
//! let binary_scope = BinaryEncodedScope::try_from(vec![1, 2, 0, 3, 4].as_slice())?;
33
//! assert!(binary_scope.elements(Some(0))?.eq(&vec![&vec![1, 2], &vec![3, 4]]));
1✔
34
//! # }
1✔
35
//!
36
//! // Will be encoded as path and REST-method-set pairs.
37
//! let aif_scope = AifEncodedScope::from(vec![
38
//!    ("/s/temp", AifRestMethod::Get.into()), ("/none", AifRestMethodSet::empty())
1✔
39
//! ]);
1✔
40
//! assert_eq!(aif_scope.elements(), &vec![
1✔
41
//!    AifEncodedScopeElement::new("/s/temp", AifRestMethod::Get),
1✔
42
//!    AifEncodedScopeElement::new("/none", AifRestMethodSet::empty())
1✔
43
//! ]);
1✔
44
//! # Ok::<(), Box<dyn Error>>(())
1✔
45
//! ```
1✔
46
//! And then you could wrap it in the [Scope] type and use it in a field,
1✔
47
//! e.g. in an [`AuthServerRequestCreationHint`](crate::AuthServerRequestCreationHint):
48
//! ```
49
//! # use std::error::Error;
1✔
50
//! # use dcaf::common::scope::{BinaryEncodedScope, TextEncodedScope};
51
//! # use dcaf::{AuthServerRequestCreationHint, Scope};
52
//! # use dcaf::endpoints::creation_hint::AuthServerRequestCreationHintBuilderError;
53
//! # #[cfg(feature = "std")] {
54
//! # let text_scope = TextEncodedScope::try_from(vec!["first_client", "second_client"])?;
55
//! # let original_scope = text_scope.clone();
1✔
56
//! # let binary_scope = BinaryEncodedScope::try_from(vec![1, 2, 0, 3, 4].as_slice())?;
1✔
57
//! let hint: AuthServerRequestCreationHint = AuthServerRequestCreationHint::builder().scope(Scope::from(text_scope)).build()?;
1✔
58
//! # assert_eq!(hint.scope, Some(Scope::from(original_scope)));
1✔
59
//! # }
1✔
60
//! # Ok::<(), Box<dyn Error>>(())
61
//! ```
1✔
62
//! This works with the binary encoded scope too, of course:
1✔
63
//! ```
64
//! # use std::error::Error;
1✔
65
//! # use dcaf::common::scope::{BinaryEncodedScope, TextEncodedScope};
66
//! # use dcaf::{AuthServerRequestCreationHint, Scope};
67
//! # use dcaf::endpoints::creation_hint::AuthServerRequestCreationHintBuilderError;
68
//! # #[cfg(feature = "std")] {
69
//! # let binary_scope = BinaryEncodedScope::try_from(vec![1, 2, 0, 3, 4].as_slice())?;
70
//! # let original_scope = binary_scope.clone();
1✔
71
//! let hint: AuthServerRequestCreationHint = AuthServerRequestCreationHint::builder().scope(Scope::from(binary_scope)).build()?;
1✔
72
//! # assert_eq!(hint.scope, Some(Scope::from(original_scope)));
1✔
73
//! # }
1✔
74
//! # Ok::<(), Box<dyn Error>>(())
75
//! ```
1✔
76
//! As well as with the AIF-encoded scope:
1✔
77
//! ```
78
//! # use std::error::Error;
1✔
79
//! # use dcaf::common::scope::{AifEncodedScope, AifRestMethod, AifRestMethodSet};
80
//! # use dcaf::{AuthServerRequestCreationHint, Scope};
81
//! # use dcaf::endpoints::creation_hint::AuthServerRequestCreationHintBuilderError;
82
//! # let aif_scope = AifEncodedScope::from(vec![
83
//! #    ("/s/temp", AifRestMethod::Get.into()), ("/none", AifRestMethodSet::empty())
1✔
84
//! # ]);
1✔
85
//! # let original_scope = aif_scope.clone();
1✔
86
//! # #[cfg(feature = "std")] {
1✔
87
//! let hint: AuthServerRequestCreationHint = AuthServerRequestCreationHint::builder().scope(Scope::from(aif_scope)).build()?;
88
//! # assert_eq!(hint.scope, Some(Scope::from(original_scope)));
1✔
89
//! # }
1✔
90
//! # Ok::<(), Box<dyn Error>>(())
91
//! ```
1✔
92
//!
1✔
93
//! # Sources
94
//! For the original OAuth 2.0 standard, scopes are defined in
95
//! [RFC 6749, section 1.3](https://www.rfc-editor.org/rfc/rfc6749#section-1.3),
96
//! while for ACE-OAuth, they're specified in
97
//! [RFC 9200, section 5.8.1](https://www.rfc-editor.org/rfc/rfc9200#section-5.8.1-2.4).
98
//! AIF is defined in [RFC 9237](https://www.rfc-editor.org/rfc/rfc9237).
99

100
use {alloc::string::String, alloc::string::ToString, alloc::vec, alloc::vec::Vec};
101

102
use core::fmt::{Display, Formatter};
103

104
use enumflags2::{bitflags, BitFlags};
105
use serde::{Deserialize, Serialize};
106
use strum_macros::IntoStaticStr;
107

108
use crate::common::cbor_values::ByteString;
109

110
#[cfg(test)]
111
mod tests;
112

113
/// A set of [`AifRestMethod`]s, represented as bitflags.
114
/// Intended to be used in [`AifEncodedScope`]s.
115
///
116
/// In order to create an instance of this type, simply do one of the following things:
117
/// ```
118
/// # use std::error::Error;
1✔
119
/// # use enumflags2::make_bitflags;
120
/// # use dcaf::common::scope::{AifRestMethod, AifRestMethodSet};
121
/// // By bitwise operators:
122
/// let multiple_or: AifRestMethodSet = AifRestMethod::Get | AifRestMethod::Put;
123
/// assert!(multiple_or.contains(AifRestMethod::Get) && multiple_or.contains(AifRestMethod::Put));
1✔
124
/// # assert_eq!(multiple_or.len(), 2);
1✔
125
/// // By the `make_bitflags` macro, to be more compact:
1✔
126
/// let multiple_macro: AifRestMethodSet = make_bitflags!(AifRestMethod::{Get | Put});
127
/// assert!(multiple_macro.contains(AifRestMethod::Get | AifRestMethod::Put));
1✔
128
/// # assert_eq!(multiple_macro.len(), 2);
1✔
129
/// // Or by the methods defined on `AifRestMethodSet`:
1✔
130
/// let single = AifRestMethodSet::try_from(AifRestMethod::Get)?;
131
/// assert!(single.exactly_one().filter(|x| x == &AifRestMethod::Get).is_some());
1✔
132
/// let empty = AifRestMethodSet::empty();
1✔
133
/// assert!(empty.is_empty());
1✔
134
/// let all = AifRestMethodSet::all();
1✔
135
/// assert!(all.is_all());
1✔
136
/// # Ok::<(), Box<dyn Error>>(())
1✔
137
/// ```
1✔
138
pub type AifRestMethodSet = BitFlags<AifRestMethod>;
1✔
139

140
/// A scope encoded as a space-delimited list of strings, as defined in
141
/// [RFC 6749, section 1.3](https://www.rfc-editor.org/rfc/rfc6749#section-1.3).
142
///
143
/// Note that the syntax specified in the RFC has to be followed:
144
/// ```text
145
/// scope       = scope-token *( SP scope-token )
146
/// scope-token = 1*( %x21 / %x23-5B / %x5D-7E )
147
/// ```
148
///
149
/// # Example
150
///
151
/// You can create a `TextEncodedScope` from a space-separated string:
152
/// ```
153
/// # use dcaf::common::scope::TextEncodedScope;
1✔
154
/// # use dcaf::error::InvalidTextEncodedScopeError;
155
/// let scope = TextEncodedScope::try_from("first second third")?;
156
/// assert!(scope.elements().eq(["first", "second", "third"]));
1✔
157
/// # Ok::<(), InvalidTextEncodedScopeError>(())
1✔
158
/// ```
1✔
159
/// It's also possible to pass in a vector of strings:
1✔
160
/// ```
161
/// # use dcaf::common::scope::TextEncodedScope;
1✔
162
/// # use dcaf::error::InvalidTextEncodedScopeError;
163
/// let scope = TextEncodedScope::try_from(vec!["first", "second", "third"])?;
164
/// assert!(scope.elements().eq(["first", "second", "third"]));
1✔
165
/// assert!(TextEncodedScope::try_from(vec!["not allowed"]).is_err());
1✔
166
/// # Ok::<(), InvalidTextEncodedScopeError>(())
1✔
167
/// ```
1✔
168
///
1✔
169
/// But note that you have to follow the syntax from the RFC (which implicitly specifies
170
/// that given scopes can't be empty):
171
/// ```
172
/// # use dcaf::common::scope::TextEncodedScope;
1✔
173
/// assert!(TextEncodedScope::try_from("can't use \\ or \"").is_err());
174
/// assert!(TextEncodedScope::try_from("  no   weird spaces ").is_err());
1✔
175
/// assert!(TextEncodedScope::try_from(vec![]).is_err());
1✔
176
/// ```
1✔
177
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize)]
1✔
178
pub struct TextEncodedScope(String);
179

180
impl Display for TextEncodedScope {
181
    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
82✔
182
        write!(f, "{}", self.0)
82✔
183
    }
82✔
184
}
185

186
/// A scope encoded using a custom binary encoding.
187
/// See [Scope] for more information.
188
///
189
/// # Example
190
///
191
/// Simply create a `BinaryEncodedScope` from a byte array (we're using the byte `0x21` as
192
/// a separator in this example):
193
/// ```
194
/// # use dcaf::common::scope::{BinaryEncodedScope};
1✔
195
/// # use dcaf::error::InvalidBinaryEncodedScopeError;
196
/// let scope = BinaryEncodedScope::try_from(vec![0x00, 0x21, 0xDC, 0xAF].as_slice())?;
197
/// assert!(scope.elements(Some(0x21))?.eq(&vec![vec![0x00], vec![0xDC, 0xAF]]));
1✔
198
/// # Ok::<(), InvalidBinaryEncodedScopeError>(())
1✔
199
/// ```
1✔
200
///
1✔
201
/// But note that the input array can't be empty:
202
/// ```
203
/// # use dcaf::common::scope::BinaryEncodedScope;
1✔
204
/// assert!(BinaryEncodedScope::try_from(vec![].as_slice()).is_err());
205
/// ```
1✔
206
///
1✔
207
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize)]
208
pub struct BinaryEncodedScope(ByteString);
209

210
/// REST (CoAP or HTTP) methods, intended for use in an [`AifEncodedScopeElement`].
211
///
212
/// In order to create a bitmask, simply use one of the following on an enum variant:
213
/// - bitwise operators (e.g., `AifRestMethod::Get | AifRestMethod::Put`)
214
/// - `make_bitflags` macro (e.g., `make_bitflags!(AifRestMethod::{Get,Put})`)
215
/// - `From` implementations (e.g., `AifRestMethod::Get.into()`)
216
///
217
/// Note that in addition to the usual CoAP and HTTP REST methods
218
/// (see "Relevant Documents" below),
219
/// methods for [Dynamic Resource Creation](https://www.rfc-editor.org/rfc/rfc9237#section-2.3)
220
/// are also provided.
221
///
222
/// This uses the [`enumflags2`] crate to make it easy to work with resulting bitmasks.
223
///
224
/// # Relevant Documents
225
/// - Definition of `REST-method-set` data model for use in AIF:
226
///   Figure 4 of [RFC 9237](https://www.rfc-editor.org/rfc/rfc9237#figure-4)
227
/// - Specification of HTTP methods GET, POST, PUT, DELETE: [RFC 7231, section 4.3](https://datatracker.ietf.org/doc/html/rfc7231#section-4.3)
228
/// - Specification of HTTP PATCH method: [RFC 5789](https://datatracker.ietf.org/doc/html/rfc5789)
229
/// - Specification of CoAP methods GET, POST, PUT, DELETE: [RFC 7252, section 5.8](https://datatracker.ietf.org/doc/html/rfc7252#section-5.8),
230
/// - Specification of CoAP methods FETCH, PATCH, AND iPATCH: [RFC 8132](https://datatracker.ietf.org/doc/html/rfc8132)
231
/// - Specification of Dynamic CoAP methods:
232
///   [RFC 9237, section 2.3](https://www.rfc-editor.org/rfc/rfc9237#section-2.3)
233
///
234
/// # Example
235
/// You can easily combine multiple fields using the bitwise OR operator, as well as
236
/// using the built-in methods from [`enumflags2`].
237
/// Created bitmasks can then be used in an [AifEncodedScopeElement]:
238
/// ```
239
/// # use dcaf::AifEncodedScope;
1✔
240
/// # use dcaf::common::scope::{AifEncodedScopeElement, AifRestMethod, AifRestMethodSet};
241
/// let get = AifEncodedScopeElement::new("restricted", AifRestMethod::Get);
242
/// let multiple = AifEncodedScopeElement::new("less_restricted",
1✔
243
///                                            AifRestMethod::Get | AifRestMethod::Fetch);
1✔
244
/// // GET equals 2^0, FETCH equals 2^4
1✔
245
/// assert_eq!(multiple.permissions.bits(), 0b1 | 0b10000);
1✔
246
/// let all = AifEncodedScopeElement::new("unrestricted", AifRestMethodSet::all());
1✔
247
/// ```
1✔
248
/// These elements can in turn be used in an [AifEncodedScope] (or [LibdcafEncodedScope]):
1✔
249
/// ```
250
/// # use dcaf::AifEncodedScope;
1✔
251
/// # use dcaf::common::scope::{AifEncodedScopeElement, AifRestMethod, AifRestMethodSet};
252
/// # let get = AifEncodedScopeElement::new("restricted", AifRestMethod::Get);
253
/// # let multiple = AifEncodedScopeElement::new("less_restricted",
1✔
254
/// #                                            AifRestMethod::Get | AifRestMethod::Fetch);
1✔
255
/// # let all = AifEncodedScopeElement::new("unrestricted", AifRestMethodSet::all());
1✔
256
/// let scope = AifEncodedScope::new(vec![get, multiple, all]);
1✔
257
/// ```
1✔
258
#[bitflags]
2,560✔
259
#[derive(Serialize, Deserialize, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
14✔
260
#[repr(u64)]
261
pub enum AifRestMethod {
262
    /// GET method as specified in [RFC 7252, section 5.8.1 (CoAP)](https://datatracker.ietf.org/doc/html/rfc7252#section-5.8.1)
263
    /// and [RFC 7231, section 4.3.1 (HTTP)](https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.1).
264
    Get = u64::pow(2, 0),
265

266
    /// POST method as specified in [RFC 7252, section 5.8.2 (CoAP)](https://datatracker.ietf.org/doc/html/rfc7252#section-5.8.2)
267
    /// and [RFC 7231, section 4.3.3 (HTTP)](https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.3).
268
    Post = u64::pow(2, 1),
269

270
    /// PUT method as specified in [RFC 7252, section 5.8.3 (CoAP)](https://datatracker.ietf.org/doc/html/rfc7252#section-5.8.3)
271
    /// and [RFC 7231, section 4.3.4 (HTTP)](https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.4).
272
    Put = u64::pow(2, 2),
273

274
    /// DELETE method as specified in [RFC 7252, section 5.8.4 (CoAP)](https://datatracker.ietf.org/doc/html/rfc7252#section-5.8.4)
275
    /// and [RFC 7231, section 4.3.5 (HTTP)](https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.5).
276
    Delete = u64::pow(2, 3),
277

278
    /// FETCH method as specified in [RFC 8132, section 2 (CoAP)](https://datatracker.ietf.org/doc/html/rfc8132#section-2).
279
    ///
280
    /// Not available for HTTP.
281
    Fetch = u64::pow(2, 4),
282

283
    /// PATCH method as specified in [RFC 8132, section 3 (CoAP)](https://datatracker.ietf.org/doc/html/rfc8132#section-3).
284
    ///
285
    /// Not available for HTTP.
286
    Patch = u64::pow(2, 5),
287

288
    /// iPATCH method as specified in [RFC 8132, section 3 (CoAP)](https://datatracker.ietf.org/doc/html/rfc8132#section-3).
289
    ///
290
    /// Not available for HTTP.
291
    IPatch = u64::pow(2, 6),
292

293
    /// GET method as specified in [RFC 7252, section 5.8.1 (CoAP)](https://datatracker.ietf.org/doc/html/rfc7252#section-5.8.1)
294
    /// and [RFC 7231, section 4.3.1 (HTTP)](https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.1),
295
    /// intended for use in [Dynamic Resource Creation](https://www.rfc-editor.org/rfc/rfc9237#section-2.3).
296
    DynamicGet = u64::pow(2, 32),
297

298
    /// POST method as specified in [RFC 7252, section 5.8.2 (CoAP)](https://datatracker.ietf.org/doc/html/rfc7252#section-5.8.2)
299
    /// and [RFC 7231, section 4.3.3 (HTTP)](https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.3),
300
    /// intended for use in [Dynamic Resource Creation](https://www.rfc-editor.org/rfc/rfc9237#section-2.3).
301
    DynamicPost = u64::pow(2, 33),
302

303
    /// PUT method as specified in [RFC 7252, section 5.8.3 (CoAP)](https://datatracker.ietf.org/doc/html/rfc7252#section-5.8.3)
304
    /// and [RFC 7231, section 4.3.4 (HTTP)](https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.4),
305
    /// intended for use in [Dynamic Resource Creation](https://www.rfc-editor.org/rfc/rfc9237#section-2.3).
306
    DynamicPut = u64::pow(2, 34),
307

308
    /// DELETE method as specified in [RFC 7252, section 5.8.4 (CoAP)](https://datatracker.ietf.org/doc/html/rfc7252#section-5.8.4)
309
    /// and [RFC 7231, section 4.3.5 (HTTP)](https://datatracker.ietf.org/doc/html/rfc7231#section-4.3.5),
310
    /// intended for use in [Dynamic Resource Creation](https://www.rfc-editor.org/rfc/rfc9237#section-2.3).
311
    DynamicDelete = u64::pow(2, 35),
312

313
    /// FETCH method as specified in [RFC 8132, section 2 (CoAP)](https://datatracker.ietf.org/doc/html/rfc8132#section-2),
314
    /// intended for use in [Dynamic Resource Creation](https://www.rfc-editor.org/rfc/rfc9237#section-2.3).
315
    ///
316
    /// Not available for HTTP.
317
    DynamicFetch = u64::pow(2, 36),
318

319
    /// PATCH method as specified in [RFC 8132, section 3 (CoAP)](https://datatracker.ietf.org/doc/html/rfc8132#section-3),
320
    /// intended for use in [Dynamic Resource Creation](https://www.rfc-editor.org/rfc/rfc9237#section-2.3).
321
    ///
322
    /// Not available for HTTP.
323
    DynamicPatch = u64::pow(2, 37),
324

325
    /// iPATCH method as specified in [RFC 8132, section 3 (CoAP)](https://datatracker.ietf.org/doc/html/rfc8132#section-3),
326
    /// intended for use in [Dynamic Resource Creation](https://www.rfc-editor.org/rfc/rfc9237#section-2.3).
327
    ///
328
    /// Not available for HTTP.
329
    DynamicIPatch = u64::pow(2, 38),
330
}
331

332
/// An element as part of an [`AifEncodedScope`], consisting of a path and a set of permissions
333
/// which are specified as a set of REST methods.
334
///
335
/// See [`AifEncodedScope`] for more information and a usage example.
336
///
337
/// Can also be used as the single member of a [`LibdcafEncodedScope`].
338
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
339
pub struct AifEncodedScopeElement {
340
    /// Identifier for the object of this scope element,
341
    /// given as a URI of a resource on a CoAP server.
342
    ///
343
    /// Refer to [section 2 of RFC 9237](https://www.rfc-editor.org/rfc/rfc9237#section-2)
344
    /// for specification details.
345
    pub path: String,
346

347
    /// Permissions for the object (identified by [path](AifEncodedScopeElement::path))
348
    /// of this scope element, given as a set of REST (CoAP or HTTP) methods.
349
    ///
350
    /// More specifically, this is a bitmask---see [`AifRestMethod`] for further explanation.
351
    /// Refer to [section 2 of RFC 9237](https://www.rfc-editor.org/rfc/rfc9237#section-2)
352
    /// for specification details.
353
    pub permissions: BitFlags<AifRestMethod>,
354
}
355

356
/// A scope encoded using the
357
/// [Authorization Information Format (AIF) for ACE](https://www.rfc-editor.org/rfc/rfc9237).
358
///
359
/// More specifically, this uses the specific instantiation of AIF intended for REST resources
360
/// which are identified by URI paths, as described in [RFC 9237, section 2.1](https://www.rfc-editor.org/rfc/rfc9237#section-2.1).
361
/// An AIF-encoded scope consists of [`AifEncodedScopeElement`]s, each describing a URI path
362
/// (the object of the scope) and a set of REST methods (the permissions of the scope).
363
///
364
/// Note that the [`libdcaf` implementation](https://gitlab.informatik.uni-bremen.de/DCAF/dcaf)
365
/// uses a format in which only a single [`AifEncodedScopeElement`] is used in the scope.
366
/// To use this format, please use the [`LibdcafEncodedScope`] instead.
367
///
368
/// # Example
369
/// For example, say you want to create a scope consisting of two elements:
370
/// - A scope for the local path `/restricted`,
371
///   consisting only of "read-only" methods GET and FETCH.
372
/// - A scope for the local path `/unrestricted`, allowing every method.
373
///
374
/// This would look like the following:
375
/// ```
376
/// # use dcaf::AifEncodedScope;
1✔
377
/// # use dcaf::common::scope::{AifEncodedScopeElement, AifRestMethod, AifRestMethodSet};
378
/// let restricted = AifEncodedScopeElement::new("restricted", AifRestMethod::Get | AifRestMethod::Fetch);
379
/// let unrestricted = AifEncodedScopeElement::new("unrestricted", AifRestMethodSet::all());
1✔
380
/// let scope = AifEncodedScope::new(vec![restricted, unrestricted]);
1✔
381
/// # let restricted = AifEncodedScopeElement::new("restricted", AifRestMethod::Get | AifRestMethod::Fetch);
1✔
382
/// # let unrestricted = AifEncodedScopeElement::new("unrestricted", AifRestMethodSet::all());
1✔
383
/// assert_eq!(scope.elements(), &vec![restricted, unrestricted])
1✔
384
/// ```
1✔
385
///
1✔
386
/// ## Encoding
387
/// The scope from the example above would be encoded like this (given in JSON):
388
/// ```json
389
/// [["restricted", 17], ["unrestricted", 545460846719]]
390
/// ```
391
/// As specified in [RFC 9237, section 3](https://www.rfc-editor.org/rfc/rfc9237#section-3),
392
/// `GET` to `iPATCH` are encoded from 2<sup>0</sup> to 2<sup>6</sup>, while the dynamic variants
393
/// go from 2<sup>32</sup> to 2<sup>38</sup>. This is why in `restricted`, the number equals
394
/// 17 (2<sup>0</sup> + 2<sup>4</sup>), and in `unrestricted` equals the sum of all these numbers.
395
/// [`AifRestMethod`] does the work on this (including encoding and decoding bitmasks given as
396
/// numbers), clients do not need to handle this themselves and can simply use its methods together
397
/// with the methods provided by [`AifRestMethodSet`].
398
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize)]
399
pub struct AifEncodedScope(Vec<AifEncodedScopeElement>);
400

401
/// A scope encoded using the [Authorization Information Format (AIF) for ACE](https://www.rfc-editor.org/rfc/rfc9237)
402
/// as in [`AifEncodedScope`], but only consisting of a single [`AifEncodedScopeElement`]
403
/// instead of an array of them.
404
///
405
/// This is done to provide interoperability support for the
406
/// [`libdcaf` implementation](https://gitlab.informatik.uni-bremen.de/DCAF/dcaf),
407
/// which currently uses this format to describe its scopes.
408
///
409
/// *This struct is only provided to allow compatability with the
410
/// [`libdcaf` implementation](https://gitlab.informatik.uni-bremen.de/DCAF/dcaf)---if you don't
411
/// require this, simply use the spec-compliant [`AifEncodedScope`] instead, as it provides a
412
/// superset of the functionality of this type.*
413
///
414
/// Refer to [`AifEncodedScope`] for details on the format, and "Difference to [`AifEncodedScope`]"
415
/// for details on the difference to it.
416
///
417
/// # Example
418
/// To create a scope allowing only the GET and FETCH methods to be called the local URI `readonly`:
419
/// ```
420
/// # use dcaf::common::scope::{AifEncodedScopeElement, AifRestMethod};
1✔
421
/// # use dcaf::LibdcafEncodedScope;
422
/// let scope = LibdcafEncodedScope::new("readonly", AifRestMethod::Get | AifRestMethod::Fetch);
423
/// assert_eq!(scope.element().permissions.bits(), u64::pow(2, 0) + u64::pow(2, 4));
1✔
424
/// ```
1✔
425
///
1✔
426
/// # Difference to [`AifEncodedScope`]
427
/// The only difference here is that while [`AifEncodedScope`] would encode the above example
428
/// like so (given as JSON):
429
/// ```json
430
/// [["readonly", 17]]
431
/// ```
432
/// [`LibdcafEncodedScope`] would encode it like so:
433
/// ```json
434
/// ["readonly", 17]
435
/// ```
436
/// Note that this implies that the latter only allows a single scope element (i.e. a single row
437
/// in the access matrix) to be specified, while the former allows multiple elements.
438
/// As mentioned in the beginning, only use this struct if you need to communicate with libdcaf,
439
/// use [`AifEncodedScope`] in all other cases.
440
#[derive(Debug, PartialEq, Eq, Clone, Hash, Serialize)]
441
pub struct LibdcafEncodedScope(AifEncodedScopeElement);
442

443
/// Scope of an access token as specified in
444
/// [RFC 9200, section 5.8.1](https://www.rfc-editor.org/rfc/rfc9200#section-5.8.1-2.4).
445
///
446
/// May be used both for [AccessTokenRequest](crate::AccessTokenRequest)s and
447
/// [AccessTokenResponse](crate::AccessTokenResponse)s.
448
/// Note that you rarely need to create instances of this type for that purpose,
449
/// instead you can just pass in the concrete types (e.g. [TextEncodedScope], [BinaryEncodedScope])
450
/// directly into the builder.
451
///
452
/// # Example
453
///
454
/// You can create binary, AIF-, or text-encoded scopes:
455
/// ```
456
/// # use std::error::Error;
1✔
457
/// # use dcaf::common::scope::{BinaryEncodedScope, Scope, TextEncodedScope, AifEncodedScope, AifRestMethod};
458
/// # use dcaf::error::{InvalidTextEncodedScopeError, InvalidBinaryEncodedScopeError};
459
/// # #[cfg(feature = "std")] {
460
/// let text_scope = Scope::from(TextEncodedScope::try_from("dcaf rs")?);
461
/// let binary_scope = Scope::from(BinaryEncodedScope::try_from(vec![0xDC, 0xAF].as_slice())?);
1✔
462
/// let aif_scope = Scope::from(AifEncodedScope::from(vec![("/tmp", AifRestMethod::Get.into())]));
1✔
463
/// # }
1✔
464
/// # Ok::<(), Box<dyn Error>>(())
1✔
465
/// ```
1✔
466
///
1✔
467
/// For information on how to initialize a specific scope type
468
/// or retrieve the individual elements inside them, see their respective documentation pages.
469
#[derive(Debug, PartialEq, Eq, Clone, Hash, IntoStaticStr)]
470
pub enum Scope {
471
    /// Scope encoded using Text, as specified in
472
    /// [RFC 6749, section 1.3](https://www.rfc-editor.org/rfc/rfc6749#section-1.3).
473
    ///
474
    /// For details, see the documentation of [`TextEncodedScope`].
475
    ///
476
    /// # Example
477
    /// Creating a scope containing "device_alpha" and "device_beta" (note that spaces in their
478
    /// name wouldn't work):
479
    /// ```
480
    /// # use dcaf::common::scope::TextEncodedScope;
1✔
481
    /// # use dcaf::error::InvalidTextEncodedScopeError;
482
    /// let scope = TextEncodedScope::try_from(vec!["device_alpha", "device_beta"])?;
483
    /// assert_eq!(scope, TextEncodedScope::try_from("device_alpha device_beta")?);
1✔
484
    /// assert!(scope.elements().eq(vec!["device_alpha", "device_beta"]));
1✔
485
    /// assert!(TextEncodedScope::try_from(vec!["device alpha", "device beta"]).is_err());
1✔
486
    /// # Ok::<(), InvalidTextEncodedScopeError>(())
1✔
487
    /// ```
1✔
488
    TextEncoded(TextEncodedScope),
1✔
489

490
    /// Scope encoded using custom binary encoding.
491
    ///
492
    /// For details, see the documentation of [`BinaryEncodedScope`].
493
    ///
494
    /// # Example
495
    /// Creating a scope containing 0xDCAF and 0xAFDC with a separator of 0x00:
496
    /// ```
497
    /// # use dcaf::common::scope::BinaryEncodedScope;
1✔
498
    /// # use dcaf::error::InvalidBinaryEncodedScopeError;
499
    /// let scope = BinaryEncodedScope::try_from(vec![0xDC, 0xAF, 0x00, 0xAF, 0xDC].as_slice())?;
500
    /// assert!(scope.elements(Some(0x00))?.eq(&vec![vec![0xDC, 0xAF], vec![0xAF, 0xDC]]));
1✔
501
    /// assert!(scope.elements(None)?.eq(&vec![vec![0xDC, 0xAF, 0x00, 0xAF, 0xDC]]));
1✔
502
    /// assert!(scope.elements(Some(0xDC)).is_err());  // no separators at the beginning or end
1✔
503
    /// # Ok::<(), InvalidBinaryEncodedScopeError>(())
1✔
504
    /// ```
1✔
505
    BinaryEncoded(BinaryEncodedScope),
1✔
506

507
    /// Scope encoded using the [Authorization Information Format (AIF) for ACE](https://www.rfc-editor.org/rfc/rfc9237).
508
    ///
509
    /// For details, see the documentation of [`AifEncodedScope`].
510
    ///
511
    /// # Example
512
    /// Creating a scope containing `["/s/temp", 1]` (1 representing `GET`) and `["/a/led", 5]`
513
    /// (5 representing `GET` and `FETCH`):
514
    /// ```
515
    /// # use dcaf::AifEncodedScope;
1✔
516
    /// # use dcaf::common::scope::AifRestMethod;
517
    /// let scope = AifEncodedScope::from(vec![
518
    ///    ("/s/temp", AifRestMethod::Get.into()),
1✔
519
    ///    ("/a/led", AifRestMethod::Get | AifRestMethod::Fetch)
1✔
520
    /// ]);
1✔
521
    /// ```
1✔
522
    AifEncoded(AifEncodedScope),
1✔
523

524
    /// [`libdcaf`](https://gitlab.informatik.uni-bremen.de/DCAF/dcaf)-specific
525
    /// variant of [`AifEncoded`](Scope::AifEncoded) which consists of only a single
526
    /// [`AifEncodedScopeElement`].
527
    ///
528
    /// *Use only if trying to maintain compatibility to `libdcaf`.*
529
    ///
530
    /// For details, see the documentation of [`LibdcafEncodedScope`].
531
    ///
532
    /// # Example
533
    /// Creating a scope containing `["/s/temp", 1]` (1 representing `GET`):
534
    /// ```
535
    /// # use dcaf::LibdcafEncodedScope;
1✔
536
    /// # use dcaf::common::scope::AifRestMethod;
537
    /// let scope = LibdcafEncodedScope::new("/s/temp", AifRestMethod::Get.into());
538
    /// ```
1✔
539
    LibdcafEncoded(LibdcafEncodedScope),
1✔
540
}
541

542
/// Contains conversion methods for ACE-OAuth data types.
543
/// One part of this is converting enum types from and to their CBOR abbreviations in
544
/// [`cbor_abbreviations`](crate::constants::cbor_abbreviations),
545
/// another part is implementing the [`ToCborMap`](crate::ToCborMap) type for the
546
/// models which are represented as CBOR maps.
547
mod conversion {
548
    use ciborium::value::{Integer, Value};
549
    use serde::de::Error;
550
    use serde::{Deserializer, Serializer};
551

552
    use crate::error::{
553
        InvalidAifEncodedScopeError, InvalidBinaryEncodedScopeError, InvalidTextEncodedScopeError,
554
        ScopeFromValueError, WrongSourceTypeError,
555
    };
556

557
    use super::*;
558

559
    impl TextEncodedScope {
560
        /// Return the individual elements (i.e., access ranges) of this scope.
561
        ///
562
        /// Post-condition: The returned iterator will not be empty, and none of its elements
563
        /// may contain spaces (` `), double-quotes (`"`) or backslashes (`\\'`).
564
        ///
565
        /// # Example
566
        ///
567
        /// ```
568
        /// # use dcaf::common::scope::TextEncodedScope;
1✔
569
        /// # use dcaf::error::InvalidTextEncodedScopeError;
570
        /// let simple = TextEncodedScope::try_from("this is a test")?;
571
        /// assert!(simple.elements().eq(vec!["this", "is", "a", "test"]));
1✔
572
        /// # Ok::<(), InvalidTextEncodedScopeError>(())
1✔
573
        /// ```
1✔
574
        pub fn elements(&self) -> impl Iterator<Item = &str> {
497✔
575
            self.0.split(' ')
497✔
576
        }
497✔
577
    }
578

579
    impl TryFrom<&str> for TextEncodedScope {
580
        type Error = InvalidTextEncodedScopeError;
581

582
        fn try_from(value: &str) -> Result<Self, Self::Error> {
1,093✔
583
            if value.ends_with(' ') {
1,093✔
584
                Err(InvalidTextEncodedScopeError::EndsWithSeparator)
91✔
585
            } else if value.starts_with(' ') {
1,002✔
586
                Err(InvalidTextEncodedScopeError::StartsWithSeparator)
2✔
587
            } else if value.contains(['"', '\\']) {
1,000✔
588
                Err(InvalidTextEncodedScopeError::IllegalCharacters)
88✔
589
            } else if value.contains("  ") {
912✔
590
                Err(InvalidTextEncodedScopeError::ConsecutiveSeparators)
1✔
591
            } else if value.is_empty() {
911✔
592
                Err(InvalidTextEncodedScopeError::EmptyScope)
1✔
593
            } else {
594
                Ok(TextEncodedScope(value.into()))
910✔
595
            }
596
        }
1,093✔
597
    }
598

599
    impl TryFrom<Vec<&str>> for TextEncodedScope {
600
        type Error = InvalidTextEncodedScopeError;
601

602
        fn try_from(value: Vec<&str>) -> Result<Self, Self::Error> {
692✔
603
            if value.iter().any(|x| x.contains([' ', '\\', '"'])) {
1,033✔
604
                Err(InvalidTextEncodedScopeError::IllegalCharacters)
193✔
605
            } else if value.iter().any(|x| x.is_empty()) {
830✔
606
                Err(InvalidTextEncodedScopeError::EmptyElement)
2✔
607
            } else if value.is_empty() {
497✔
608
                Err(InvalidTextEncodedScopeError::EmptyScope)
83✔
609
            } else {
610
                // Fold the vec into a single string, using space as a separator
611
                Ok(TextEncodedScope(value.join(" ")))
414✔
612
            }
613
        }
692✔
614
    }
615

616
    impl BinaryEncodedScope {
617
        /// Return the individual elements (i.e., access ranges) of this scope.
618
        ///
619
        /// If no separator is given (i.e. it is `None`), it is assumed that the scope consists
620
        /// of a single element and will be returned as such.
621
        ///
622
        /// ## Pre-conditions
623
        /// - If a separator is given, it may neither be the first nor last element of the scope.
624
        /// - If a separator is given, it may not occur twice in a row in the scope.
625
        /// - The scope must not be empty.
626
        ///
627
        /// ## Post-conditions
628
        /// - The returned vector will not be empty.
629
        /// - None of its elements will be empty.
630
        /// - If a separator is given, none of its elements will contain it.
631
        /// - If no separator is given, the vector will consist of a single element, containing
632
        ///   the whole binary-encoded scope.
633
        ///
634
        /// # Example
635
        ///
636
        /// ```
637
        /// # use dcaf::common::scope::BinaryEncodedScope;
1✔
638
        /// # use dcaf::error::InvalidBinaryEncodedScopeError;
639
        /// let simple = BinaryEncodedScope::try_from(vec![0xDC, 0x21, 0xAF].as_slice())?;
640
        /// assert!(simple.elements(Some(0x21))?.eq(&vec![vec![0xDC], vec![0xAF]]));
1✔
641
        /// assert!(simple.elements(None)?.eq(&vec![vec![0xDC, 0x21, 0xAF]]));
1✔
642
        /// assert!(simple.elements(Some(0xDC)).is_err());
1✔
643
        /// # Ok::<(), InvalidBinaryEncodedScopeError>(())
1✔
644
        /// ```
1✔
645
        ///
1✔
646
        /// # Errors
647
        /// - If the binary encoded scope separated by the given `separator` is invalid in any way.
648
        ///   This may be the case if:
649
        ///   - The scope starts with a separator
650
        ///   - The scope ends with a separator
651
        ///   - The scope contains two separators in a row.
652
        ///
653
        /// # Panics
654
        /// If the pre-condition that the scope isn't empty is violated.
655
        /// This shouldn't occur, as it's an invariant of [BinaryEncodedScope].
656
        pub fn elements(
696✔
657
            &self,
696✔
658
            separator: Option<u8>,
696✔
659
        ) -> Result<Vec<&[u8]>, InvalidBinaryEncodedScopeError> {
696✔
660
            // We use an assert rather than an Error because the client is not expected to handle this.
696✔
661
            assert!(
696✔
662
                !self.0.is_empty(),
696✔
663
                "Invariant violated: Scope may not be empty"
×
664
            );
665
            if let Some(separator) = separator {
696✔
666
                let split = self.0.split(move |x| x == &separator);
3,010✔
667
                if self.0.first().filter(|x| **x != separator).is_none() {
532✔
668
                    Err(InvalidBinaryEncodedScopeError::StartsWithSeparator(
177✔
669
                        separator,
177✔
670
                    ))
177✔
671
                } else if self.0.last().filter(|x| **x != separator).is_none() {
355✔
672
                    Err(InvalidBinaryEncodedScopeError::EndsWithSeparator(separator))
2✔
673
                } else if self.0.windows(2).any(|x| x[0] == x[1] && x[1] == separator) {
1,155✔
674
                    Err(InvalidBinaryEncodedScopeError::ConsecutiveSeparators(
1✔
675
                        separator,
1✔
676
                    ))
1✔
677
                } else {
678
                    debug_assert!(
352✔
679
                        split.clone().all(|x| !x.is_empty()),
685✔
680
                        "Post-condition violated: Result may not contain empty slices"
×
681
                    );
682
                    Ok(split.collect())
352✔
683
                }
684
            } else {
685
                // no separator given
686
                Ok(vec![self.0.as_slice()])
164✔
687
            }
688
        }
696✔
689
    }
690

691
    impl TryFrom<&[u8]> for BinaryEncodedScope {
692
        type Error = InvalidBinaryEncodedScopeError;
693

694
        fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
702✔
695
            let vec = value.to_vec();
702✔
696
            if vec.is_empty() {
702✔
697
                Err(InvalidBinaryEncodedScopeError::EmptyScope)
83✔
698
            } else {
699
                Ok(BinaryEncodedScope(vec))
619✔
700
            }
701
        }
702✔
702
    }
703

704
    impl AifEncodedScopeElement {
705
        /// Creates a new [`AifEncodedScopeElement`] over the given `path` and `permissions`.
706
        ///
707
        /// # Example
708
        /// Let's take the example given in Table 2 of [the RFC](https://www.rfc-editor.org/rfc/rfc9237#table-2):
709
        /// ```text
710
        ///   +================+===================================+
711
        ///   | URI-local-part | Permission Set                    |
712
        ///   +================+===================================+
713
        ///   | /a/make-coffee | POST, Dynamic-GET, Dynamic-DELETE |
714
        ///   +----------------+-----------------------------------+
715
        /// ```
716
        ///
717
        /// We could create an `AifEncodedScopeElement` from that data like this:
718
        /// (the `make_bitflags` macro is used for better readability, but is not required)
719
        /// ```
720
        /// # use enumflags2::make_bitflags;
1✔
721
        /// use dcaf::common::scope::{AifEncodedScopeElement, AifRestMethod};
722
        /// let element = AifEncodedScopeElement::new(
723
        ///    "/a/make-coffee", make_bitflags!(AifRestMethod::{Post | DynamicGet | DynamicDelete})
1✔
724
        /// );
1✔
725
        /// ```
1✔
726
        #[must_use]
1✔
727
        pub fn new<T, U>(path: T, permissions: U) -> AifEncodedScopeElement
368✔
728
        where
368✔
729
            T: Into<String>,
368✔
730
            U: Into<BitFlags<AifRestMethod>>,
368✔
731
        {
368✔
732
            AifEncodedScopeElement {
368✔
733
                path: path.into(),
368✔
734
                permissions: permissions.into(),
368✔
735
            }
368✔
736
        }
368✔
737

738
        /// Tries to create a new [`AifEncodedScopeElement`] from the given `path` and `permissions`.
739
        ///
740
        /// `permissions` must be a valid bitmask of REST methods, as defined in
741
        /// [section 3 of RFC 9237](https://www.rfc-editor.org/rfc/rfc9237#section-3).
742
        ///
743
        /// # Errors
744
        /// If the given `permissions` do not correspond to a valid set of [`AifRestMethod`]s
745
        /// as defined in
746
        /// [section 3 of RFC 9237](https://www.rfc-editor.org/rfc/rfc9237#section-3).
747
        ///
748
        /// # Example
749
        /// For example, say we want to encode `["/a/led", 5]`, where the 5 corresponds to
750
        /// `GET` and `FETCH` (due to 2<sup>0</sup> and 2<sup>4</sup>):
751
        /// ```
752
        /// # use dcaf::common::scope::{AifEncodedScopeElement, AifRestMethod};
1✔
753
        /// # use dcaf::error::InvalidAifEncodedScopeError;
754
        /// let element = AifEncodedScopeElement::try_from_bits("/a/led", 5)?;
755
        /// assert_eq!(element, AifEncodedScopeElement::new("/a/led", AifRestMethod::Get | AifRestMethod::Put));
1✔
756
        /// # Ok::<(), InvalidAifEncodedScopeError>(())
1✔
757
        /// ```
1✔
758
        /// This method returns a result because it's possible to specify a bitmask that doesn't
1✔
759
        /// represent a REST method (such as 2<sup>31</sup>):
760
        /// ```
761
        /// # use dcaf::common::scope::{AifEncodedScopeElement};
1✔
762
        /// assert!(AifEncodedScopeElement::try_from_bits("no", u64::pow(2, 31)).is_err());
763
        /// ```
1✔
764
        pub fn try_from_bits<T>(
19✔
765
            path: T,
19✔
766
            permissions: u64,
19✔
767
        ) -> Result<AifEncodedScopeElement, InvalidAifEncodedScopeError>
19✔
768
        where
19✔
769
            T: Into<String>,
19✔
770
        {
19✔
771
            BitFlags::<AifRestMethod>::from_bits(permissions)
19✔
772
                .map_err(|_| InvalidAifEncodedScopeError::InvalidRestMethodSet)
19✔
773
                .map(|permissions| AifEncodedScopeElement {
19✔
774
                    path: path.into(),
7✔
775
                    permissions,
7✔
776
                })
19✔
777
        }
19✔
778

779
        /// Turns itself into a [`Value`].
780
        fn into_cbor_value(self) -> Value {
15✔
781
            Value::Array(vec![
15✔
782
                Value::Text(self.path),
15✔
783
                Value::Integer(Integer::from(self.permissions.bits())),
15✔
784
            ])
15✔
785
        }
15✔
786
    }
787

788
    impl AifEncodedScope {
789
        /// Creates a new [`AifEncodedScope`] consisting of the given `elements`.
790
        #[must_use]
791
        pub fn new(elements: Vec<AifEncodedScopeElement>) -> AifEncodedScope {
675✔
792
            AifEncodedScope(elements)
675✔
793
        }
675✔
794

795
        /// Returns a reference to the [`AifEncodedScopeElement`]s of this scope.
796
        ///
797
        /// # Example
798
        /// ```
799
        /// # use dcaf::AifEncodedScope;
1✔
800
        /// # use dcaf::common::scope::{AifEncodedScopeElement, AifRestMethod, AifRestMethodSet};
801
        /// let first = AifEncodedScopeElement::new("first", AifRestMethodSet::empty());
802
        /// let second = AifEncodedScopeElement::new("second", AifRestMethod::Patch);
1✔
803
        /// // We only clone here for the assert call below. This is usually not required.
1✔
804
        /// let scope = AifEncodedScope::new(vec![first.clone(), second.clone()]);
1✔
805
        /// assert_eq!(scope.elements(), &vec![first, second]);
1✔
806
        /// ```
1✔
807
        #[must_use]
1✔
808
        pub fn elements(&self) -> &Vec<AifEncodedScopeElement> {
250✔
809
            &self.0
250✔
810
        }
250✔
811

812
        /// Returns the [`AifEncodedScopeElement`]s of this scope.
813
        ///
814
        /// # Example
815
        /// ```
816
        /// # use dcaf::AifEncodedScope;
1✔
817
        /// # use dcaf::common::scope::{AifEncodedScopeElement, AifRestMethod, AifRestMethodSet};
818
        /// let first = AifEncodedScopeElement::new("first", AifRestMethodSet::empty());
819
        /// let second = AifEncodedScopeElement::new("second", AifRestMethod::Patch);
1✔
820
        /// // We only clone here for the assert call below. This is usually not required.
1✔
821
        /// let scope = AifEncodedScope::new(vec![first.clone(), second.clone()]);
1✔
822
        /// assert_eq!(scope.to_elements(), vec![first, second]);
1✔
823
        /// ```
1✔
824
        #[must_use]
1✔
825
        pub fn to_elements(self) -> Vec<AifEncodedScopeElement> {
88✔
826
            self.0
88✔
827
        }
88✔
828
    }
829

830
    impl Serialize for AifEncodedScopeElement {
831
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
×
832
        where
×
833
            S: Serializer,
×
834
        {
×
835
            Value::Array(vec![
×
836
                Value::Text(self.path.clone()),
×
837
                Value::Integer(Integer::from(self.permissions.bits())),
×
838
            ])
×
839
            .serialize(serializer)
×
840
        }
×
841
    }
842

843
    impl<T> From<Vec<(T, BitFlags<AifRestMethod>)>> for AifEncodedScope
844
    where
845
        T: Into<String>,
846
    {
847
        fn from(value: Vec<(T, BitFlags<AifRestMethod>)>) -> Self {
22✔
848
            AifEncodedScope::new(
22✔
849
                value
22✔
850
                    .into_iter()
22✔
851
                    .map(|(path, set)| AifEncodedScopeElement::new(path, set))
37✔
852
                    .collect(),
22✔
853
            )
22✔
854
        }
22✔
855
    }
856

857
    impl TryFrom<Vec<(String, u64)>> for AifEncodedScope {
858
        type Error = InvalidAifEncodedScopeError;
859

860
        fn try_from(value: Vec<(String, u64)>) -> Result<Self, Self::Error> {
8✔
861
            Ok(AifEncodedScope::new(
8✔
862
                value
8✔
863
                    .into_iter()
8✔
864
                    .map(|(path, rest)| AifEncodedScopeElement::try_from_bits(path, rest))
9✔
865
                    .collect::<Result<Vec<AifEncodedScopeElement>, InvalidAifEncodedScopeError>>(
8✔
866
                    )?,
8✔
867
            ))
868
        }
8✔
869
    }
870

871
    impl LibdcafEncodedScope {
872
        /// Creates a new libdcaf-encoded scope from the given `path` and `permissions`.
873
        ///
874
        /// Refer to [`AifEncodedScopeElement::new`] for details and
875
        /// an example applicable to this method.
876
        #[must_use]
877
        pub fn new<T>(path: T, permissions: BitFlags<AifRestMethod>) -> LibdcafEncodedScope
8✔
878
        where
8✔
879
            T: Into<String>,
8✔
880
        {
8✔
881
            LibdcafEncodedScope(AifEncodedScopeElement::new(path, permissions))
8✔
882
        }
8✔
883

884
        /// Creates a new libdcaf-encoded scope from the given `element`.
885
        ///
886
        /// Refer to [`AifEncodedScopeElement`] and [`LibdcafEncodedScope`] for details.
887
        #[must_use]
888
        pub fn from_element(element: AifEncodedScopeElement) -> LibdcafEncodedScope {
5✔
889
            LibdcafEncodedScope(element)
5✔
890
        }
5✔
891

892
        /// Tries to create a new libdcaf-encoded scope from the given `path` and `permissions`.
893
        ///
894
        /// The given `permissions` must be a valid bitmask of the allowed REST methods,
895
        /// as defined in [section 3 of RFC 9237](https://www.rfc-editor.org/rfc/rfc9237#section-3).
896
        ///
897
        /// # Errors
898
        /// Refer to [`AifEncodedScopeElement::try_from_bits`].
899
        ///
900
        /// # Example
901
        /// Refer to [`AifEncodedScopeElement::try_from_bits`].
902
        pub fn try_from_bits<T>(
6✔
903
            path: T,
6✔
904
            permissions: u64,
6✔
905
        ) -> Result<LibdcafEncodedScope, InvalidAifEncodedScopeError>
6✔
906
        where
6✔
907
            T: Into<String>,
6✔
908
        {
6✔
909
            Ok(LibdcafEncodedScope::from_element(
6✔
910
                AifEncodedScopeElement::try_from_bits(path, permissions)?,
6✔
911
            ))
912
        }
6✔
913

914
        /// Returns a reference to the single element contained in this scope.
915
        #[must_use]
916
        pub fn element(&self) -> &AifEncodedScopeElement {
86✔
917
            &self.0
86✔
918
        }
86✔
919

920
        /// Returns the single element contained in this scope.
921
        #[must_use]
922
        pub fn to_element(self) -> AifEncodedScopeElement {
1✔
923
            self.0
1✔
924
        }
1✔
925

926
        /// Returns a vector of a reference to the single element in this scope.
927
        #[must_use]
928
        pub fn elements(&self) -> Vec<&AifEncodedScopeElement> {
4✔
929
            vec![self.element()]
4✔
930
        }
4✔
931

932
        /// Returns a vector of the single element in this scope.
933
        #[must_use]
934
        pub fn to_elements(self) -> Vec<AifEncodedScopeElement> {
1✔
935
            vec![self.to_element()]
1✔
936
        }
1✔
937
    }
938

939
    impl From<LibdcafEncodedScope> for Scope {
940
        fn from(value: LibdcafEncodedScope) -> Self {
4✔
941
            Scope::LibdcafEncoded(value)
4✔
942
        }
4✔
943
    }
944

945
    impl From<AifEncodedScope> for Scope {
946
        fn from(value: AifEncodedScope) -> Self {
252✔
947
            Scope::AifEncoded(value)
252✔
948
        }
252✔
949
    }
950

951
    impl From<TextEncodedScope> for Scope {
952
        fn from(value: TextEncodedScope) -> Self {
660✔
953
            Scope::TextEncoded(value)
660✔
954
        }
660✔
955
    }
956

957
    impl From<BinaryEncodedScope> for Scope {
958
        fn from(value: BinaryEncodedScope) -> Self {
251✔
959
            Scope::BinaryEncoded(value)
251✔
960
        }
251✔
961
    }
962

963
    impl TryFrom<Vec<&str>> for Scope {
964
        type Error = InvalidTextEncodedScopeError;
965

966
        fn try_from(value: Vec<&str>) -> Result<Self, InvalidTextEncodedScopeError> {
84✔
967
            Ok(Scope::from(TextEncodedScope::try_from(value)?))
84✔
968
        }
84✔
969
    }
970

971
    impl TryFrom<&[u8]> for Scope {
972
        type Error = InvalidBinaryEncodedScopeError;
973

974
        fn try_from(value: &[u8]) -> Result<Self, InvalidBinaryEncodedScopeError> {
2✔
975
            Ok(Scope::from(BinaryEncodedScope::try_from(value)?))
2✔
976
        }
2✔
977
    }
978

979
    impl TryFrom<Vec<(String, u64)>> for Scope {
980
        type Error = InvalidAifEncodedScopeError;
981

982
        fn try_from(value: Vec<(String, u64)>) -> Result<Self, Self::Error> {
×
983
            Ok(Scope::from(AifEncodedScope::try_from(value)?))
×
984
        }
×
985
    }
986

987
    impl TryFrom<Scope> for BinaryEncodedScope {
988
        type Error = WrongSourceTypeError<Scope>;
989

990
        fn try_from(value: Scope) -> Result<Self, Self::Error> {
1✔
991
            if let Scope::BinaryEncoded(scope) = value {
1✔
992
                Ok(scope)
×
993
            } else {
994
                Err(WrongSourceTypeError::new("BinaryEncoded", value.into()))
1✔
995
            }
996
        }
1✔
997
    }
998

999
    impl TryFrom<Scope> for TextEncodedScope {
1000
        type Error = WrongSourceTypeError<Scope>;
1001

1002
        fn try_from(value: Scope) -> Result<Self, Self::Error> {
1✔
1003
            if let Scope::TextEncoded(scope) = value {
1✔
1004
                Ok(scope)
×
1005
            } else {
1006
                Err(WrongSourceTypeError::new("TextEncoded", value.into()))
1✔
1007
            }
1008
        }
1✔
1009
    }
1010

1011
    impl TryFrom<Scope> for AifEncodedScope {
1012
        type Error = WrongSourceTypeError<Scope>;
1013

1014
        fn try_from(value: Scope) -> Result<Self, Self::Error> {
1✔
1015
            if let Scope::AifEncoded(scope) = value {
1✔
1016
                Ok(scope)
×
1017
            } else {
1018
                Err(WrongSourceTypeError::new("AifEncoded", value.into()))
1✔
1019
            }
1020
        }
1✔
1021
    }
1022

1023
    impl TryFrom<Scope> for LibdcafEncodedScope {
1024
        type Error = WrongSourceTypeError<Scope>;
1025

1026
        fn try_from(value: Scope) -> Result<Self, Self::Error> {
2✔
1027
            if let Scope::LibdcafEncoded(scope) = value {
2✔
1028
                Ok(scope)
×
1029
            } else {
1030
                Err(WrongSourceTypeError::new("LibdcafEncoded", value.into()))
2✔
1031
            }
1032
        }
2✔
1033
    }
1034

1035
    impl From<Scope> for Value {
1036
        fn from(scope: Scope) -> Self {
423✔
1037
            match scope {
423✔
1038
                Scope::TextEncoded(text) => Value::Text(text.0),
413✔
1039
                Scope::BinaryEncoded(binary) => Value::Bytes(binary.0),
4✔
1040
                Scope::AifEncoded(aif) => Value::Array(
3✔
1041
                    aif.to_elements()
3✔
1042
                        .into_iter()
3✔
1043
                        .map(AifEncodedScopeElement::into_cbor_value)
3✔
1044
                        .collect(),
3✔
1045
                ),
3✔
1046
                Scope::LibdcafEncoded(lib) => lib.0.into_cbor_value(),
3✔
1047
            }
1048
        }
423✔
1049
    }
1050

1051
    impl TryFrom<Value> for Scope {
1052
        type Error = ScopeFromValueError;
1053

1054
        fn try_from(value: Value) -> Result<Self, Self::Error> {
433✔
1055
            #[allow(clippy::needless_pass_by_value)] // makes it easier to use later on
1056
            fn value_to_aif_element(
18✔
1057
                value: Value,
18✔
1058
            ) -> Result<AifEncodedScopeElement, InvalidAifEncodedScopeError> {
18✔
1059
                let values = value
18✔
1060
                    .as_array()
18✔
1061
                    .ok_or(InvalidAifEncodedScopeError::MalformedArray)?;
18✔
1062
                let path = values
18✔
1063
                    .first()
18✔
1064
                    .and_then(Value::as_text)
18✔
1065
                    .ok_or(InvalidAifEncodedScopeError::MalformedArray)?
18✔
1066
                    .to_string();
18✔
1067
                let permissions = values
18✔
1068
                    .get(1)
18✔
1069
                    .and_then(|x| {
18✔
1070
                        x.as_integer().map(|x| {
18✔
1071
                            u64::try_from(x)
18✔
1072
                                .map(BitFlags::<AifRestMethod>::from_bits)
18✔
1073
                                .map(Result::ok)
18✔
1074
                                .ok()
18✔
1075
                        })
18✔
1076
                    })
18✔
1077
                    .flatten()
18✔
1078
                    .flatten() // better than ???, I guess
18✔
1079
                    .ok_or(InvalidAifEncodedScopeError::MalformedArray)?;
18✔
1080
                Ok(AifEncodedScopeElement::new(path, permissions))
18✔
1081
            }
18✔
1082

1083
            match value {
433✔
1084
                Value::Bytes(b) => Ok(Scope::BinaryEncoded(BinaryEncodedScope::try_from(
4✔
1085
                    b.as_slice(),
4✔
1086
                )?)),
4✔
1087
                Value::Text(t) => Ok(Scope::TextEncoded(TextEncodedScope::try_from(t.as_str())?)),
413✔
1088
                Value::Array(a) => {
9✔
1089
                    if a.first().filter(|x| x.is_text()).is_some() {
9✔
1090
                        // Special handling for libdcaf
1091
                        Ok(Scope::LibdcafEncoded(LibdcafEncodedScope(
1092
                            value_to_aif_element(Value::Array(a))?,
3✔
1093
                        )))
1094
                    } else {
1095
                        a.into_iter()
6✔
1096
                            .map(value_to_aif_element)
6✔
1097
                            .collect::<Result<Vec<AifEncodedScopeElement>, InvalidAifEncodedScopeError>>()
6✔
1098
                            .map(|x| Scope::AifEncoded(AifEncodedScope::new(x)))
6✔
1099
                            .map_err(ScopeFromValueError::InvalidAifEncodedScope)
6✔
1100
                    }
1101
                }
1102
                v => Err(ScopeFromValueError::invalid_type(&v)),
7✔
1103
            }
1104
        }
433✔
1105
    }
1106

1107
    impl Serialize for Scope {
1108
        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
421✔
1109
        where
421✔
1110
            S: Serializer,
421✔
1111
        {
421✔
1112
            Value::from(self.clone()).serialize(serializer)
421✔
1113
        }
421✔
1114
    }
1115

1116
    impl<'de> Deserialize<'de> for Scope {
1117
        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
3✔
1118
        where
3✔
1119
            D: Deserializer<'de>,
3✔
1120
        {
3✔
1121
            Scope::try_from(Value::deserialize(deserializer)?)
3✔
1122
                .map_err(|x| Error::custom(x.to_string()))
3✔
1123
        }
3✔
1124
    }
1125
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc