1use std::collections::{HashSet, HashMap};
3use std::sync::Arc;
4
5use itertools::Itertools;
6
7use crate::config::{ClassificationConfig, DynamicGroupType, ClassificationLevel, ClassificationMarking, ClassificationSubGroup, ClassificationGroup};
8use crate::errors::Errors;
9
10type Result<T> = std::result::Result<T, Errors>;
12
13const MIN_LVL: i32 = 1;
15const MAX_LVL: i32 = 10000;
17const NULL_LVL: i32 = 0;
19const INVALID_LVL: i32 = 10001;
21const NULL_CLASSIFICATION: &str = "NULL";
23const INVALID_SHORT_CLASSIFICATION: &str = "INV";
25const INVALID_CLASSIFICATION: &str = "INVALID";
27
28#[derive(Default, Debug, PartialEq)]
30pub struct ClassificationParser {
31 pub original_definition: ClassificationConfig,
33
34 enforce: bool,
36
37 dynamic_groups: bool,
39
40 dynamic_groups_type: DynamicGroupType,
42
43 levels: HashMap<i32, ClassificationLevel>,
45
46 levels_scores_map: HashMap<String, i32>,
48
49 access_req: HashMap<String, Arc<ClassificationMarking>>,
51
52 groups: HashMap<String, Arc<ClassificationGroup>>,
54
55 groups_aliases: HashMap<String, HashSet<String>>,
57
58 groups_auto_select: Vec<String>,
60
61 groups_auto_select_short: Vec<String>,
63
64 subgroups: HashMap<String, Arc<ClassificationSubGroup>>,
66
67 subgroups_aliases: HashMap<String, HashSet<String>>,
69
70 subgroups_auto_select: Vec<String>,
72
73 subgroups_auto_select_short: Vec<String>,
75
76 description: HashMap<String, String>,
78
79 invalid_mode: bool,
81 unrestricted: String,
86
87 restricted: String,
89}
90
91pub trait IBool: Into<Option<bool>> + Copy {}
93impl<T: Into<Option<bool>> + Copy> IBool for T {}
94
95impl ClassificationParser {
96
97 pub fn load(path: &std::path::Path) -> Result<Self> {
99 let file = std::fs::File::open(path)?;
101 Self::new(serde_yaml::from_reader(file)?)
102 }
103
104 pub fn new(definition: ClassificationConfig) -> Result<Self> {
106 let mut new = Self {
107 original_definition: definition.clone(),
108 enforce: definition.enforce,
109 dynamic_groups: definition.dynamic_groups,
110 dynamic_groups_type: definition.dynamic_groups_type,
111 ..Default::default()
112 };
113
114 new.insert_level(ClassificationLevel {
116 aliases: vec![],
117 css: Default::default(),
118 description: INVALID_CLASSIFICATION.to_owned(),
119 lvl: INVALID_LVL,
120 name: INVALID_CLASSIFICATION.parse()?,
121 short_name: INVALID_SHORT_CLASSIFICATION.parse()?,
122 is_hidden: false,
123 }, true)?;
124
125 new.insert_level(ClassificationLevel {
127 aliases: vec![],
128 css: Default::default(),
129 description: NULL_CLASSIFICATION.to_owned(),
130 lvl: NULL_LVL,
131 name: NULL_CLASSIFICATION.parse()?,
132 short_name: NULL_CLASSIFICATION.parse()?,
133 is_hidden: false,
134 }, true)?;
135
136 for level in definition.levels {
138 new.insert_level(level, false)?;
139 }
140
141 for x in definition.required {
142 new.description.insert(x.short_name.to_string(), x.description.clone());
143 new.description.insert(x.name.to_string(), x.description.clone());
144 let x = Arc::new(x);
145
146 for name in x.unique_names() {
147 if let Some(old) = new.access_req.insert(name.to_string(), x.clone()) {
148 return Err(Errors::InvalidDefinition(format!("Duplicate required name: {}", old.name)))
149 }
150 }
151 }
152
153 for x in definition.groups {
154 for a in &x.aliases {
155 new.groups_aliases.entry(a.to_string()).or_default().insert(x.short_name.to_string());
156 }
157 if let Some(a) = &x.solitary_display_name {
158 new.groups_aliases.entry(a.to_string()).or_default().insert(x.short_name.to_string());
159 }
160 if x.auto_select {
161 new.groups_auto_select.push(x.name.to_string());
162 new.groups_auto_select_short.push(x.short_name.to_string());
163 }
164
165 new.description.insert(x.short_name.to_string(), x.description.to_string());
166 new.description.insert(x.name.to_string(), x.description.to_string());
167
168 let x = Arc::new(x);
169 if x.name != x.short_name {
170 if let Some(old) = new.groups.insert(x.name.to_string(), x.clone()) {
171 return Err(Errors::InvalidDefinition(format!("Duplicate group name: {}", old.name)))
172 }
173 }
174 if let Some(old) = new.groups.insert(x.short_name.to_string(), x) {
175 return Err(Errors::InvalidDefinition(format!("Duplicate group name: {}", old.short_name)))
176 }
177 }
178
179 for x in definition.subgroups {
180 for a in &x.aliases {
181 new.subgroups_aliases.entry(a.to_string()).or_default().insert(x.short_name.to_string());
182 }
183 if x.auto_select {
187 new.subgroups_auto_select.push(x.name.to_string());
188 new.subgroups_auto_select_short.push(x.short_name.to_string());
189 }
190
191 new.description.insert(x.short_name.to_string(), x.description.to_string());
192 new.description.insert(x.name.to_string(), x.description.to_string());
193
194 let x = Arc::new(x);
195 if x.name != x.short_name {
196 if let Some(old) = new.subgroups.insert(x.name.to_string(), x.clone()) {
197 return Err(Errors::InvalidDefinition(format!("Duplicate subgroup name: {}", old.name)))
198 }
199 }
200 if let Some(old) = new.subgroups.insert(x.short_name.to_string(), x) {
201 return Err(Errors::InvalidDefinition(format!("Duplicate subgroup name: {}", old.short_name)))
202 }
203 }
204
205 if !new.is_valid(&definition.unrestricted) {
206 return Err(Errors::InvalidDefinition("Classification definition's unrestricted classification is invalid.".to_owned()));
207 }
208
209 if !new.is_valid(&definition.restricted) {
210 return Err(Errors::InvalidDefinition("Classification definition's restricted classification is invalid.".to_owned()));
211 }
212
213 new.unrestricted = new.normalize_classification(&definition.unrestricted)?;
214 new.restricted = new.normalize_classification(&definition.restricted)?;
215
216 Ok(new)
225 }
226
227 fn insert_level(&mut self, ll: ClassificationLevel, force: bool) -> Result<()> {
229 if !force {
231 if [INVALID_CLASSIFICATION, INVALID_SHORT_CLASSIFICATION, NULL_CLASSIFICATION].contains(&ll.short_name.as_str()) {
232 return Err(Errors::InvalidDefinition("You cannot use reserved words NULL, INVALID or INV in your classification definition.".to_owned()));
233 }
234 if [INVALID_CLASSIFICATION, INVALID_SHORT_CLASSIFICATION, NULL_CLASSIFICATION].contains(&ll.name.as_str()) {
235 return Err(Errors::InvalidDefinition("You cannot use reserved words NULL, INVALID or INV in your classification definition.".to_owned()));
236 }
237
238 if ll.lvl > MAX_LVL {
239 return Err(Errors::InvalidDefinition(format!("Level over maximum classification level of {MAX_LVL}.")))
240 }
241 if ll.lvl < MIN_LVL {
242 return Err(Errors::InvalidDefinition(format!("Level under minimum classification level of {MIN_LVL}.")))
243 }
244 }
245
246 for name in ll.unique_names() {
248 if let Some(level) = self.levels_scores_map.insert(name.to_string(), ll.lvl) {
249 return Err(Errors::InvalidDefinition(format!("Name clash between classification levels: {name} on {level} and {}", ll.lvl)))
250 }
251 }
252
253 if let Some(old) = self.levels.insert(ll.lvl, ll) {
254 return Err(Errors::InvalidDefinition(format!("Duplicate classification level: {}", old.lvl)))
255 }
256 return Ok(())
257 }
258
259fn _get_c12n_level_index(&self, c12n: &str) -> Result<(i32, String)> {
297 let c12n = c12n.trim().to_uppercase();
299
300 let (lvl, remain) = c12n.split_once("//").unwrap_or((&c12n, ""));
301 if let Some(value) = self.levels_scores_map.get(lvl) {
302 return Ok((*value, remain.to_string()))
303 }
304 Err(Errors::InvalidClassification(format!("Classification level '{lvl}' was not found in your classification definition.")))
305 }
306
307 fn _get_c12n_required(&self, c12n: &str, long_format: impl IBool) -> (Vec<String>, Vec<String>) {
309 let long_format = long_format.into().unwrap_or(true);
310
311 let c12n = c12n.trim().to_uppercase();
313
314 let mut return_set: Vec<String> = vec![];
315 let mut others: Vec<String> = vec![];
316
317 for p in c12n.split('/') {
318 if p.is_empty() {
319 continue
320 }
321
322 if let Some(data) = self.access_req.get(p) {
323 if long_format {
324 return_set.push(data.name.to_string());
325 } else {
326 return_set.push(data.short_name.to_string());
327 }
328 } else {
329 others.push(p.to_owned())
330 }
331 }
332
333 return_set.sort_unstable();
334 return_set.dedup();
335 return (return_set, others)
336 }
337
338 fn _get_c12n_groups(&self, c12n_parts: Vec<String>,
340 long_format: impl IBool,
341 get_dynamic_groups: impl IBool,
342 auto_select: impl IBool
343 ) -> Result<(Vec<String>, Vec<String>, Vec<String>)> {
344 let long_format = long_format.into().unwrap_or(true);
345 let get_dynamic_groups = get_dynamic_groups.into().unwrap_or(true);
346 let auto_select = auto_select.into().unwrap_or(false);
347
348 let mut g1_set: Vec<&str> = vec![];
352 let mut g2_set: Vec<&str> = vec![];
353 let mut others = vec![];
354
355 let mut groups = vec![];
356 let mut subgroups = vec![];
357 for gp in c12n_parts {
358 if gp.starts_with("REL ") {
359 let gp = gp.replace("REL TO ", "");
361 let gp = gp.replace("REL ", "");
362 for t in gp.split(',') {
363 groups.extend(t.trim().split('/').map(|x|x.trim().to_owned()));
364 }
365 } else {
366 subgroups.push(gp)
368 }
369 }
370
371 for g in &groups {
372 if let Some(data) = self.groups.get(g) {
373 g1_set.push(data.short_name.as_str());
374 } else if let Some(aliases) = self.groups_aliases.get(g) {
375 for a in aliases {
376 g1_set.push(a)
377 }
378 } else {
379 others.push(g);
380 }
381 }
382
383 for g in &subgroups {
384 if let Some(g) = self.subgroups.get(g) {
385 g2_set.push(g.short_name.as_str());
386 } else if let Some(aliases) = self.subgroups_aliases.get(g) {
387 for a in aliases {
388 g2_set.push(a)
389 }
390 } else if let Some(aliases) = self.groups_aliases.get(g) {
391 if aliases.len() != 1 {
393 return Err(Errors::InvalidClassification(format!("Name used ambiguously: {g}")))
394 }
395 for a in aliases {
396 g1_set.push(a)
397 }
398 } else {
399 return Err(Errors::InvalidClassification(format!("Unrecognized classification part: {g}")))
400 }
401 }
402
403 let others = if self.dynamic_groups && get_dynamic_groups {
404 g1_set.extend(others.iter().map(|s|s.as_str()));
405 vec![]
406 } else {
407 others.iter().map(|s|s.to_string()).collect()
408 };
409
410 g1_set.sort_unstable();
411 g1_set.dedup();
412 g2_set.sort_unstable();
413 g2_set.dedup();
414
415 for subgroup in &g2_set {
417 match self.subgroups.get(*subgroup) {
418 Some(data) => {
419 if let Some(limited) = &data.require_group {
420 g1_set.push(limited.as_str())
421 }
422 },
423 None => {
424 return Err(Errors::InvalidClassification(format!("Unknown subgroup: {subgroup}")))
425 }
426 }
427 }
428
429 for subgroup in &g2_set {
431 match self.subgroups.get(*subgroup) {
432 Some(data) => {
433 if let Some(limited) = &data.limited_to_group {
434 if g1_set.len() > 1 || (g1_set.len() == 1 && g1_set[0] != limited.as_str()) {
435 return Err(Errors::InvalidClassification(format!("Subgroup {subgroup} is limited to group {limited} (found: {})", g1_set.join(", "))))
436 }
437 }
438 },
439 None => {
440 return Err(Errors::InvalidClassification(format!("Unknown subgroup: {subgroup}")))
441 }
442 }
443 }
444
445 if auto_select && !g1_set.is_empty() {
447 g1_set.extend(self.groups_auto_select_short.iter().map(String::as_str))
448 }
449 if auto_select && !g2_set.is_empty() {
450 g2_set.extend(self.subgroups_auto_select_short.iter().map(String::as_str))
451 }
452
453 let (mut g1_set, mut g2_set) = if long_format {
454 let g1: Result<Vec<String>> = g1_set.into_iter()
455 .map(|r| self.groups.get(r).ok_or(Errors::InvalidClassification("".to_owned())))
456 .map_ok(|r|r.name.to_string())
457 .collect();
458 let g2: Result<Vec<String>> = g2_set.into_iter()
459 .map(|r| self.subgroups.get(r).ok_or(Errors::InvalidClassification("".to_owned())))
460 .map_ok(|r|r.name.to_string())
461 .collect();
462
463 (g1?, g2?)
464 } else {
465 (g1_set.into_iter().map(|r|r.to_owned()).collect_vec(), g2_set.into_iter().map(|r| r.to_owned()).collect_vec())
466 };
467
468 g1_set.sort_unstable();
469 g1_set.dedup();
470 g2_set.sort_unstable();
471 g2_set.dedup();
472
473 return Ok((g1_set, g2_set, others))
474 }
475
476 fn _can_see_required(user_req: &Vec<String>, req: &Vec<String>) -> bool {
478 let req: HashSet<&String> = HashSet::from_iter(req);
479 let user_req = HashSet::from_iter(user_req);
480 return req.is_subset(&user_req)
481 }
482
483 fn _can_see_groups(user_groups: &Vec<String>, required_groups: &[String]) -> bool {
485 if required_groups.is_empty() {
486 return true
487 }
488
489 for g in user_groups {
490 if required_groups.contains(g) {
491 return true
492 }
493 }
494
495 return false
496 }
497
498 pub fn get_normalized_classification_text(&self, parts: ParsedClassification, long_format: bool, skip_auto_select: bool) -> Result<String> {
502 let ParsedClassification{level: lvl_idx, required: req, mut groups, mut subgroups} = parts;
503
504 let group_delim = if long_format {"REL TO "} else {"REL "};
505
506 let mut required_lvl_idx = 0;
508 for r in &req {
509 if let Some(params) = self.access_req.get(r) {
510 required_lvl_idx = required_lvl_idx.max(params.require_lvl.unwrap_or_default())
511 }
512 }
513 let mut out = self.get_classification_level_text(lvl_idx.max(required_lvl_idx), long_format)?;
514
515 let mut req_grp = vec![];
517 for r in &req {
518 if let Some(params) = self.access_req.get(r) {
519 if params.is_required_group {
520 req_grp.push(r.clone());
521 }
522 }
523 }
524 let req = req.into_iter().filter(|item|!req_grp.contains(item)).collect_vec();
526
527 if !req.is_empty() {
528 out += &("//".to_owned() + &req.join("/"));
529 }
530 if !req_grp.is_empty() {
531 req_grp.sort_unstable();
532 out += &("//".to_owned() + &req_grp.join("/"));
533 }
534
535 if long_format {
537 if !subgroups.is_empty() && !self.subgroups_auto_select.is_empty() && !skip_auto_select {
538 subgroups.extend(self.subgroups_auto_select.iter().cloned());
540 }
541 } else {
542 if !subgroups.is_empty() && !self.subgroups_auto_select_short.is_empty() && !skip_auto_select {
543 subgroups.extend(self.subgroups_auto_select_short.iter().cloned())
544 }
546 }
547 subgroups.sort_unstable();
548 subgroups.dedup();
549
550 let mut temp_groups = vec![];
552 for sg in &subgroups {
553 if let Some(subgroup) = self.subgroups.get(sg) {
554 if let Some(require_group) = &subgroup.require_group {
555 temp_groups.push(require_group.clone())
556 }
557
558 if let Some(limited_to_group) = &subgroup.limited_to_group {
559 if temp_groups.contains(limited_to_group) {
560 temp_groups = vec![limited_to_group.clone()]
561 } else {
562 temp_groups.clear()
563 }
564 }
565 }
566 }
567
568 for g in &temp_groups {
569 if let Some(data) = self.groups.get(g.as_str()) {
570 if long_format {
571 groups.push(data.name.to_string())
572 } else {
573 groups.push(data.short_name.to_string())
574 }
575 } else {
576 groups.push(g.to_string())
577 }
578 }
579
580 if long_format {
582 if !groups.is_empty() && !self.groups_auto_select.is_empty() && !skip_auto_select {
583 groups.extend(self.groups_auto_select.iter().cloned());
584 }
585 } else {
586 if !groups.is_empty() && !self.groups_auto_select_short.is_empty() && !skip_auto_select {
587 groups.extend(self.groups_auto_select_short.iter().cloned());
588 }
589 }
590 groups.sort_unstable();
591 groups.dedup();
592
593 if !groups.is_empty() {
594 out += if req_grp.is_empty() {"//"} else {"/"};
595 if groups.len() == 1 {
596 let grp = &groups[0];
598 if let Some(group_data) = self.groups.get(grp) {
599 if let Some(display_name) = &group_data.solitary_display_name {
600 out += display_name.as_str();
601 } else {
602 out += group_delim;
603 out += grp;
604 }
605 }
606 } else {
607 if !long_format {
608 let group_set: HashSet<String> = groups.iter().cloned().collect();
610 for (alias, values) in self.groups_aliases.iter() {
611 if values.len() > 1 && *values == group_set {
612 groups = vec![alias.clone()]
613 }
614 }
615 }
616 out += group_delim;
617 out += &groups.join(", ");
618 }
619 }
620
621 if !subgroups.is_empty() {
622 if groups.is_empty() && req_grp.is_empty() {
623 out += "//"
624 } else {
625 out += "/"
626 }
627 subgroups.sort_unstable();
628 out += &subgroups.join("/");
629 }
630
631 return Ok(out)
632 }
633
634 pub fn get_classification_level_text(&self, lvl_idx: i32, long_format: bool) -> Result<String> {
636 if let Some(data) = self.levels.get(&lvl_idx) {
637 if long_format {
638 return Ok(data.name.to_string())
639 } else {
640 return Ok(data.short_name.to_string())
641 }
642 }
643
644 Err(Errors::InvalidClassification(format!("Classification level number '{lvl_idx}' was not found in your classification definition.")))
645 }
646
647 pub fn get_classification_parts(&self, c12n: &str, long_format: impl IBool, get_dynamic_groups: impl IBool, auto_select: impl IBool) -> Result<ParsedClassification> {
649 let (level, remain) = self._get_c12n_level_index(c12n)?;
650 let (required, unparsed_required) = self._get_c12n_required(&remain, long_format);
651 let (groups, subgroups, unparsed_groups) = self._get_c12n_groups(unparsed_required, long_format, get_dynamic_groups, auto_select)?;
652
653 if !unparsed_groups.is_empty() {
654 return Err(Errors::InvalidClassification(format!("Unknown parts: {}", unparsed_groups.join(", "))))
655 }
656
657 Ok(ParsedClassification { level, required, groups, subgroups })
658 }
659
660 pub fn get_access_control_parts(&self, c12n: &str, user_classification: bool) -> Result<serde_json::Value> {
777 let c12n = if !self.enforce || self.invalid_mode {
778 self.unrestricted.clone()
779 } else {
780 c12n.to_owned()
781 };
782
783 let result: Result<serde_json::Value> = (||{
784 let parts = self.get_classification_parts(&c12n, false, true, !user_classification)?;
786
787 return Ok(serde_json::json!({
788 "__access_lvl__": parts.level,
789 "__access_req__": parts.required,
790 "__access_grp1__": if parts.groups.is_empty() { vec!["__EMPTY__".to_owned()] } else { parts.groups },
791 "__access_grp2__": if parts.subgroups.is_empty() { vec!["__EMPTY__".to_owned()] } else { parts.subgroups }
792 }))
793 })();
794
795 if let Err(Errors::InvalidClassification(_)) = &result {
796 if !self.enforce || self.invalid_mode {
797 return Ok(serde_json::json!({
798 "__access_lvl__": NULL_LVL,
799 "__access_req__": [],
800 "__access_grp1__": ["__EMPTY__"],
801 "__access_grp2__": ["__EMPTY__"]
802 }))
803 }
804 }
805 return result
806 }
807
808pub fn intersect_user_classification(&self, user_c12n_1: &str, user_c12n_2: &str, long_format: impl IBool) -> Result<String> {
846 let long_format = long_format.into().unwrap_or(true);
847 if !self.enforce || self.invalid_mode {
848 return Ok(self.unrestricted.clone())
849 }
850
851 let parts1 = self.get_classification_parts(user_c12n_1, long_format, None, false)?;
853 let parts2 = self.get_classification_parts(user_c12n_2, long_format, None, false)?;
854
855 let parts = ParsedClassification {
856 level: parts1.level.min(parts2.level),
857 required: intersection(&parts1.required, &parts2.required),
858 groups: intersection(&parts1.groups, &parts2.groups),
859 subgroups: intersection(&parts1.subgroups, &parts2.subgroups),
860 };
861
862 return self.get_normalized_classification_text(parts, long_format, true)
863 }
864
865 pub fn is_accessible(&self, user_c12n: &str, c12n: &str) -> Result<bool> {
874 if self.invalid_mode {
875 return Ok(false)
876 }
877
878 if !self.enforce {
879 return Ok(true)
880 }
881
882 let parts = self.get_classification_parts(c12n, None, None, false)?;
883 let user = self.get_classification_parts(user_c12n, None, None, false)?;
884
885 if user.level >= parts.level {
886 if !Self::_can_see_required(&user.required, &parts.required) {
887 return Ok(false)
888 }
889 if !Self::_can_see_groups(&user.groups, &parts.groups) {
890 return Ok(false)
891 }
892 if !Self::_can_see_groups(&user.subgroups, &parts.subgroups) {
893 return Ok(false)
894 }
895 return Ok(true)
896 }
897 return Ok(false)
898 }
899
900 pub fn is_valid(&self, c12n: &str) -> bool {
902 self.is_valid_skip_auto(c12n, false)
903 }
904
905 pub fn is_valid_skip_auto(&self, c12n: &str, skip_auto_select: bool) -> bool {
914 if !self.enforce {
915 return true;
916 }
917
918 let n_c12n = match self.normalize_classification_options(c12n, NormalizeOptions{skip_auto_select, ..Default::default()}) {
920 Ok(n_c12n) => n_c12n,
921 Err(_) => return false,
922 };
923
924 let ParsedClassification{level: lvl_idx, required: mut req, mut groups, mut subgroups} = match self.get_classification_parts(c12n, None, None, !skip_auto_select) {
926 Ok(row) => row,
927 Err(_) => return false,
928 };
929 let ParsedClassification{level: n_lvl_idx, required: mut n_req, groups: mut n_groups, subgroups: mut n_subgroups} = match self.get_classification_parts(&n_c12n, None, None, !skip_auto_select) {
930 Ok(row) => row,
931 Err(_) => return false,
932 };
933
934 if lvl_idx != n_lvl_idx { return false }
935
936 req.sort_unstable();
937 n_req.sort_unstable();
938 if req != n_req { return false }
939
940 groups.sort_unstable();
941 n_groups.sort_unstable();
942 if groups != n_groups { return false }
943
944 subgroups.sort_unstable();
945 n_subgroups.sort_unstable();
946 if subgroups != n_subgroups { return false; }
947
948 let c12n = c12n.replace("REL TO ", "");
949 let c12n = c12n.replace("REL ", "");
950 let parts = c12n.split("//").collect_vec();
951
952 if parts.len() > 3 {
954 return false
955 }
956
957
958 let mut parts = parts.iter();
960 let first = *match parts.next() {
961 Some(part) => part,
962 None => return false,
963 };
964 if !self.levels_scores_map.contains_key(first) {
965 return false;
966 }
967
968 let mut check_groups = false;
969 for cur_part in parts {
970 if check_groups { return false }
972
973 let mut items = cur_part.split('/').collect_vec();
974 let mut comma_idx = None;
975 for (idx, i) in items.iter().enumerate() {
976 if i.contains(',') {
977 if comma_idx.is_some() {
978 return false;
979 } else {
980 comma_idx = Some(idx)
981 }
982 }
983 }
984
985 if let Some(comma_idx) = comma_idx {
986 let value = items.remove(comma_idx);
987 items.extend(value.split(',').map(str::trim))
988 }
989
990 for i in items {
991 if !check_groups {
992 if !self.access_req.contains_key(i) {
994 check_groups = true
995 }
996 }
997
998 if check_groups && !self.dynamic_groups {
999 if !self.groups_aliases.contains_key(i) &&
1001 !self.groups.contains_key(i) &&
1002 !self.subgroups_aliases.contains_key(i) &&
1003 !self.subgroups.contains_key(i)
1004 {
1005 return false
1006 }
1007 }
1008 }
1009 }
1010 return true
1011 }
1012
1013 pub fn max_classification(&self, c12n_1: &str, c12n_2: &str, long_format: impl IBool) -> Result<String> {
1023 let long_format = long_format.into().unwrap_or(true);
1024
1025 if !self.enforce || self.invalid_mode {
1026 return Ok(self.unrestricted.clone())
1027 }
1028
1029 let parts1 = self.get_classification_parts(c12n_1, long_format, None, true)?;
1030 let parts2 = self.get_classification_parts(c12n_2, long_format, None, true)?;
1031
1032 let parts = parts1.max(&parts2)?;
1033
1034 return self.get_normalized_classification_text(parts, long_format, false)
1035 }
1036
1037 pub fn min_classification(&self, c12n_1: &str, c12n_2: &str, long_format: impl IBool) -> Result<String> {
1047 let long_format = long_format.into().unwrap_or(true);
1048
1049 if !self.enforce || self.invalid_mode {
1050 return Ok(self.unrestricted.clone())
1051 }
1052
1053 let parts1 = self.get_classification_parts(c12n_1, long_format, None, true)?;
1054 let parts2 = self.get_classification_parts(c12n_2, long_format, None, true)?;
1055
1056 let parts = parts1.min(&parts2);
1057
1058 return self.get_normalized_classification_text(parts, long_format, false)
1059 }
1060
1061 pub fn normalize_classification(&self, c12n: &str) -> Result<String> {
1065 self.normalize_classification_options(c12n, Default::default())
1066 }
1067
1068 pub fn normalize_classification_options(&self, c12n: &str, options: NormalizeOptions) -> Result<String> {
1080 let NormalizeOptions{long_format, skip_auto_select, get_dynamic_groups} = options;
1081
1082 if !self.enforce || self.invalid_mode || c12n.is_empty() {
1083 return Ok(self.unrestricted.clone())
1084 }
1085
1086 let parts = self.get_classification_parts(c12n, long_format, get_dynamic_groups, !skip_auto_select)?;
1093 let new_c12n = self.get_normalized_classification_text(parts, long_format, skip_auto_select)?;
1095 return Ok(new_c12n)
1102 }
1103
1104 pub fn build_user_classification(&self, c12n_1: &str, c12n_2: &str, long_format: impl IBool) -> Result<String> {
1114 let long_format = long_format.into().unwrap_or(true);
1115
1116 if !self.enforce || self.invalid_mode {
1117 return Ok(self.unrestricted.clone())
1118 }
1119
1120 let parts1 = self.get_classification_parts(c12n_1, long_format, None, false)?;
1122 let parts2 = self.get_classification_parts(c12n_2, long_format, None, false)?;
1123
1124 let level = parts1.level.max(parts2.level);
1125 let required = union(&parts1.required, &parts2.required);
1126 let groups = union(&parts1.groups, &parts2.groups);
1127 let subgroups = union(&parts1.subgroups, &parts2.subgroups);
1128
1129 return self.get_normalized_classification_text(ParsedClassification { level, required, groups, subgroups }, long_format, true)
1130 }
1131
1132 pub fn levels(&self) -> &HashMap<i32, ClassificationLevel> {
1134 &self.levels
1135 }
1136
1137 pub fn restricted(&self) -> &str {
1139 self.restricted.as_str()
1140 }
1141
1142 pub fn unrestricted(&self) -> &str {
1144 self.unrestricted.as_str()
1145 }
1146}
1147
1148#[derive(Debug, PartialEq, Default, Clone)]
1150pub struct ParsedClassification {
1151 pub level: i32,
1153 pub required: Vec<String>,
1155 pub groups: Vec<String>,
1157 pub subgroups: Vec<String>,
1159}
1160
1161fn intersection(a: &Vec<String>, b: &Vec<String>) -> Vec<String> {
1163 HashSet::<&String>::from_iter(a).intersection(&HashSet::from_iter(b)).map(|&r|r.clone()).collect()
1164}
1165
1166fn union(a: &[String], b: &[String]) -> Vec<String> {
1168 let mut out = a.to_owned();
1169 out.extend(b.iter().cloned());
1170 out.sort_unstable();
1171 out.dedup();
1172 out
1173}
1174
1175
1176impl ParsedClassification {
1177 fn min(&self, other: &Self) -> Self {
1179 let required = intersection(&self.required, &other.required);
1180
1181 let groups = if self.groups.is_empty() || other.groups.is_empty() {
1182 vec![]
1183 } else {
1184 union(&self.groups, &other.groups)
1185 };
1186
1187 let subgroups = if self.subgroups.is_empty() || other.subgroups.is_empty() {
1188 vec![]
1189 } else {
1190 union(&self.subgroups, &other.subgroups)
1191 };
1192
1193 Self {
1194 level: self.level.min(other.level),
1195 required,
1196 groups,
1197 subgroups,
1198 }
1199 }
1200
1201 fn _max_groups(groups_1: &Vec<String>, groups_2: &Vec<String>) -> Result<Vec<String>> {
1203 let groups = if !groups_1.is_empty() && !groups_2.is_empty() {
1204 intersection(groups_1, groups_2)
1205 } else {
1207 union(groups_1, groups_2)
1208 };
1210
1211 if !groups_1.is_empty() && !groups_2.is_empty() && groups.is_empty() {
1212 return Err(Errors::InvalidClassification(format!("Could not find any intersection between the groups. {groups_1:?} & {groups_2:?}")))
1214 }
1215
1216 return Ok(groups)
1217 }
1218
1219 pub fn max(&self, other: &Self) -> Result<Self> {
1221 let level = self.level.max(other.level);
1222 let required = union(&self.required, &other.required);
1223
1224 let groups = Self::_max_groups(&self.groups, &other.groups)?;
1225 let subgroups = Self::_max_groups(&self.subgroups, &other.subgroups)?;
1226
1227 Ok(Self {
1228 level,
1229 required,
1230 groups,
1231 subgroups,
1232 })
1233 }
1234}
1235
1236pub struct NormalizeOptions {
1238 pub long_format: bool,
1240 pub skip_auto_select: bool,
1242 pub get_dynamic_groups: bool
1244}
1245
1246impl Default for NormalizeOptions {
1247 fn default() -> Self {
1248 Self { long_format: true, skip_auto_select: false, get_dynamic_groups: true }
1249 }
1250}
1251
1252impl NormalizeOptions {
1253 pub fn short() -> Self {
1255 Self{long_format: false, ..Default::default()}
1256 }
1257}
1258
1259pub fn sample_config() -> ClassificationConfig {
1274 ClassificationConfig{
1275 enforce: true,
1276 dynamic_groups: false,
1277 dynamic_groups_type: crate::config::DynamicGroupType::All,
1278 levels: vec![
1279 ClassificationLevel::new(1, "L0", "Level 0", vec!["Open"]),
1280 ClassificationLevel::new(5, "L1", "Level 1", vec![]),
1281 ClassificationLevel::new(15, "L2", "Level 2", vec![]),
1282 ],
1283 groups: vec![
1284 ClassificationGroup::new("A", "Group A"),
1285 ClassificationGroup::new("B", "Group B"),
1286 ClassificationGroup::new_solitary("X", "Group X", "XX"),
1287 ],
1288 required: vec![
1289 ClassificationMarking::new("LE", "Legal Department", vec!["Legal"]),
1290 ClassificationMarking::new("AC", "Accounting", vec!["Acc"]),
1291 ClassificationMarking::new_required("orcon", "Originator Controlled"),
1292 ClassificationMarking::new_required("nocon", "No Contractor Access"),
1293 ],
1294 subgroups: vec![
1295 ClassificationSubGroup::new_aliased("R1", "Reserve One", vec!["R0"]),
1296 ClassificationSubGroup::new_with_required("R2", "Reserve Two", "X"),
1297 ClassificationSubGroup::new_with_limited("R3", "Reserve Three", "X"),
1298 ],
1299 restricted: "L2".to_owned(),
1300 unrestricted: "L0".to_owned(),
1301 }
1302}
1303
1304
1305#[cfg(test)]
1306mod test {
1307
1308 use std::path::Path;
1316
1317 use crate::classification::{NormalizeOptions, ParsedClassification};
1318
1319 use super::{sample_config as setup_config, ClassificationParser, Result};
1320
1321 fn setup() -> ClassificationParser {
1322 ClassificationParser::new(setup_config()).unwrap()
1323 }
1324
1325 #[test]
1326 fn load_yaml() {
1327 let yaml = serde_yaml::to_string(&setup_config()).unwrap();
1328 let file = tempfile::NamedTempFile::new().unwrap();
1329 std::fs::write(file.path(), yaml).unwrap();
1330 assert_eq!(ClassificationParser::load(file.path()).unwrap(), setup());
1331 }
1332
1333 #[test]
1334 fn load_json() {
1335 let json = serde_json::to_string(&setup_config()).unwrap();
1336 println!("{json}");
1337 let file = tempfile::NamedTempFile::new().unwrap();
1338 std::fs::write(file.path(), json).unwrap();
1339 assert_eq!(ClassificationParser::load(file.path()).unwrap(), setup());
1340 }
1341
1342 #[test]
1343 fn bad_files() {
1344 assert!(ClassificationParser::load(Path::new("/not-a-file/not-a-file")).is_err());
1345 assert!(ClassificationParser::load(Path::new("/not-a-file/not-a-file")).unwrap_err().to_string().contains("invalid"));
1346 assert!(format!("{:?}", ClassificationParser::load(Path::new("/not-a-file/not-a-file"))).contains("InvalidDefinition"));
1347
1348 let file = tempfile::NamedTempFile::new().unwrap();
1349 std::fs::write(file.path(), "{}").unwrap();
1350 assert!(ClassificationParser::load(file.path()).is_err());
1351 assert!(ClassificationParser::load(file.path()).unwrap_err().to_string().contains("invalid"));
1352 }
1353
1354 #[test]
1355 fn invalid_classifications() {
1356 let mut config = setup_config();
1357
1358 assert!(ClassificationParser::new(config.clone()).is_ok());
1360 config.levels[1].short_name = "INV".parse().unwrap();
1361 assert!(ClassificationParser::new(config.clone()).is_err());
1362 config.levels[1].short_name = "NULL".parse().unwrap();
1363 assert!(ClassificationParser::new(config.clone()).is_err());
1364
1365 let mut config = setup_config();
1367 config.levels[1].name = "INV".parse().unwrap();
1368 assert!(ClassificationParser::new(config.clone()).is_err());
1369 config.levels[1].name = "NULL".parse().unwrap();
1370 assert!(ClassificationParser::new(config.clone()).is_err());
1371
1372 let mut config = setup_config();
1374 config.levels[0].short_name = "L0".parse().unwrap();
1375 config.levels[1].short_name = "L0".parse().unwrap();
1376 assert!(ClassificationParser::new(config.clone()).is_err());
1377
1378 let mut config = setup_config();
1380 config.levels[0].lvl = 100;
1381 config.levels[1].lvl = 100;
1382 assert!(ClassificationParser::new(config.clone()).is_err());
1383
1384 let mut config = setup_config();
1386 config.required[0].short_name = "AA".parse().unwrap();
1387 config.required[1].short_name = "AA".parse().unwrap();
1388 assert!(ClassificationParser::new(config.clone()).is_err());
1389
1390 let mut config = setup_config();
1392 config.required[0].name = "AA".parse().unwrap();
1393 config.required[1].name = "AA".parse().unwrap();
1394 assert!(ClassificationParser::new(config.clone()).is_err());
1395
1396 let mut config = setup_config();
1398 config.groups[0].short_name = "AA".parse().unwrap();
1399 config.groups[1].short_name = "AA".parse().unwrap();
1400 assert!(ClassificationParser::new(config.clone()).is_err());
1401
1402 let mut config = setup_config();
1404 config.groups[0].name = "AA".parse().unwrap();
1405 config.groups[1].name = "AA".parse().unwrap();
1406 assert!(ClassificationParser::new(config.clone()).is_err());
1407
1408 let mut config = setup_config();
1410 config.subgroups[0].short_name = "AA".parse().unwrap();
1411 config.subgroups[1].short_name = "AA".parse().unwrap();
1412 assert!(ClassificationParser::new(config.clone()).is_err());
1413
1414 let mut config = setup_config();
1416 config.subgroups[0].name = "AA".parse().unwrap();
1417 config.subgroups[1].name = "AA".parse().unwrap();
1418 assert!(ClassificationParser::new(config.clone()).is_err());
1419
1420 let mut config = setup_config();
1422 config.restricted = "XF".to_string();
1423 assert!(ClassificationParser::new(config.clone()).is_err());
1424
1425 let mut config = setup_config();
1427 config.unrestricted = "XF".to_string();
1428 assert!(ClassificationParser::new(config.clone()).is_err());
1429
1430 let mut config = setup_config();
1432 config.levels[0].lvl = 0;
1433 assert!(ClassificationParser::new(config.clone()).is_err());
1434 config.levels[0].lvl = 10002;
1435 assert!(ClassificationParser::new(config.clone()).is_err());
1436 }
1437
1438 #[test]
1439 fn bad_commas() {
1440 let ce = setup();
1441
1442 assert!(ce.is_valid("L1//REL A, B/ORCON/NOCON"));
1443 assert!(!ce.is_valid("L1//REL A, B/ORCON,NOCON"));
1444 assert!(!ce.is_valid("L1//ORCON,NOCON/REL A, B"));
1445
1446 assert_eq!(ce.normalize_classification_options("L1//REL A, B/ORCON/NOCON", NormalizeOptions::short()).unwrap(), "L1//NOCON/ORCON/REL A, B");
1447 }
1448
1449 #[test]
1450 fn typo_errors() {
1451 let ce = setup();
1452 assert!(ce.normalize_classification("L1//REL A, B/ORCON,NOCON").is_err());
1453 assert!(ce.normalize_classification("L1//ORCON,NOCON/REL A, B").is_err());
1454 }
1455
1456 #[test]
1457 fn minimums() {
1458 let ce = setup();
1459
1460 assert_eq!(ce.min_classification("L0", "L0", false).unwrap(), "L0");
1462 assert_eq!(ce.min_classification("L0", "L0", true).unwrap(), "LEVEL 0");
1463 assert_eq!(ce.min_classification("L0", "L1", false).unwrap(), "L0");
1464 assert_eq!(ce.min_classification("L0", "L1", true).unwrap(), "LEVEL 0");
1465 assert_eq!(ce.min_classification("L0", "L2", false).unwrap(), "L0");
1466 assert_eq!(ce.min_classification("L0", "L2", true).unwrap(), "LEVEL 0");
1467 assert_eq!(ce.min_classification("L1", "L0", false).unwrap(), "L0");
1468 assert_eq!(ce.min_classification("L1", "L0", true).unwrap(), "LEVEL 0");
1469 assert_eq!(ce.min_classification("L1", "L1", false).unwrap(), "L1");
1470 assert_eq!(ce.min_classification("L1", "L1", true).unwrap(), "LEVEL 1");
1471 assert_eq!(ce.min_classification("L1", "L2", false).unwrap(), "L1");
1472 assert_eq!(ce.min_classification("L1", "L2", true).unwrap(), "LEVEL 1");
1473 assert_eq!(ce.min_classification("L2", "L0", false).unwrap(), "L0");
1474 assert_eq!(ce.min_classification("L2", "L0", true).unwrap(), "LEVEL 0");
1475 assert_eq!(ce.min_classification("L2", "L1", false).unwrap(), "L1");
1476 assert_eq!(ce.min_classification("L2", "L1", true).unwrap(), "LEVEL 1");
1477 assert_eq!(ce.min_classification("L2", "L2", false).unwrap(), "L2");
1478 assert_eq!(ce.min_classification("L2", "L2", true).unwrap(), "LEVEL 2");
1479 assert_eq!(ce.min_classification("OPEN", "L2", false).unwrap(), "L0");
1480
1481 assert_eq!(ce.min_classification("L0//REL A, B", "L0", false).unwrap(), "L0");
1483 assert_eq!(ce.min_classification("L0//REL A", "L0", true).unwrap(), "LEVEL 0");
1484 assert_eq!(ce.min_classification("L0", "L2//REL A, B", false).unwrap(), "L0");
1485 assert_eq!(ce.min_classification("L0", "L1//REL A", true).unwrap(), "LEVEL 0");
1486 assert_eq!(ce.min_classification("L0//REL A, B", "L1//REL A, B", false).unwrap(), "L0//REL A, B");
1487 assert_eq!(ce.min_classification("L0//REL A, B", "L0//REL A", true).unwrap(), "LEVEL 0//REL TO GROUP A, GROUP B");
1488 assert_eq!(ce.min_classification("L0//REL B", "L0//REL B, A", true).unwrap(), "LEVEL 0//REL TO GROUP A, GROUP B");
1489
1490 assert_eq!(ce.min_classification("L0//R1/R2", "L0", false).unwrap(), "L0");
1492 assert_eq!(ce.min_classification("L0//R1", "L0", true).unwrap(), "LEVEL 0");
1493 assert_eq!(ce.min_classification("L0//R1/R2", "L1//R1/R2", false).unwrap(), "L0//XX/R1/R2");
1494 assert_eq!(ce.min_classification("L0//R1/R2", "L0//R1", true).unwrap(), "LEVEL 0//XX/RESERVE ONE/RESERVE TWO");
1495 }
1496
1497 #[test]
1498 fn maximums() {
1499 let ce = setup();
1500
1501 assert_eq!(ce.max_classification("L0", "L0", false).unwrap(), "L0");
1503 assert_eq!(ce.max_classification("L0", "L0", true).unwrap(), "LEVEL 0");
1504 assert_eq!(ce.max_classification("L0", "L1", false).unwrap(), "L1");
1505 assert_eq!(ce.max_classification("L0", "L1", true).unwrap(), "LEVEL 1");
1506 assert_eq!(ce.max_classification("L0", "L2", false).unwrap(), "L2");
1507 assert_eq!(ce.max_classification("L0", "L2", true).unwrap(), "LEVEL 2");
1508 assert_eq!(ce.max_classification("L1", "L0", false).unwrap(), "L1");
1509 assert_eq!(ce.max_classification("L1", "L0", true).unwrap(), "LEVEL 1");
1510 assert_eq!(ce.max_classification("L1", "L1", false).unwrap(), "L1");
1511 assert_eq!(ce.max_classification("L1", "L1", true).unwrap(), "LEVEL 1");
1512 assert_eq!(ce.max_classification("L1", "L2", false).unwrap(), "L2");
1513 assert_eq!(ce.max_classification("L1", "L2", true).unwrap(), "LEVEL 2");
1514 assert_eq!(ce.max_classification("L2", "L0", false).unwrap(), "L2");
1515 assert_eq!(ce.max_classification("L2", "L0", true).unwrap(), "LEVEL 2");
1516 assert_eq!(ce.max_classification("L2", "L1", false).unwrap(), "L2");
1517 assert_eq!(ce.max_classification("L2", "L1", true).unwrap(), "LEVEL 2");
1518 assert_eq!(ce.max_classification("L2", "L2", false).unwrap(), "L2");
1519 assert_eq!(ce.max_classification("L2", "L2", true).unwrap(), "LEVEL 2");
1520
1521 assert_eq!(ce.max_classification("L0//REL A, B", "L0", false).unwrap(), "L0//REL A, B");
1523 assert_eq!(ce.max_classification("L0//REL A", "L1", true).unwrap(), "LEVEL 1//REL TO GROUP A");
1524 assert_eq!(ce.max_classification("L0", "L2//REL A, B", false).unwrap(), "L2//REL A, B");
1525 assert_eq!(ce.max_classification("L0", "L1//REL A", true).unwrap(), "LEVEL 1//REL TO GROUP A");
1526 assert_eq!(ce.max_classification("L0//REL A, B", "L1//REL A, B", false).unwrap(), "L1//REL A, B");
1527 assert_eq!(ce.max_classification("L0//REL A, B", "L0//REL A", true).unwrap(), "LEVEL 0//REL TO GROUP A");
1528 assert_eq!(ce.max_classification("L0//REL B", "L0//REL B, A", true).unwrap(), "LEVEL 0//REL TO GROUP B");
1529 assert!(ce.max_classification("L0//REL B", "L0//REL A", true).is_err());
1530 assert!(ce.max_classification("L0//REL B", "L0//REL A", false).is_err());
1531
1532 assert_eq!(ce.max_classification("L0//R1/R2", "L0", false).unwrap(), "L0//XX/R1/R2");
1534 assert_eq!(ce.max_classification("L0//R1", "L0", true).unwrap(), "LEVEL 0//RESERVE ONE");
1535 assert_eq!(ce.max_classification("L0//R1/R2", "L1//R1/R2", false).unwrap(), "L1//XX/R1/R2");
1536 assert_eq!(ce.max_classification("L0//R1/R2", "L0//R1", true).unwrap(), "LEVEL 0//XX/RESERVE ONE");
1537 }
1538
1539 #[test]
1540 fn multi_group_alias() {
1541 let mut config = setup_config();
1542 config.groups[0].aliases.push("Alphabet Gang".parse().unwrap());
1543 config.groups[1].aliases.push("Alphabet Gang".parse().unwrap());
1544 let ce = ClassificationParser::new(config).unwrap();
1545
1546 assert_eq!(ce.normalize_classification_options("L0//REL A", NormalizeOptions::short()).unwrap(), "L0//REL A");
1547 assert_eq!(ce.normalize_classification_options("L0//REL A, B", NormalizeOptions::short()).unwrap(), "L0//REL ALPHABET GANG");
1548 assert!(ce.normalize_classification("L0//ALPHABET GANG").is_err())
1549 }
1550
1551 #[test]
1552 fn auto_select_group() {
1553 let mut config = setup_config();
1554 config.groups[0].auto_select = true;
1555 let ce = ClassificationParser::new(config).unwrap();
1556
1557 assert_eq!(ce.normalize_classification_options("L0", NormalizeOptions::short()).unwrap(), "L0");
1558 assert_eq!(ce.normalize_classification_options("L0//REL A", NormalizeOptions::short()).unwrap(), "L0//REL A");
1559 assert_eq!(ce.normalize_classification_options("L0//REL B", NormalizeOptions::short()).unwrap(), "L0//REL A, B");
1560 assert_eq!(ce.normalize_classification_options("L0//REL A, B", NormalizeOptions::short()).unwrap(), "L0//REL A, B");
1561 assert_eq!(ce.normalize_classification_options("L0", NormalizeOptions::default()).unwrap(), "LEVEL 0");
1562 assert_eq!(ce.normalize_classification_options("L0//REL A", NormalizeOptions::default()).unwrap(), "LEVEL 0//REL TO GROUP A");
1563 assert_eq!(ce.normalize_classification_options("L0//REL B", NormalizeOptions::default()).unwrap(), "LEVEL 0//REL TO GROUP A, GROUP B");
1564 assert_eq!(ce.normalize_classification_options("L0//REL A, B", NormalizeOptions::default()).unwrap(), "LEVEL 0//REL TO GROUP A, GROUP B");
1565 assert_eq!(ce.min_classification("L1", "L0//REL B", false).unwrap(), "L0");
1566 assert_eq!(ce.max_classification("L1", "L0//REL B", false).unwrap(), "L1//REL A, B");
1567 }
1568
1569 #[test]
1570 fn auto_select_subgroup() {
1571 let mut config = setup_config();
1572 config.subgroups[0].auto_select = true;
1573 let ce = ClassificationParser::new(config).unwrap();
1574
1575 assert_eq!(ce.normalize_classification_options("L0", NormalizeOptions::short()).unwrap(), "L0");
1576 assert_eq!(ce.normalize_classification_options("L0//R0", NormalizeOptions::short()).unwrap(), "L0//R1");
1577 assert_eq!(ce.normalize_classification_options("L0//R2", NormalizeOptions::short()).unwrap(), "L0//XX/R1/R2");
1578 assert_eq!(ce.normalize_classification_options("L0//R1/R2", NormalizeOptions::short()).unwrap(), "L0//XX/R1/R2");
1579 assert_eq!(ce.normalize_classification_options("L0", NormalizeOptions::default()).unwrap(), "LEVEL 0");
1580 assert_eq!(ce.normalize_classification_options("L0//R1", NormalizeOptions::default()).unwrap(), "LEVEL 0//RESERVE ONE");
1581 assert_eq!(ce.normalize_classification_options("L0//R2", NormalizeOptions::default()).unwrap(), "LEVEL 0//XX/RESERVE ONE/RESERVE TWO");
1582 assert_eq!(ce.normalize_classification_options("L0//R1/R2", NormalizeOptions::default()).unwrap(), "LEVEL 0//XX/RESERVE ONE/RESERVE TWO");
1583 assert_eq!(ce.min_classification("L1", "L0//R2", false).unwrap(), "L0");
1584 assert_eq!(ce.max_classification("L1", "L0//R2", false).unwrap(), "L1//XX/R1/R2");
1585 }
1586
1587 #[test]
1588 fn parts() {
1589 let ce = setup();
1590
1591 assert_eq!(ce.get_classification_parts("L0", None, None, None).unwrap(), ParsedClassification{level: 1, ..Default::default()});
1593 assert_eq!(ce.get_classification_parts("LEVEL 0", None, None, None).unwrap(), ParsedClassification{level: 1, ..Default::default()});
1594 assert_eq!(ce.get_classification_parts("L1", None, None, None).unwrap(), ParsedClassification{level: 5, ..Default::default()});
1595 assert_eq!(ce.get_classification_parts("LEVEL 1", None, None, None).unwrap(), ParsedClassification{level: 5, ..Default::default()});
1596 assert_eq!(ce.get_classification_parts("L0", false, None, None).unwrap(), ParsedClassification{level: 1, ..Default::default()});
1597 assert_eq!(ce.get_classification_parts("LEVEL 0", false, None, None).unwrap(), ParsedClassification{level: 1, ..Default::default()});
1598 assert_eq!(ce.get_classification_parts("L1", false, None, None).unwrap(), ParsedClassification{level: 5, ..Default::default()});
1599 assert_eq!(ce.get_classification_parts("LEVEL 1", false, None, None).unwrap(), ParsedClassification{level: 5, ..Default::default()});
1600
1601 assert_eq!(ce.get_classification_parts("L0//REL A", None, None, None).unwrap(), ParsedClassification{level: 1, groups: vec!["GROUP A".to_owned()], ..Default::default()});
1603 assert_eq!(ce.get_classification_parts("LEVEL 0//REL Group A", None, None, None).unwrap(), ParsedClassification{level: 1, groups: vec!["GROUP A".to_owned()], ..Default::default()});
1604 assert_eq!(ce.get_classification_parts("L0//REL A", false, None, None).unwrap(), ParsedClassification{level: 1, groups: vec!["A".to_owned()], ..Default::default()});
1605 assert_eq!(ce.get_classification_parts("LEVEL 0//REL Group A", false, None, None).unwrap(), ParsedClassification{level: 1, groups: vec!["A".to_owned()], ..Default::default()});
1606
1607 for auto in [true, false] {
1609 assert_eq!(ce.get_classification_parts("L0//R1/R2", false, None, auto).unwrap(), ParsedClassification{level: 1, groups: vec!["X".to_owned()], subgroups: vec!["R1".to_owned(), "R2".to_owned()], ..Default::default()});
1610 assert_eq!(ce.get_classification_parts("L0//R1", false, None, auto).unwrap(), ParsedClassification{level: 1, subgroups: vec!["R1".to_owned()], ..Default::default()});
1611 }
1612 }
1613
1614 #[test]
1615 fn normalize() {
1616 let ce = setup();
1617
1618 assert_eq!(ce.normalize_classification_options("L0", NormalizeOptions::short()).unwrap(), "L0");
1620 assert_eq!(ce.normalize_classification("L1").unwrap(), "LEVEL 1");
1621
1622 assert_eq!(ce.normalize_classification("L0//REL A, B").unwrap(), "LEVEL 0//REL TO GROUP A, GROUP B");
1624 assert_eq!(ce.normalize_classification_options("L0//REL A, B", NormalizeOptions::short()).unwrap(), "L0//REL A, B");
1625 assert_eq!(ce.normalize_classification("L0//REL A").unwrap(), "LEVEL 0//REL TO GROUP A");
1626 assert_eq!(ce.normalize_classification_options("L0//REL A", NormalizeOptions::short()).unwrap(), "L0//REL A");
1627 assert_eq!(ce.normalize_classification("L2//REL A, B").unwrap(), "LEVEL 2//REL TO GROUP A, GROUP B");
1628 assert_eq!(ce.normalize_classification_options("L2//REL A, B", NormalizeOptions::short()).unwrap(), "L2//REL A, B");
1629 assert_eq!(ce.normalize_classification("L1//REL A").unwrap(), "LEVEL 1//REL TO GROUP A");
1630 assert_eq!(ce.normalize_classification_options("L1//REL A", NormalizeOptions::short()).unwrap(), "L1//REL A");
1631 assert_eq!(ce.normalize_classification("L0//REL B").unwrap(), "LEVEL 0//REL TO GROUP B");
1632 assert_eq!(ce.normalize_classification_options("L0//REL B", NormalizeOptions::short()).unwrap(), "L0//REL B");
1633 assert_eq!(ce.normalize_classification("L0//REL B, A").unwrap(), "LEVEL 0//REL TO GROUP A, GROUP B");
1634 assert_eq!(ce.normalize_classification_options("L0//REL B, A", NormalizeOptions::short()).unwrap(), "L0//REL A, B");
1635
1636 assert_eq!(ce.normalize_classification("L1//LE").unwrap(), "LEVEL 1//LEGAL DEPARTMENT");
1638
1639 assert!(ce.normalize_classification("GARBO").is_err());
1641 assert!(ce.normalize_classification("GARBO").unwrap_err().to_string().contains("invalid"));
1642 assert!(ce.normalize_classification("L1//GARBO").is_err());
1643 assert!(ce.normalize_classification("L1//LE//GARBO").is_err());
1644 }
1645
1646 #[test]
1647 fn access_control() -> Result<()> {
1648 let ce = setup();
1649
1650 assert!(ce.is_accessible("L0", "L0")?);
1652 assert!(!ce.is_accessible("L0", "L1")?);
1653 assert!(!ce.is_accessible("L0", "L2")?);
1654 assert!(ce.is_accessible("L1", "L0")?);
1655 assert!(ce.is_accessible("L1", "L1")?);
1656 assert!(!ce.is_accessible("L1", "L2")?);
1657 assert!(ce.is_accessible("L2", "L0")?);
1658 assert!(ce.is_accessible("L2", "L1")?);
1659 assert!(ce.is_accessible("L2", "L2")?);
1660
1661 assert!(!ce.is_accessible("L2", "L0//LE")?);
1663 assert!(ce.is_accessible("L2//LE", "L0//LE")?);
1664
1665 assert!(!ce.is_accessible("L2", "L2//LE/AC")?);
1666 assert!(!ce.is_accessible("L2//LE", "L2//LE/AC")?);
1667 assert!(!ce.is_accessible("L2//AC", "L2//LE/AC")?);
1668 assert!(ce.is_accessible("L2//LE/AC", "L2//LE/AC")?);
1669
1670 assert!(!ce.is_accessible("L2", "L2//ORCON/NOCON")?);
1672 assert!(!ce.is_accessible("L2//ORCON", "L2//ORCON/NOCON")?);
1673 assert!(!ce.is_accessible("L2//NOCON", "L2//ORCON/NOCON")?);
1674 assert!(ce.is_accessible("L2//ORCON/NOCON", "L2//ORCON/NOCON")?);
1675 assert!(!ce.is_accessible("L2//REL A", "L2//REL A,X//R2")?);
1676
1677 assert!(!ce.is_accessible("L2", "L2//REL A")?);
1679 assert!(!ce.is_accessible("L2//REL B", "L2//REL A")?);
1680 assert!(ce.is_accessible("L2//REL B", "L2//REL A, B")?);
1681 assert!(ce.is_accessible("L2//REL B", "L2//REL B")?);
1682 assert!(ce.is_accessible("L2//REL B", "L2")?);
1683
1684 Ok(())
1685 }
1686
1687 #[test]
1689 fn unexpected_subcompartment() -> Result<()> {
1690 let ce = setup();
1691 assert_eq!(ce.normalize_classification("L1//LE")?, "LEVEL 1//LEGAL DEPARTMENT");
1692 assert!(ce.normalize_classification("L1//LE-").is_err());
1693 assert!(ce.normalize_classification("L1//LE-O").is_err());
1694 Ok(())
1695 }
1696
1697 #[test]
1699 fn group_outside_rel() -> Result<()> {
1700 let ce = setup();
1701 assert!(ce.normalize_classification("L1//REL A/G").is_err());
1702 assert!(ce.normalize_classification("L1//REL A/B").is_err());
1703 Ok(())
1704 }
1705
1706 #[test]
1708 fn dynamic_group_error() -> Result<()> {
1709 let mut config = setup_config();
1710 config.dynamic_groups = true;
1711 let ce = ClassificationParser::new(config)?;
1712
1713 assert!(ce.normalize_classification("GARBO").is_err());
1714 assert!(ce.normalize_classification("GARBO").unwrap_err().to_string().contains("invalid"));
1715 assert!(ce.normalize_classification("L1//GARBO").is_err());
1716 assert!(ce.normalize_classification("L1//LE//GARBO").is_err());
1717
1718 assert!(ce.normalize_classification("L1//REL A, B/ORCON,NOCON").is_err());
1719 assert!(ce.normalize_classification("L1//ORCON,NOCON/REL A, B").is_err());
1720
1721 assert!(ce.normalize_classification("L1//REL A/G").is_err());
1722 assert!(ce.normalize_classification("L1//REL A/B").is_err());
1723
1724 return Ok(())
1725 }
1726
1727 #[test]
1728 fn require_group() -> Result<()> {
1729 let ce = setup();
1730 assert_eq!(ce.normalize_classification("L1//R1")?, "LEVEL 1//RESERVE ONE");
1731 assert_eq!(ce.normalize_classification("L1//R2")?, "LEVEL 1//XX/RESERVE TWO");
1732 Ok(())
1733 }
1734
1735 #[test]
1736 fn limited_to_group() -> Result<()> {
1737 let ce = setup();
1738 assert_eq!(ce.normalize_classification("L1//R3")?, "LEVEL 1//RESERVE THREE");
1739 assert_eq!(ce.normalize_classification("L1//R3/REL X")?, "LEVEL 1//XX/RESERVE THREE");
1740 assert!(ce.normalize_classification("L1//R3/REL A").is_err());
1741 assert!(ce.normalize_classification("L1//R3/REL A, X").is_err());
1742 Ok(())
1743 }
1744
1745 #[test]
1746 fn build_user_classification() -> Result<()> {
1747 let ce = setup();
1748
1749 let class = ce.build_user_classification("L1", "L0//LE", false)?;
1750 assert_eq!(class, "L1//LE");
1751
1752 let class = ce.build_user_classification(&class, "L0//REL A", false)?;
1753 assert_eq!(class, "L1//LE//REL A");
1754
1755 let class = ce.build_user_classification(&class, "L0//XX", false)?;
1756 assert_eq!(class, "L1//LE//REL A, X");
1757
1758 let class = ce.build_user_classification(&class, "L0//AC", false)?;
1759 assert_eq!(class, "L1//AC/LE//REL A, X");
1760
1761 let class = ce.build_user_classification(&class, "L2//R1", false)?;
1762 assert_eq!(class, "L2//AC/LE//REL A, X/R1");
1763
1764 let class = ce.build_user_classification(&class, "L0//R2", false)?;
1765 assert_eq!(class, "L2//AC/LE//REL A, X/R1/R2");
1766
1767 Ok(())
1768 }
1769}