1use std::collections::HashMap;
4
5use serde::{Deserialize, Serialize};
6
7use crate::errors::Errors;
8
9pub static DEFAULT_CLASSIFICATION_DATA: &str = r"
13# Turn on/off classification enforcement. When this flag is off, this
14# completely disables the classification engine, any documents added while
15# the classification engine is off gets the default unrestricted value
16enforce: false
17
18# Turn on/off dynamic group creation. This feature allow you to dynamically create classification groups based on
19# features from the user.
20dynamic_groups: false
21
22# Set the type of dynamic groups to be used
23# email: groups will be based of the user's email domain
24# group: groups will be created out the the user's group values
25# all: groups will be created out of both the email domain and the group values
26dynamic_groups_type: email
27
28# List of Classification level:
29# Graded list were a smaller number is less restricted then an higher number.
30levels:
31 # List of alternate names for the current marking
32 - aliases:
33 - UNRESTRICTED
34 - UNCLASSIFIED
35 - U
36 - TLP:W
37 - TLP:WHITE
38 # Stylesheet applied in the UI for the different levels
39 css:
40 # Name of the color scheme used for display (default, primary, secondary, success, info, warning, error)
41 color: default
42 # Description of the classification level
43 description: Subject to standard copyright rules, TLP:CLEAR information may be distributed without restriction.
44 # Interger value of the Classification level (higher is more classified)
45 lvl: 100
46 # Long name of the classification item
47 name: TLP:CLEAR
48 # Short name of the classification item
49 short_name: TLP:C
50 - aliases: []
51 css:
52 color: success
53 description:
54 Recipients may share TLP:GREEN information with peers and partner organizations
55 within their sector or community, but not via publicly accessible channels. Information
56 in this category can be circulated widely within a particular community. TLP:GREEN
57 information may not be released outside of the community.
58 lvl: 110
59 name: TLP:GREEN
60 short_name: TLP:G
61 - aliases: []
62 css:
63 color: warning
64 description:
65 Recipients may only share TLP:AMBER information with members of their
66 own organization and with clients or customers who need to know the information
67 to protect themselves or prevent further harm.
68 lvl: 120
69 name: TLP:AMBER
70 short_name: TLP:A
71 - aliases:
72 - RESTRICTED
73 css:
74 color: warning
75 description:
76 Recipients may only share TLP:AMBER+STRICT information with members of their
77 own organization.
78 lvl: 125
79 name: TLP:AMBER+STRICT
80 short_name: TLP:A+S
81
82# List of required tokens:
83# A user requesting access to an item must have all the
84# required tokens the item has to gain access to it
85required:
86 - aliases: []
87 description: Produced using a commercial tool with limited distribution
88 name: COMMERCIAL
89 short_name: CMR
90 # The minimum classification level an item must have
91 # for this token to be valid. (optional)
92 # require_lvl: 100
93 # This is a token that is required but will display in the groups part
94 # of the classification string. (optional)
95 # is_required_group: true
96
97# List of groups:
98# A user requesting access to an item must be part of a least
99# of one the group the item is part of to gain access
100groups:
101 - aliases: []
102 # This is a special flag that when set to true, if any groups are selected
103 # in a classification. This group will automatically be selected too. (optional)
104 auto_select: true
105 description: Employees of CSE
106 name: CSE
107 short_name: CSE
108 # Assuming that this groups is the only group selected, this is the display name
109 # that will be used in the classification (that values has to be in the aliases
110 # of this group and only this group) (optional)
111 # solitary_display_name: ANY
112
113# List of subgroups:
114# A user requesting access to an item must be part of a least
115# of one the subgroup the item is part of to gain access
116subgroups:
117 - aliases: []
118 description: Member of Incident Response team
119 name: IR TEAM
120 short_name: IR
121 - aliases: []
122 description: Member of the Canadian Centre for Cyber Security
123 # This is a special flag that auto-select the corresponding group
124 # when this subgroup is selected (optional)
125 require_group: CSE
126 name: CCCS
127 short_name: CCCS
128 # This is a special flag that makes sure that none other then the
129 # corresponding group is selected when this subgroup is selected (optional)
130 # limited_to_group: CSE
131
132# Default restricted classification
133restricted: TLP:A+S//CMR
134
135# Default unrestricted classification:
136# When no classification are provided or that the classification engine is
137# disabled, this is the classification value each items will get
138unrestricted: TLP:C";
139
140pub fn ready_classification(config_data: Option<&str>) -> Result<ClassificationConfig, Errors> {
142 if let Some(config_data) = config_data {
143 let yml_data: serde_yaml::mapping::Mapping = serde_yaml::from_str(config_data)?;
145 let mut config: serde_yaml::Value = serde_yaml::from_str(DEFAULT_CLASSIFICATION_DATA)?;
146 config.as_mapping_mut().unwrap().extend(yml_data);
147 Ok(serde_yaml::from_value(config)?)
148 } else {
149 Ok(serde_yaml::from_str(DEFAULT_CLASSIFICATION_DATA)?)
150 }
151}
152
153
154#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
156#[serde(rename_all="lowercase")]
157pub enum DynamicGroupType {
158 #[default]
160 Email,
161 Group,
163 All,
165}
166
167
168#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
170#[serde(deny_unknown_fields)]
171pub struct ClassificationConfig {
172 pub enforce: bool,
176
177 pub dynamic_groups: bool,
180
181 #[serde(default)]
183 pub dynamic_groups_type: DynamicGroupType,
184
185 pub levels: Vec<ClassificationLevel>,
188
189 pub required: Vec<ClassificationMarking>,
193
194 pub groups: Vec<ClassificationGroup>,
198
199 pub subgroups: Vec<ClassificationSubGroup>,
203
204 pub restricted: String,
206
207 pub unrestricted: String,
211}
212
213#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
215#[serde(deny_unknown_fields)]
216pub struct ClassificationLevel {
217 #[serde(default)]
219 pub aliases: Vec<NameString>,
220
221 #[serde(default="default_css")]
223 pub css: HashMap<String, String>,
224
225 #[serde(default="default_description")]
227 pub description: String,
228
229 pub lvl: i32,
231
232 pub name: NameString,
234
235 pub short_name: NameString,
237
238 #[serde(default)]
240 pub is_hidden: bool,
241
242 }
246
247impl ClassificationLevel {
248 pub fn new(lvl: i32, short_name: &str, name: &str, aliases: Vec<&str>) -> Self {
250 ClassificationLevel {
251 aliases: aliases.into_iter().map(|x|x.parse().unwrap()).collect(),
252 css: default_css(),
253 description: default_description(),
254 lvl,
255 name: name.parse().unwrap(),
256 short_name: short_name.parse().unwrap(),
257 is_hidden: false,
258 }
259 }
260
261 pub fn unique_names(&self) -> Vec<NameString> {
263 let mut names = vec![self.name.clone(), self.short_name.clone()];
264 names.extend(self.aliases.iter().cloned());
265 names.sort_unstable();
266 names.dedup();
267 return names
268 }
269}
270
271fn default_css() -> HashMap<String, String> { [("color".to_owned(), "default".to_owned()), ].into_iter().collect() }
273
274fn default_description() -> String {"N/A".to_owned()}
276
277#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
279#[serde(deny_unknown_fields)]
280pub struct ClassificationMarking {
281 #[serde(default)]
283 pub aliases: Vec<NameString>,
284
285 #[serde(default="default_description")]
287 pub description: String,
288
289 pub name: NameString,
292
293 pub short_name: NameString,
296
297 #[serde(default)]
299 pub require_lvl: Option<i32>,
300
301 #[serde(default)]
304 pub is_required_group: bool,
305
306 #[serde(default)]
308 pub is_hidden: bool,
309}
310
311impl ClassificationMarking {
312 pub fn new(short_name: &str, name: &str, aliases: Vec<&str>) -> Self {
314 Self {
315 aliases: aliases.into_iter().map(|x|x.parse().unwrap()).collect(),
316 description: default_description(),
317 name: name.parse().unwrap(),
318 short_name: short_name.parse().unwrap(),
319 require_lvl: None,
320 is_required_group: false,
321 is_hidden: false,
322 }
323 }
324
325 pub fn new_required(short_name: &str, name: &str) -> Self {
327 let mut new = Self::new(short_name, name, vec![]);
328 new.is_required_group = true;
329 new
330 }
331
332 pub fn unique_names(&self) -> Vec<NameString> {
334 let mut names = vec![self.name.clone(), self.short_name.clone()];
335 names.extend(self.aliases.iter().cloned());
336 names.sort_unstable();
337 names.dedup();
338 return names
339 }
340}
341
342#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
344pub struct ClassificationGroup {
345 #[serde(default)]
347 pub aliases: Vec<NameString>,
348
349 #[serde(default)]
352 pub auto_select: bool,
353
354 #[serde(default="default_description")]
356 pub description: String,
357
358 pub name: NameString,
360
361 pub short_name: NameString,
363
364 #[serde(default)]
368 pub solitary_display_name: Option<NameString>,
369
370 #[serde(default)]
372 pub is_hidden: bool,
373}
374
375impl ClassificationGroup {
376 pub fn new(short_name: &str, name: &str) -> Self {
378 Self {
379 name: name.parse().unwrap(),
380 short_name: short_name.parse().unwrap(),
381 aliases: vec![],
382 auto_select: false,
383 description: default_description(),
384 solitary_display_name: None,
385 is_hidden: false,
386 }
387 }
388
389 pub fn new_solitary(short_name: &str, name: &str, solitary_display: &str) -> Self {
391 let mut new = Self::new(short_name, name);
392 new.solitary_display_name = Some(solitary_display.parse().unwrap());
393 return new
394 }
395}
396
397#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
399#[serde(deny_unknown_fields)]
400pub struct ClassificationSubGroup {
401 #[serde(default)]
403 pub aliases: Vec<NameString>,
404
405 #[serde(default)]
408 pub auto_select: bool,
409
410 #[serde(default="default_description")]
412 pub description: String,
413
414 pub name: NameString,
416
417 pub short_name: NameString,
419
420 #[serde(default)]
431 pub require_group: Option<NameString>,
432
433 #[serde(default)]
436 pub limited_to_group: Option<NameString>,
437
438 #[serde(default)]
440 pub is_hidden: bool,
441}
442
443
444impl ClassificationSubGroup {
445 pub fn new_aliased(short_name: &str, name: &str, aliases: Vec<&str>) -> Self {
447 Self {
448 short_name: short_name.parse().unwrap(),
449 name: name.parse().unwrap(),
450 aliases: aliases.iter().map(|item|item.parse().unwrap()).collect(),
451 auto_select: false,
452 description: default_description(),
453 require_group: None,
454 limited_to_group: None,
455 is_hidden: false,
456 }
457 }
458
459 pub fn new_with_required(short_name: &str, name: &str, required: &str) -> Self {
461 let mut new = Self::new_aliased(short_name, name, vec![]);
462 new.require_group = Some(required.parse().unwrap());
463 return new
464 }
465
466 pub fn new_with_limited(short_name: &str, name: &str, limited: &str) -> Self {
468 let mut new = Self::new_aliased(short_name, name, vec![]);
469 new.limited_to_group = Some(limited.parse().unwrap());
470 return new
471 }
472}
473
474
475#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
478pub struct NameString(String);
479
480impl core::ops::Deref for NameString {
481 type Target = str;
482
483 fn deref(&self) -> &Self::Target {
484 self.0.as_str()
485 }
486}
487
488impl std::str::FromStr for NameString {
489 type Err = Errors;
490
491 fn from_str(s: &str) -> Result<Self, Self::Err> {
492 let data = s.trim().to_uppercase();
493 if data.is_empty() {
494 return Err(Errors::ClassificationNameEmpty)
495 }
496 Ok(Self(data))
497 }
498}
499
500impl core::fmt::Display for NameString {
501 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
502 f.write_str(&self.0)
503 }
504}
505
506impl NameString {
507 pub fn as_str(&self) -> &str {
509 &self.0
510 }
511}
512
513impl<'de> Deserialize<'de> for NameString {
514 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
515 where D: serde::Deserializer<'de> {
516 let data = String::deserialize(deserializer)?;
517 data.parse().map_err(serde::de::Error::custom)
518 }
519}
520
521impl Serialize for NameString {
522 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
523 where
524 S: serde::Serializer {
525 self.0.serialize(serializer)
526 }
527}
528
529#[test]
530fn check_default_configurations() {
531 let default_config = ready_classification(None).unwrap();
532 assert!(!default_config.enforce);
533
534 println!("{:?}", serde_yaml::to_value(DEFAULT_CLASSIFICATION_DATA).unwrap());
535
536 let config = ready_classification(Some("enforce: true")).unwrap();
537 assert!(config.enforce);
538
539 use crate::classification::ClassificationParser;
540 let ce = ClassificationParser::new(default_config).unwrap();
541
542 assert_eq!(ce.normalize_classification("ABC123").unwrap(), "");
543}