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 = definition.unrestricted.clone();
214 new.restricted = definition.restricted.clone();
215
216 new.unrestricted = new.normalize_classification(&definition.unrestricted)?;
217 new.restricted = new.normalize_classification(&definition.restricted)?;
218
219 Ok(new)
228 }
229
230 fn insert_level(&mut self, ll: ClassificationLevel, force: bool) -> Result<()> {
232 if !force {
234 if [INVALID_CLASSIFICATION, INVALID_SHORT_CLASSIFICATION, NULL_CLASSIFICATION].contains(&ll.short_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 if [INVALID_CLASSIFICATION, INVALID_SHORT_CLASSIFICATION, NULL_CLASSIFICATION].contains(&ll.name.as_str()) {
238 return Err(Errors::InvalidDefinition("You cannot use reserved words NULL, INVALID or INV in your classification definition.".to_owned()));
239 }
240
241 if ll.lvl > MAX_LVL {
242 return Err(Errors::InvalidDefinition(format!("Level over maximum classification level of {MAX_LVL}.")))
243 }
244 if ll.lvl < MIN_LVL {
245 return Err(Errors::InvalidDefinition(format!("Level under minimum classification level of {MIN_LVL}.")))
246 }
247 }
248
249 for name in ll.unique_names() {
251 if let Some(level) = self.levels_scores_map.insert(name.to_string(), ll.lvl) {
252 return Err(Errors::InvalidDefinition(format!("Name clash between classification levels: {name} on {level} and {}", ll.lvl)))
253 }
254 }
255
256 if let Some(old) = self.levels.insert(ll.lvl, ll) {
257 return Err(Errors::InvalidDefinition(format!("Duplicate classification level: {}", old.lvl)))
258 }
259 return Ok(())
260 }
261
262fn _get_c12n_level_index(&self, c12n: &str) -> Result<(i32, String)> {
300 let c12n = c12n.trim().to_uppercase();
302
303 let (lvl, remain) = c12n.split_once("//").unwrap_or((&c12n, ""));
304 if let Some(value) = self.levels_scores_map.get(lvl) {
305 return Ok((*value, remain.to_string()))
306 }
307 Err(Errors::InvalidClassification(format!("Classification level '{lvl}' was not found in your classification definition.")))
308 }
309
310 fn _get_c12n_required(&self, c12n: &str, long_format: impl IBool) -> (Vec<String>, Vec<String>) {
312 let long_format = long_format.into().unwrap_or(true);
313
314 let c12n = c12n.trim().to_uppercase();
316
317 let mut return_set: Vec<String> = vec![];
318 let mut others: Vec<String> = vec![];
319
320 for p in c12n.split('/') {
321 if p.is_empty() {
322 continue
323 }
324
325 if let Some(data) = self.access_req.get(p) {
326 if long_format {
327 return_set.push(data.name.to_string());
328 } else {
329 return_set.push(data.short_name.to_string());
330 }
331 } else {
332 others.push(p.to_owned())
333 }
334 }
335
336 return_set.sort_unstable();
337 return_set.dedup();
338 return (return_set, others)
339 }
340
341 fn _get_c12n_groups(&self, c12n_parts: Vec<String>,
343 long_format: impl IBool,
344 get_dynamic_groups: impl IBool,
345 auto_select: impl IBool
346 ) -> Result<(Vec<String>, Vec<String>, Vec<String>)> {
347 let long_format = long_format.into().unwrap_or(true);
348 let get_dynamic_groups = get_dynamic_groups.into().unwrap_or(true);
349 let auto_select = auto_select.into().unwrap_or(false);
350
351 let mut g1_set: Vec<&str> = vec![];
355 let mut g2_set: Vec<&str> = vec![];
356 let mut others = vec![];
357
358 let mut groups = vec![];
359 let mut subgroups = vec![];
360 for gp in c12n_parts {
361 if gp.starts_with("REL ") {
362 let gp = gp.replace("REL TO ", "");
364 let gp = gp.replace("REL ", "");
365 for t in gp.split(',') {
366 groups.extend(t.trim().split('/').map(|x|x.trim().to_owned()));
367 }
368 } else {
369 subgroups.push(gp)
371 }
372 }
373
374 for g in &groups {
375 if let Some(data) = self.groups.get(g) {
376 g1_set.push(data.short_name.as_str());
377 } else if let Some(aliases) = self.groups_aliases.get(g) {
378 for a in aliases {
379 g1_set.push(a)
380 }
381 } else {
382 others.push(g);
383 }
384 }
385
386 for g in &subgroups {
387 if let Some(g) = self.subgroups.get(g) {
388 g2_set.push(g.short_name.as_str());
389 } else if let Some(aliases) = self.subgroups_aliases.get(g) {
390 for a in aliases {
391 g2_set.push(a)
392 }
393 } else if let Some(aliases) = self.groups_aliases.get(g) {
394 if aliases.len() != 1 {
396 return Err(Errors::InvalidClassification(format!("Name used ambiguously: {g}")))
397 }
398 for a in aliases {
399 g1_set.push(a)
400 }
401 } else {
402 return Err(Errors::InvalidClassification(format!("Unrecognized classification part: {g}")))
403 }
404 }
405
406 let others = if self.dynamic_groups && get_dynamic_groups {
407 g1_set.extend(others.iter().map(|s|s.as_str()));
408 vec![]
409 } else {
410 others.iter().map(|s|s.to_string()).collect()
411 };
412
413 g1_set.sort_unstable();
414 g1_set.dedup();
415 g2_set.sort_unstable();
416 g2_set.dedup();
417
418 for subgroup in &g2_set {
420 match self.subgroups.get(*subgroup) {
421 Some(data) => {
422 if let Some(limited) = &data.require_group {
423 g1_set.push(limited.as_str())
424 }
425 },
426 None => {
427 return Err(Errors::InvalidClassification(format!("Unknown subgroup: {subgroup}")))
428 }
429 }
430 }
431
432 for subgroup in &g2_set {
434 match self.subgroups.get(*subgroup) {
435 Some(data) => {
436 if let Some(limited) = &data.limited_to_group {
437 if g1_set.len() > 1 || (g1_set.len() == 1 && g1_set[0] != limited.as_str()) {
438 return Err(Errors::InvalidClassification(format!("Subgroup {subgroup} is limited to group {limited} (found: {})", g1_set.join(", "))))
439 }
440 }
441 },
442 None => {
443 return Err(Errors::InvalidClassification(format!("Unknown subgroup: {subgroup}")))
444 }
445 }
446 }
447
448 if auto_select && !g1_set.is_empty() {
450 g1_set.extend(self.groups_auto_select_short.iter().map(String::as_str))
451 }
452 if auto_select && !g2_set.is_empty() {
453 g2_set.extend(self.subgroups_auto_select_short.iter().map(String::as_str))
454 }
455
456 let (mut g1_set, mut g2_set) = if long_format {
457 let g1: Vec<String> = g1_set.into_iter()
458 .map(|r| match self.groups.get(r) {Some(r) => r.name.to_string(), None => r.to_string()})
459 .collect();
460 let g2: Result<Vec<String>> = g2_set.into_iter()
461 .map(|r| self.subgroups.get(r).ok_or(Errors::InvalidClassification("".to_owned())))
462 .map_ok(|r|r.name.to_string())
463 .collect();
464
465 (g1, g2?)
466 } else {
467 (g1_set.into_iter().map(|r|r.to_owned()).collect_vec(), g2_set.into_iter().map(|r| r.to_owned()).collect_vec())
468 };
469
470 g1_set.sort_unstable();
471 g1_set.dedup();
472 g2_set.sort_unstable();
473 g2_set.dedup();
474
475 return Ok((g1_set, g2_set, others))
476 }
477
478 fn _can_see_required(user_req: &Vec<String>, req: &Vec<String>) -> bool {
480 let req: HashSet<&String> = HashSet::from_iter(req);
481 let user_req = HashSet::from_iter(user_req);
482 return req.is_subset(&user_req)
483 }
484
485 fn _can_see_groups(user_groups: &Vec<String>, required_groups: &[String]) -> bool {
487 if required_groups.is_empty() {
488 return true
489 }
490
491 for g in user_groups {
492 if required_groups.contains(g) {
493 return true
494 }
495 }
496
497 return false
498 }
499
500 pub fn get_normalized_classification_text(&self, parts: ParsedClassification, long_format: bool, skip_auto_select: bool) -> Result<String> {
504 let ParsedClassification{level: lvl_idx, required: req, mut groups, mut subgroups} = parts;
505
506 let group_delim = if long_format {"REL TO "} else {"REL "};
507
508 let mut required_lvl_idx = 0;
510 for r in &req {
511 if let Some(params) = self.access_req.get(r) {
512 required_lvl_idx = required_lvl_idx.max(params.require_lvl.unwrap_or_default())
513 }
514 }
515 let mut out = self.get_classification_level_text(lvl_idx.max(required_lvl_idx), long_format)?;
516
517 let mut req_grp = vec![];
519 for r in &req {
520 if let Some(params) = self.access_req.get(r) {
521 if params.is_required_group {
522 req_grp.push(r.clone());
523 }
524 }
525 }
526 let req = req.into_iter().filter(|item|!req_grp.contains(item)).collect_vec();
528
529 if !req.is_empty() {
530 out += &("//".to_owned() + &req.join("/"));
531 }
532 if !req_grp.is_empty() {
533 req_grp.sort_unstable();
534 out += &("//".to_owned() + &req_grp.join("/"));
535 }
536
537 if long_format {
539 if !subgroups.is_empty() && !self.subgroups_auto_select.is_empty() && !skip_auto_select {
540 subgroups.extend(self.subgroups_auto_select.iter().cloned());
542 }
543 } else {
544 if !subgroups.is_empty() && !self.subgroups_auto_select_short.is_empty() && !skip_auto_select {
545 subgroups.extend(self.subgroups_auto_select_short.iter().cloned())
546 }
548 }
549 subgroups.sort_unstable();
550 subgroups.dedup();
551
552 let mut temp_groups = vec![];
554 for sg in &subgroups {
555 if let Some(subgroup) = self.subgroups.get(sg) {
556 if let Some(require_group) = &subgroup.require_group {
557 temp_groups.push(require_group.clone())
558 }
559
560 if let Some(limited_to_group) = &subgroup.limited_to_group {
561 if temp_groups.contains(limited_to_group) {
562 temp_groups = vec![limited_to_group.clone()]
563 } else {
564 temp_groups.clear()
565 }
566 }
567 }
568 }
569
570 for g in &temp_groups {
571 if let Some(data) = self.groups.get(g.as_str()) {
572 if long_format {
573 groups.push(data.name.to_string())
574 } else {
575 groups.push(data.short_name.to_string())
576 }
577 } else {
578 groups.push(g.to_string())
579 }
580 }
581
582 if long_format {
584 if !groups.is_empty() && !self.groups_auto_select.is_empty() && !skip_auto_select {
585 groups.extend(self.groups_auto_select.iter().cloned());
586 }
587 } else {
588 if !groups.is_empty() && !self.groups_auto_select_short.is_empty() && !skip_auto_select {
589 groups.extend(self.groups_auto_select_short.iter().cloned());
590 }
591 }
592 groups.sort_unstable();
593 groups.dedup();
594
595 if !groups.is_empty() {
596 out += if req_grp.is_empty() {"//"} else {"/"};
597 if groups.len() == 1 {
598 let grp = &groups[0];
600 if let Some(group_data) = self.groups.get(grp) {
601 if let Some(display_name) = &group_data.solitary_display_name {
602 out += display_name.as_str();
603 } else {
604 out += group_delim;
605 out += grp;
606 }
607 }
608 } else {
609 if !long_format {
610 let group_set: HashSet<String> = groups.iter().cloned().collect();
612 for (alias, values) in self.groups_aliases.iter() {
613 if values.len() > 1 && *values == group_set {
614 groups = vec![alias.clone()]
615 }
616 }
617 }
618 out += group_delim;
619 out += &groups.join(", ");
620 }
621 }
622
623 if !subgroups.is_empty() {
624 if groups.is_empty() && req_grp.is_empty() {
625 out += "//"
626 } else {
627 out += "/"
628 }
629 subgroups.sort_unstable();
630 out += &subgroups.join("/");
631 }
632
633 return Ok(out)
634 }
635
636 pub fn get_classification_level_text(&self, lvl_idx: i32, long_format: bool) -> Result<String> {
638 if let Some(data) = self.levels.get(&lvl_idx) {
639 if long_format {
640 return Ok(data.name.to_string())
641 } else {
642 return Ok(data.short_name.to_string())
643 }
644 }
645
646 Err(Errors::InvalidClassification(format!("Classification level number '{lvl_idx}' was not found in your classification definition.")))
647 }
648
649 pub fn get_classification_parts(&self, c12n: &str, long_format: impl IBool, get_dynamic_groups: impl IBool, auto_select: impl IBool) -> Result<ParsedClassification> {
651 let (level, remain) = self._get_c12n_level_index(c12n)?;
652 let (required, unparsed_required) = self._get_c12n_required(&remain, long_format);
653 let (groups, subgroups, unparsed_groups) = self._get_c12n_groups(unparsed_required, long_format, get_dynamic_groups, auto_select)?;
654
655 if !unparsed_groups.is_empty() {
656 return Err(Errors::InvalidClassification(format!("Unknown parts: {}", unparsed_groups.join(", "))))
657 }
658
659 Ok(ParsedClassification { level, required, groups, subgroups })
660 }
661
662 pub fn get_access_control_parts(&self, c12n: &str, user_classification: bool) -> Result<serde_json::Value> {
779 let c12n = if !self.enforce || self.invalid_mode {
780 self.unrestricted.clone()
781 } else {
782 c12n.to_owned()
783 };
784
785 let result: Result<serde_json::Value> = (||{
786 let parts = self.get_classification_parts(&c12n, false, true, !user_classification)?;
788
789 return Ok(serde_json::json!({
790 "__access_lvl__": parts.level,
791 "__access_req__": parts.required,
792 "__access_grp1__": if parts.groups.is_empty() { vec!["__EMPTY__".to_owned()] } else { parts.groups },
793 "__access_grp2__": if parts.subgroups.is_empty() { vec!["__EMPTY__".to_owned()] } else { parts.subgroups }
794 }))
795 })();
796
797 if let Err(Errors::InvalidClassification(_)) = &result {
798 if !self.enforce || self.invalid_mode {
799 return Ok(serde_json::json!({
800 "__access_lvl__": NULL_LVL,
801 "__access_req__": [],
802 "__access_grp1__": ["__EMPTY__"],
803 "__access_grp2__": ["__EMPTY__"]
804 }))
805 }
806 }
807 return result
808 }
809
810pub fn intersect_user_classification(&self, user_c12n_1: &str, user_c12n_2: &str, long_format: impl IBool) -> Result<String> {
848 let long_format = long_format.into().unwrap_or(true);
849 if !self.enforce || self.invalid_mode {
850 return Ok(self.unrestricted.clone())
851 }
852
853 let parts1 = self.get_classification_parts(user_c12n_1, long_format, None, false)?;
855 let parts2 = self.get_classification_parts(user_c12n_2, long_format, None, false)?;
856
857 let parts = ParsedClassification {
858 level: parts1.level.min(parts2.level),
859 required: intersection(&parts1.required, &parts2.required),
860 groups: intersection(&parts1.groups, &parts2.groups),
861 subgroups: intersection(&parts1.subgroups, &parts2.subgroups),
862 };
863
864 return self.get_normalized_classification_text(parts, long_format, true)
865 }
866
867 pub fn is_accessible(&self, user_c12n: &str, c12n: &str) -> Result<bool> {
876 if self.invalid_mode {
877 return Ok(false)
878 }
879
880 if !self.enforce {
881 return Ok(true)
882 }
883
884 let parts = self.get_classification_parts(c12n, None, None, false)?;
885 let user = self.get_classification_parts(user_c12n, None, None, false)?;
886
887 if user.level >= parts.level {
888 if !Self::_can_see_required(&user.required, &parts.required) {
889 return Ok(false)
890 }
891 if !Self::_can_see_groups(&user.groups, &parts.groups) {
892 return Ok(false)
893 }
894 if !Self::_can_see_groups(&user.subgroups, &parts.subgroups) {
895 return Ok(false)
896 }
897 return Ok(true)
898 }
899 return Ok(false)
900 }
901
902 pub fn is_valid(&self, c12n: &str) -> bool {
904 self.is_valid_skip_auto(c12n, false)
905 }
906
907 pub fn is_valid_skip_auto(&self, c12n: &str, skip_auto_select: bool) -> bool {
916 if !self.enforce {
917 return true;
918 }
919
920 let n_c12n = match self.normalize_classification_options(c12n, NormalizeOptions{skip_auto_select, ..Default::default()}) {
922 Ok(n_c12n) => n_c12n,
923 Err(_) => return false,
924 };
925
926 let ParsedClassification{level: lvl_idx, required: mut req, mut groups, mut subgroups} = match self.get_classification_parts(c12n, None, None, !skip_auto_select) {
928 Ok(row) => row,
929 Err(_) => return false,
930 };
931 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) {
932 Ok(row) => row,
933 Err(_) => return false,
934 };
935
936 if lvl_idx != n_lvl_idx { return false }
937
938 req.sort_unstable();
939 n_req.sort_unstable();
940 if req != n_req { return false }
941
942 groups.sort_unstable();
943 n_groups.sort_unstable();
944 if groups != n_groups { return false }
945
946 subgroups.sort_unstable();
947 n_subgroups.sort_unstable();
948 if subgroups != n_subgroups { return false; }
949
950 let c12n = c12n.replace("REL TO ", "");
951 let c12n = c12n.replace("REL ", "");
952 let parts = c12n.split("//").collect_vec();
953
954 if parts.len() > 3 {
956 return false
957 }
958
959
960 let mut parts = parts.iter();
962 let first = *match parts.next() {
963 Some(part) => part,
964 None => return false,
965 };
966 if !self.levels_scores_map.contains_key(first) {
967 return false;
968 }
969
970 let mut check_groups = false;
971 for cur_part in parts {
972 if check_groups { return false }
974
975 let mut items = cur_part.split('/').collect_vec();
976 let mut comma_idx = None;
977 for (idx, i) in items.iter().enumerate() {
978 if i.contains(',') {
979 if comma_idx.is_some() {
980 return false;
981 } else {
982 comma_idx = Some(idx)
983 }
984 }
985 }
986
987 if let Some(comma_idx) = comma_idx {
988 let value = items.remove(comma_idx);
989 items.extend(value.split(',').map(str::trim))
990 }
991
992 for i in items {
993 if !check_groups {
994 if !self.access_req.contains_key(i) {
996 check_groups = true
997 }
998 }
999
1000 if check_groups && !self.dynamic_groups {
1001 if !self.groups_aliases.contains_key(i) &&
1003 !self.groups.contains_key(i) &&
1004 !self.subgroups_aliases.contains_key(i) &&
1005 !self.subgroups.contains_key(i)
1006 {
1007 return false
1008 }
1009 }
1010 }
1011 }
1012 return true
1013 }
1014
1015 pub fn max_classification(&self, c12n_1: &str, c12n_2: &str, long_format: impl IBool) -> Result<String> {
1025 let long_format = long_format.into().unwrap_or(true);
1026
1027 if !self.enforce || self.invalid_mode {
1028 return Ok(self.unrestricted.clone())
1029 }
1030
1031 let parts1 = self.get_classification_parts(c12n_1, long_format, None, true)?;
1032 let parts2 = self.get_classification_parts(c12n_2, long_format, None, true)?;
1033
1034 let parts = parts1.max(&parts2)?;
1035
1036 return self.get_normalized_classification_text(parts, long_format, false)
1037 }
1038
1039 pub fn min_classification(&self, c12n_1: &str, c12n_2: &str, long_format: impl IBool) -> Result<String> {
1049 let long_format = long_format.into().unwrap_or(true);
1050
1051 if !self.enforce || self.invalid_mode {
1052 return Ok(self.unrestricted.clone())
1053 }
1054
1055 let parts1 = self.get_classification_parts(c12n_1, long_format, None, true)?;
1056 let parts2 = self.get_classification_parts(c12n_2, long_format, None, true)?;
1057
1058 let parts = parts1.min(&parts2);
1059
1060 return self.get_normalized_classification_text(parts, long_format, false)
1061 }
1062
1063 pub fn normalize_classification(&self, c12n: &str) -> Result<String> {
1067 self.normalize_classification_options(c12n, Default::default())
1068 }
1069
1070 pub fn normalize_classification_options(&self, c12n: &str, options: NormalizeOptions) -> Result<String> {
1082 let NormalizeOptions{long_format, skip_auto_select, get_dynamic_groups} = options;
1083
1084 if !self.enforce || self.invalid_mode || c12n.is_empty() {
1085 return Ok(self.unrestricted.clone())
1086 }
1087
1088 let parts = self.get_classification_parts(c12n, long_format, get_dynamic_groups, !skip_auto_select)?;
1095 let new_c12n = self.get_normalized_classification_text(parts, long_format, skip_auto_select)?;
1097 return Ok(new_c12n)
1104 }
1105
1106 pub fn build_user_classification(&self, c12n_1: &str, c12n_2: &str, long_format: impl IBool) -> Result<String> {
1116 let long_format = long_format.into().unwrap_or(true);
1117
1118 if !self.enforce || self.invalid_mode {
1119 return Ok(self.unrestricted.clone())
1120 }
1121
1122 let parts1 = self.get_classification_parts(c12n_1, long_format, None, false)?;
1124 let parts2 = self.get_classification_parts(c12n_2, long_format, None, false)?;
1125
1126 let level = parts1.level.max(parts2.level);
1127 let required = union(&parts1.required, &parts2.required);
1128 let groups = union(&parts1.groups, &parts2.groups);
1129 let subgroups = union(&parts1.subgroups, &parts2.subgroups);
1130
1131 return self.get_normalized_classification_text(ParsedClassification { level, required, groups, subgroups }, long_format, true)
1132 }
1133
1134 pub fn levels(&self) -> &HashMap<i32, ClassificationLevel> {
1136 &self.levels
1137 }
1138
1139 pub fn restricted(&self) -> &str {
1141 self.restricted.as_str()
1142 }
1143
1144 pub fn unrestricted(&self) -> &str {
1146 self.unrestricted.as_str()
1147 }
1148}
1149
1150#[derive(Debug, PartialEq, Default, Clone)]
1152pub struct ParsedClassification {
1153 pub level: i32,
1155 pub required: Vec<String>,
1157 pub groups: Vec<String>,
1159 pub subgroups: Vec<String>,
1161}
1162
1163fn intersection(a: &Vec<String>, b: &Vec<String>) -> Vec<String> {
1165 HashSet::<&String>::from_iter(a).intersection(&HashSet::from_iter(b)).map(|&r|r.clone()).collect()
1166}
1167
1168fn union(a: &[String], b: &[String]) -> Vec<String> {
1170 let mut out = a.to_owned();
1171 out.extend(b.iter().cloned());
1172 out.sort_unstable();
1173 out.dedup();
1174 out
1175}
1176
1177
1178impl ParsedClassification {
1179 fn min(&self, other: &Self) -> Self {
1181 let required = intersection(&self.required, &other.required);
1182
1183 let groups = if self.groups.is_empty() || other.groups.is_empty() {
1184 vec![]
1185 } else {
1186 union(&self.groups, &other.groups)
1187 };
1188
1189 let subgroups = if self.subgroups.is_empty() || other.subgroups.is_empty() {
1190 vec![]
1191 } else {
1192 union(&self.subgroups, &other.subgroups)
1193 };
1194
1195 Self {
1196 level: self.level.min(other.level),
1197 required,
1198 groups,
1199 subgroups,
1200 }
1201 }
1202
1203 fn _max_groups(groups_1: &Vec<String>, groups_2: &Vec<String>) -> Result<Vec<String>> {
1205 let groups = if !groups_1.is_empty() && !groups_2.is_empty() {
1206 intersection(groups_1, groups_2)
1207 } else {
1209 union(groups_1, groups_2)
1210 };
1212
1213 if !groups_1.is_empty() && !groups_2.is_empty() && groups.is_empty() {
1214 return Err(Errors::InvalidClassification(format!("Could not find any intersection between the groups. {groups_1:?} & {groups_2:?}")))
1216 }
1217
1218 return Ok(groups)
1219 }
1220
1221 pub fn max(&self, other: &Self) -> Result<Self> {
1223 let level = self.level.max(other.level);
1224 let required = union(&self.required, &other.required);
1225
1226 let groups = Self::_max_groups(&self.groups, &other.groups)?;
1227 let subgroups = Self::_max_groups(&self.subgroups, &other.subgroups)?;
1228
1229 Ok(Self {
1230 level,
1231 required,
1232 groups,
1233 subgroups,
1234 })
1235 }
1236}
1237
1238pub struct NormalizeOptions {
1240 pub long_format: bool,
1242 pub skip_auto_select: bool,
1244 pub get_dynamic_groups: bool
1246}
1247
1248impl Default for NormalizeOptions {
1249 fn default() -> Self {
1250 Self { long_format: true, skip_auto_select: false, get_dynamic_groups: true }
1251 }
1252}
1253
1254impl NormalizeOptions {
1255 pub fn short() -> Self {
1257 Self{long_format: false, ..Default::default()}
1258 }
1259}
1260
1261pub fn sample_config() -> ClassificationConfig {
1276 ClassificationConfig{
1277 enforce: true,
1278 dynamic_groups: false,
1279 dynamic_groups_type: crate::config::DynamicGroupType::All,
1280 levels: vec![
1281 ClassificationLevel::new(1, "L0", "Level 0", vec!["Open"]),
1282 ClassificationLevel::new(5, "L1", "Level 1", vec![]),
1283 ClassificationLevel::new(15, "L2", "Level 2", vec![]),
1284 ],
1285 groups: vec![
1286 ClassificationGroup::new("A", "Group A"),
1287 ClassificationGroup::new("B", "Group B"),
1288 ClassificationGroup::new_solitary("X", "Group X", "XX"),
1289 ],
1290 required: vec![
1291 ClassificationMarking::new("LE", "Legal Department", vec!["Legal"]),
1292 ClassificationMarking::new("AC", "Accounting", vec!["Acc"]),
1293 ClassificationMarking::new_required("orcon", "Originator Controlled"),
1294 ClassificationMarking::new_required("nocon", "No Contractor Access"),
1295 ],
1296 subgroups: vec![
1297 ClassificationSubGroup::new_aliased("R1", "Reserve One", vec!["R0"]),
1298 ClassificationSubGroup::new_with_required("R2", "Reserve Two", "X"),
1299 ClassificationSubGroup::new_with_limited("R3", "Reserve Three", "X"),
1300 ],
1301 restricted: "L2".to_owned(),
1302 unrestricted: "L0".to_owned(),
1303 }
1304}
1305
1306
1307#[cfg(test)]
1308mod test {
1309
1310 use std::path::Path;
1318
1319 use crate::classification::{NormalizeOptions, ParsedClassification};
1320
1321 use super::{sample_config as setup_config, ClassificationParser, Result};
1322
1323 fn setup() -> ClassificationParser {
1324 ClassificationParser::new(setup_config()).unwrap()
1325 }
1326
1327 #[test]
1328 fn load_yaml() {
1329 let yaml = serde_yaml::to_string(&setup_config()).unwrap();
1330 let file = tempfile::NamedTempFile::new().unwrap();
1331 std::fs::write(file.path(), yaml).unwrap();
1332 assert_eq!(ClassificationParser::load(file.path()).unwrap(), setup());
1333 }
1334
1335 #[test]
1336 fn load_json() {
1337 let json = serde_json::to_string(&setup_config()).unwrap();
1338 println!("{json}");
1339 let file = tempfile::NamedTempFile::new().unwrap();
1340 std::fs::write(file.path(), json).unwrap();
1341 assert_eq!(ClassificationParser::load(file.path()).unwrap(), setup());
1342 }
1343
1344 #[test]
1345 fn bad_files() {
1346 assert!(ClassificationParser::load(Path::new("/not-a-file/not-a-file")).is_err());
1347 assert!(ClassificationParser::load(Path::new("/not-a-file/not-a-file")).unwrap_err().to_string().contains("invalid"));
1348 assert!(format!("{:?}", ClassificationParser::load(Path::new("/not-a-file/not-a-file"))).contains("InvalidDefinition"));
1349
1350 let file = tempfile::NamedTempFile::new().unwrap();
1351 std::fs::write(file.path(), "{}").unwrap();
1352 assert!(ClassificationParser::load(file.path()).is_err());
1353 assert!(ClassificationParser::load(file.path()).unwrap_err().to_string().contains("invalid"));
1354 }
1355
1356 #[test]
1357 fn invalid_classifications() {
1358 let mut config = setup_config();
1359
1360 assert!(ClassificationParser::new(config.clone()).is_ok());
1362 config.levels[1].short_name = "INV".parse().unwrap();
1363 assert!(ClassificationParser::new(config.clone()).is_err());
1364 config.levels[1].short_name = "NULL".parse().unwrap();
1365 assert!(ClassificationParser::new(config.clone()).is_err());
1366
1367 let mut config = setup_config();
1369 config.levels[1].name = "INV".parse().unwrap();
1370 assert!(ClassificationParser::new(config.clone()).is_err());
1371 config.levels[1].name = "NULL".parse().unwrap();
1372 assert!(ClassificationParser::new(config.clone()).is_err());
1373
1374 let mut config = setup_config();
1376 config.levels[0].short_name = "L0".parse().unwrap();
1377 config.levels[1].short_name = "L0".parse().unwrap();
1378 assert!(ClassificationParser::new(config.clone()).is_err());
1379
1380 let mut config = setup_config();
1382 config.levels[0].lvl = 100;
1383 config.levels[1].lvl = 100;
1384 assert!(ClassificationParser::new(config.clone()).is_err());
1385
1386 let mut config = setup_config();
1388 config.required[0].short_name = "AA".parse().unwrap();
1389 config.required[1].short_name = "AA".parse().unwrap();
1390 assert!(ClassificationParser::new(config.clone()).is_err());
1391
1392 let mut config = setup_config();
1394 config.required[0].name = "AA".parse().unwrap();
1395 config.required[1].name = "AA".parse().unwrap();
1396 assert!(ClassificationParser::new(config.clone()).is_err());
1397
1398 let mut config = setup_config();
1400 config.groups[0].short_name = "AA".parse().unwrap();
1401 config.groups[1].short_name = "AA".parse().unwrap();
1402 assert!(ClassificationParser::new(config.clone()).is_err());
1403
1404 let mut config = setup_config();
1406 config.groups[0].name = "AA".parse().unwrap();
1407 config.groups[1].name = "AA".parse().unwrap();
1408 assert!(ClassificationParser::new(config.clone()).is_err());
1409
1410 let mut config = setup_config();
1412 config.subgroups[0].short_name = "AA".parse().unwrap();
1413 config.subgroups[1].short_name = "AA".parse().unwrap();
1414 assert!(ClassificationParser::new(config.clone()).is_err());
1415
1416 let mut config = setup_config();
1418 config.subgroups[0].name = "AA".parse().unwrap();
1419 config.subgroups[1].name = "AA".parse().unwrap();
1420 assert!(ClassificationParser::new(config.clone()).is_err());
1421
1422 let mut config = setup_config();
1424 config.restricted = "XF".to_string();
1425 assert!(ClassificationParser::new(config.clone()).is_err());
1426
1427 let mut config = setup_config();
1429 config.unrestricted = "XF".to_string();
1430 assert!(ClassificationParser::new(config.clone()).is_err());
1431
1432 let mut config = setup_config();
1434 config.levels[0].lvl = 0;
1435 assert!(ClassificationParser::new(config.clone()).is_err());
1436 config.levels[0].lvl = 10002;
1437 assert!(ClassificationParser::new(config.clone()).is_err());
1438 }
1439
1440 #[test]
1441 fn bad_commas() {
1442 let ce = setup();
1443
1444 assert!(ce.is_valid("L1//REL A, B/ORCON/NOCON"));
1445 assert!(!ce.is_valid("L1//REL A, B/ORCON,NOCON"));
1446 assert!(!ce.is_valid("L1//ORCON,NOCON/REL A, B"));
1447
1448 assert_eq!(ce.normalize_classification_options("L1//REL A, B/ORCON/NOCON", NormalizeOptions::short()).unwrap(), "L1//NOCON/ORCON/REL A, B");
1449 }
1450
1451 #[test]
1452 fn typo_errors() {
1453 let ce = setup();
1454 assert!(ce.normalize_classification("L1//REL A, B/ORCON,NOCON").is_err());
1455 assert!(ce.normalize_classification("L1//ORCON,NOCON/REL A, B").is_err());
1456 }
1457
1458 #[test]
1459 fn minimums() {
1460 let ce = setup();
1461
1462 assert_eq!(ce.min_classification("L0", "L0", false).unwrap(), "L0");
1464 assert_eq!(ce.min_classification("L0", "L0", true).unwrap(), "LEVEL 0");
1465 assert_eq!(ce.min_classification("L0", "L1", false).unwrap(), "L0");
1466 assert_eq!(ce.min_classification("L0", "L1", true).unwrap(), "LEVEL 0");
1467 assert_eq!(ce.min_classification("L0", "L2", false).unwrap(), "L0");
1468 assert_eq!(ce.min_classification("L0", "L2", true).unwrap(), "LEVEL 0");
1469 assert_eq!(ce.min_classification("L1", "L0", false).unwrap(), "L0");
1470 assert_eq!(ce.min_classification("L1", "L0", true).unwrap(), "LEVEL 0");
1471 assert_eq!(ce.min_classification("L1", "L1", false).unwrap(), "L1");
1472 assert_eq!(ce.min_classification("L1", "L1", true).unwrap(), "LEVEL 1");
1473 assert_eq!(ce.min_classification("L1", "L2", false).unwrap(), "L1");
1474 assert_eq!(ce.min_classification("L1", "L2", true).unwrap(), "LEVEL 1");
1475 assert_eq!(ce.min_classification("L2", "L0", false).unwrap(), "L0");
1476 assert_eq!(ce.min_classification("L2", "L0", true).unwrap(), "LEVEL 0");
1477 assert_eq!(ce.min_classification("L2", "L1", false).unwrap(), "L1");
1478 assert_eq!(ce.min_classification("L2", "L1", true).unwrap(), "LEVEL 1");
1479 assert_eq!(ce.min_classification("L2", "L2", false).unwrap(), "L2");
1480 assert_eq!(ce.min_classification("L2", "L2", true).unwrap(), "LEVEL 2");
1481 assert_eq!(ce.min_classification("OPEN", "L2", false).unwrap(), "L0");
1482
1483 assert_eq!(ce.min_classification("L0//REL A, B", "L0", false).unwrap(), "L0");
1485 assert_eq!(ce.min_classification("L0//REL A", "L0", true).unwrap(), "LEVEL 0");
1486 assert_eq!(ce.min_classification("L0", "L2//REL A, B", false).unwrap(), "L0");
1487 assert_eq!(ce.min_classification("L0", "L1//REL A", true).unwrap(), "LEVEL 0");
1488 assert_eq!(ce.min_classification("L0//REL A, B", "L1//REL A, B", false).unwrap(), "L0//REL A, B");
1489 assert_eq!(ce.min_classification("L0//REL A, B", "L0//REL A", true).unwrap(), "LEVEL 0//REL TO GROUP A, GROUP B");
1490 assert_eq!(ce.min_classification("L0//REL B", "L0//REL B, A", true).unwrap(), "LEVEL 0//REL TO GROUP A, GROUP B");
1491
1492 assert_eq!(ce.min_classification("L0//R1/R2", "L0", false).unwrap(), "L0");
1494 assert_eq!(ce.min_classification("L0//R1", "L0", true).unwrap(), "LEVEL 0");
1495 assert_eq!(ce.min_classification("L0//R1/R2", "L1//R1/R2", false).unwrap(), "L0//XX/R1/R2");
1496 assert_eq!(ce.min_classification("L0//R1/R2", "L0//R1", true).unwrap(), "LEVEL 0//XX/RESERVE ONE/RESERVE TWO");
1497 }
1498
1499 #[test]
1500 fn maximums() {
1501 let ce = setup();
1502
1503 assert_eq!(ce.max_classification("L0", "L0", false).unwrap(), "L0");
1505 assert_eq!(ce.max_classification("L0", "L0", true).unwrap(), "LEVEL 0");
1506 assert_eq!(ce.max_classification("L0", "L1", false).unwrap(), "L1");
1507 assert_eq!(ce.max_classification("L0", "L1", true).unwrap(), "LEVEL 1");
1508 assert_eq!(ce.max_classification("L0", "L2", false).unwrap(), "L2");
1509 assert_eq!(ce.max_classification("L0", "L2", true).unwrap(), "LEVEL 2");
1510 assert_eq!(ce.max_classification("L1", "L0", false).unwrap(), "L1");
1511 assert_eq!(ce.max_classification("L1", "L0", true).unwrap(), "LEVEL 1");
1512 assert_eq!(ce.max_classification("L1", "L1", false).unwrap(), "L1");
1513 assert_eq!(ce.max_classification("L1", "L1", true).unwrap(), "LEVEL 1");
1514 assert_eq!(ce.max_classification("L1", "L2", false).unwrap(), "L2");
1515 assert_eq!(ce.max_classification("L1", "L2", true).unwrap(), "LEVEL 2");
1516 assert_eq!(ce.max_classification("L2", "L0", false).unwrap(), "L2");
1517 assert_eq!(ce.max_classification("L2", "L0", true).unwrap(), "LEVEL 2");
1518 assert_eq!(ce.max_classification("L2", "L1", false).unwrap(), "L2");
1519 assert_eq!(ce.max_classification("L2", "L1", true).unwrap(), "LEVEL 2");
1520 assert_eq!(ce.max_classification("L2", "L2", false).unwrap(), "L2");
1521 assert_eq!(ce.max_classification("L2", "L2", true).unwrap(), "LEVEL 2");
1522
1523 assert_eq!(ce.max_classification("L0//REL A, B", "L0", false).unwrap(), "L0//REL A, B");
1525 assert_eq!(ce.max_classification("L0//REL A", "L1", true).unwrap(), "LEVEL 1//REL TO GROUP A");
1526 assert_eq!(ce.max_classification("L0", "L2//REL A, B", false).unwrap(), "L2//REL A, B");
1527 assert_eq!(ce.max_classification("L0", "L1//REL A", true).unwrap(), "LEVEL 1//REL TO GROUP A");
1528 assert_eq!(ce.max_classification("L0//REL A, B", "L1//REL A, B", false).unwrap(), "L1//REL A, B");
1529 assert_eq!(ce.max_classification("L0//REL A, B", "L0//REL A", true).unwrap(), "LEVEL 0//REL TO GROUP A");
1530 assert_eq!(ce.max_classification("L0//REL B", "L0//REL B, A", true).unwrap(), "LEVEL 0//REL TO GROUP B");
1531 assert!(ce.max_classification("L0//REL B", "L0//REL A", true).is_err());
1532 assert!(ce.max_classification("L0//REL B", "L0//REL A", false).is_err());
1533
1534 assert_eq!(ce.max_classification("L0//R1/R2", "L0", false).unwrap(), "L0//XX/R1/R2");
1536 assert_eq!(ce.max_classification("L0//R1", "L0", true).unwrap(), "LEVEL 0//RESERVE ONE");
1537 assert_eq!(ce.max_classification("L0//R1/R2", "L1//R1/R2", false).unwrap(), "L1//XX/R1/R2");
1538 assert_eq!(ce.max_classification("L0//R1/R2", "L0//R1", true).unwrap(), "LEVEL 0//XX/RESERVE ONE");
1539 }
1540
1541 #[test]
1542 fn multi_group_alias() {
1543 let mut config = setup_config();
1544 config.groups[0].aliases.push("Alphabet Gang".parse().unwrap());
1545 config.groups[1].aliases.push("Alphabet Gang".parse().unwrap());
1546 let ce = ClassificationParser::new(config).unwrap();
1547
1548 assert_eq!(ce.normalize_classification_options("L0//REL A", NormalizeOptions::short()).unwrap(), "L0//REL A");
1549 assert_eq!(ce.normalize_classification_options("L0//REL A, B", NormalizeOptions::short()).unwrap(), "L0//REL ALPHABET GANG");
1550 assert!(ce.normalize_classification("L0//ALPHABET GANG").is_err())
1551 }
1552
1553 #[test]
1554 fn auto_select_group() {
1555 let mut config = setup_config();
1556 config.groups[0].auto_select = true;
1557 let ce = ClassificationParser::new(config).unwrap();
1558
1559 assert_eq!(ce.normalize_classification_options("L0", NormalizeOptions::short()).unwrap(), "L0");
1560 assert_eq!(ce.normalize_classification_options("L0//REL A", NormalizeOptions::short()).unwrap(), "L0//REL A");
1561 assert_eq!(ce.normalize_classification_options("L0//REL B", NormalizeOptions::short()).unwrap(), "L0//REL A, B");
1562 assert_eq!(ce.normalize_classification_options("L0//REL A, B", NormalizeOptions::short()).unwrap(), "L0//REL A, B");
1563 assert_eq!(ce.normalize_classification_options("L0", NormalizeOptions::default()).unwrap(), "LEVEL 0");
1564 assert_eq!(ce.normalize_classification_options("L0//REL A", NormalizeOptions::default()).unwrap(), "LEVEL 0//REL TO GROUP A");
1565 assert_eq!(ce.normalize_classification_options("L0//REL B", NormalizeOptions::default()).unwrap(), "LEVEL 0//REL TO GROUP A, GROUP B");
1566 assert_eq!(ce.normalize_classification_options("L0//REL A, B", NormalizeOptions::default()).unwrap(), "LEVEL 0//REL TO GROUP A, GROUP B");
1567 assert_eq!(ce.min_classification("L1", "L0//REL B", false).unwrap(), "L0");
1568 assert_eq!(ce.max_classification("L1", "L0//REL B", false).unwrap(), "L1//REL A, B");
1569 }
1570
1571 #[test]
1572 fn auto_select_subgroup() {
1573 let mut config = setup_config();
1574 config.subgroups[0].auto_select = true;
1575 let ce = ClassificationParser::new(config).unwrap();
1576
1577 assert_eq!(ce.normalize_classification_options("L0", NormalizeOptions::short()).unwrap(), "L0");
1578 assert_eq!(ce.normalize_classification_options("L0//R0", NormalizeOptions::short()).unwrap(), "L0//R1");
1579 assert_eq!(ce.normalize_classification_options("L0//R2", NormalizeOptions::short()).unwrap(), "L0//XX/R1/R2");
1580 assert_eq!(ce.normalize_classification_options("L0//R1/R2", NormalizeOptions::short()).unwrap(), "L0//XX/R1/R2");
1581 assert_eq!(ce.normalize_classification_options("L0", NormalizeOptions::default()).unwrap(), "LEVEL 0");
1582 assert_eq!(ce.normalize_classification_options("L0//R1", NormalizeOptions::default()).unwrap(), "LEVEL 0//RESERVE ONE");
1583 assert_eq!(ce.normalize_classification_options("L0//R2", NormalizeOptions::default()).unwrap(), "LEVEL 0//XX/RESERVE ONE/RESERVE TWO");
1584 assert_eq!(ce.normalize_classification_options("L0//R1/R2", NormalizeOptions::default()).unwrap(), "LEVEL 0//XX/RESERVE ONE/RESERVE TWO");
1585 assert_eq!(ce.min_classification("L1", "L0//R2", false).unwrap(), "L0");
1586 assert_eq!(ce.max_classification("L1", "L0//R2", false).unwrap(), "L1//XX/R1/R2");
1587 }
1588
1589 #[test]
1590 fn parts() {
1591 let ce = setup();
1592
1593 assert_eq!(ce.get_classification_parts("L0", None, None, None).unwrap(), ParsedClassification{level: 1, ..Default::default()});
1595 assert_eq!(ce.get_classification_parts("LEVEL 0", None, None, None).unwrap(), ParsedClassification{level: 1, ..Default::default()});
1596 assert_eq!(ce.get_classification_parts("L1", None, None, None).unwrap(), ParsedClassification{level: 5, ..Default::default()});
1597 assert_eq!(ce.get_classification_parts("LEVEL 1", None, None, None).unwrap(), ParsedClassification{level: 5, ..Default::default()});
1598 assert_eq!(ce.get_classification_parts("L0", false, None, None).unwrap(), ParsedClassification{level: 1, ..Default::default()});
1599 assert_eq!(ce.get_classification_parts("LEVEL 0", false, None, None).unwrap(), ParsedClassification{level: 1, ..Default::default()});
1600 assert_eq!(ce.get_classification_parts("L1", false, None, None).unwrap(), ParsedClassification{level: 5, ..Default::default()});
1601 assert_eq!(ce.get_classification_parts("LEVEL 1", false, None, None).unwrap(), ParsedClassification{level: 5, ..Default::default()});
1602
1603 assert_eq!(ce.get_classification_parts("L0//REL A", None, None, None).unwrap(), ParsedClassification{level: 1, groups: vec!["GROUP A".to_owned()], ..Default::default()});
1605 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()});
1606 assert_eq!(ce.get_classification_parts("L0//REL A", false, None, None).unwrap(), ParsedClassification{level: 1, groups: vec!["A".to_owned()], ..Default::default()});
1607 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()});
1608
1609 for auto in [true, false] {
1611 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()});
1612 assert_eq!(ce.get_classification_parts("L0//R1", false, None, auto).unwrap(), ParsedClassification{level: 1, subgroups: vec!["R1".to_owned()], ..Default::default()});
1613 }
1614 }
1615
1616 #[test]
1617 fn normalize() {
1618 let ce = setup();
1619
1620 assert_eq!(ce.normalize_classification_options("L0", NormalizeOptions::short()).unwrap(), "L0");
1622 assert_eq!(ce.normalize_classification("L1").unwrap(), "LEVEL 1");
1623
1624 assert_eq!(ce.normalize_classification("L0//REL A, B").unwrap(), "LEVEL 0//REL TO GROUP A, GROUP B");
1626 assert_eq!(ce.normalize_classification_options("L0//REL A, B", NormalizeOptions::short()).unwrap(), "L0//REL A, B");
1627 assert_eq!(ce.normalize_classification("L0//REL A").unwrap(), "LEVEL 0//REL TO GROUP A");
1628 assert_eq!(ce.normalize_classification_options("L0//REL A", NormalizeOptions::short()).unwrap(), "L0//REL A");
1629 assert_eq!(ce.normalize_classification("L2//REL A, B").unwrap(), "LEVEL 2//REL TO GROUP A, GROUP B");
1630 assert_eq!(ce.normalize_classification_options("L2//REL A, B", NormalizeOptions::short()).unwrap(), "L2//REL A, B");
1631 assert_eq!(ce.normalize_classification("L1//REL A").unwrap(), "LEVEL 1//REL TO GROUP A");
1632 assert_eq!(ce.normalize_classification_options("L1//REL A", NormalizeOptions::short()).unwrap(), "L1//REL A");
1633 assert_eq!(ce.normalize_classification("L0//REL B").unwrap(), "LEVEL 0//REL TO GROUP B");
1634 assert_eq!(ce.normalize_classification_options("L0//REL B", NormalizeOptions::short()).unwrap(), "L0//REL B");
1635 assert_eq!(ce.normalize_classification("L0//REL B, A").unwrap(), "LEVEL 0//REL TO GROUP A, GROUP B");
1636 assert_eq!(ce.normalize_classification_options("L0//REL B, A", NormalizeOptions::short()).unwrap(), "L0//REL A, B");
1637
1638 assert_eq!(ce.normalize_classification("L1//LE").unwrap(), "LEVEL 1//LEGAL DEPARTMENT");
1640
1641 assert!(ce.normalize_classification("GARBO").is_err());
1643 assert!(ce.normalize_classification("GARBO").unwrap_err().to_string().contains("invalid"));
1644 assert!(ce.normalize_classification("L1//GARBO").is_err());
1645 assert!(ce.normalize_classification("L1//LE//GARBO").is_err());
1646 }
1647
1648 #[test]
1649 fn access_control() -> Result<()> {
1650 let ce = setup();
1651
1652 assert!(ce.is_accessible("L0", "L0")?);
1654 assert!(!ce.is_accessible("L0", "L1")?);
1655 assert!(!ce.is_accessible("L0", "L2")?);
1656 assert!(ce.is_accessible("L1", "L0")?);
1657 assert!(ce.is_accessible("L1", "L1")?);
1658 assert!(!ce.is_accessible("L1", "L2")?);
1659 assert!(ce.is_accessible("L2", "L0")?);
1660 assert!(ce.is_accessible("L2", "L1")?);
1661 assert!(ce.is_accessible("L2", "L2")?);
1662
1663 assert!(!ce.is_accessible("L2", "L0//LE")?);
1665 assert!(ce.is_accessible("L2//LE", "L0//LE")?);
1666
1667 assert!(!ce.is_accessible("L2", "L2//LE/AC")?);
1668 assert!(!ce.is_accessible("L2//LE", "L2//LE/AC")?);
1669 assert!(!ce.is_accessible("L2//AC", "L2//LE/AC")?);
1670 assert!(ce.is_accessible("L2//LE/AC", "L2//LE/AC")?);
1671
1672 assert!(!ce.is_accessible("L2", "L2//ORCON/NOCON")?);
1674 assert!(!ce.is_accessible("L2//ORCON", "L2//ORCON/NOCON")?);
1675 assert!(!ce.is_accessible("L2//NOCON", "L2//ORCON/NOCON")?);
1676 assert!(ce.is_accessible("L2//ORCON/NOCON", "L2//ORCON/NOCON")?);
1677 assert!(!ce.is_accessible("L2//REL A", "L2//REL A,X//R2")?);
1678
1679 assert!(!ce.is_accessible("L2", "L2//REL A")?);
1681 assert!(!ce.is_accessible("L2//REL B", "L2//REL A")?);
1682 assert!(ce.is_accessible("L2//REL B", "L2//REL A, B")?);
1683 assert!(ce.is_accessible("L2//REL B", "L2//REL B")?);
1684 assert!(ce.is_accessible("L2//REL B", "L2")?);
1685
1686 Ok(())
1687 }
1688
1689 #[test]
1691 fn unexpected_subcompartment() -> Result<()> {
1692 let ce = setup();
1693 assert_eq!(ce.normalize_classification("L1//LE")?, "LEVEL 1//LEGAL DEPARTMENT");
1694 assert!(ce.normalize_classification("L1//LE-").is_err());
1695 assert!(ce.normalize_classification("L1//LE-O").is_err());
1696 Ok(())
1697 }
1698
1699 #[test]
1701 fn group_outside_rel() -> Result<()> {
1702 let ce = setup();
1703 assert!(ce.normalize_classification("L1//REL A/G").is_err());
1704 assert!(ce.normalize_classification("L1//REL A/B").is_err());
1705 Ok(())
1706 }
1707
1708 #[test]
1710 fn dynamic_group_error() -> Result<()> {
1711 let mut config = setup_config();
1712 config.dynamic_groups = true;
1713 let ce = ClassificationParser::new(config)?;
1714
1715 assert!(ce.normalize_classification("GARBO").is_err());
1716 assert!(ce.normalize_classification("GARBO").unwrap_err().to_string().contains("invalid"));
1717 assert!(ce.normalize_classification("L1//GARBO").is_err());
1718 assert!(ce.normalize_classification("L1//LE//GARBO").is_err());
1719
1720 assert!(ce.normalize_classification("L1//REL A, B/ORCON,NOCON").is_err());
1721 assert!(ce.normalize_classification("L1//ORCON,NOCON/REL A, B").is_err());
1722
1723 assert!(ce.normalize_classification("L1//REL A/G").is_err());
1724 assert!(ce.normalize_classification("L1//REL A/B").is_err());
1725
1726 return Ok(())
1727 }
1728
1729 #[test]
1730 fn require_group() -> Result<()> {
1731 let ce = setup();
1732 assert_eq!(ce.normalize_classification("L1//R1")?, "LEVEL 1//RESERVE ONE");
1733 assert_eq!(ce.normalize_classification("L1//R2")?, "LEVEL 1//XX/RESERVE TWO");
1734 Ok(())
1735 }
1736
1737 #[test]
1738 fn limited_to_group() -> Result<()> {
1739 let ce = setup();
1740 assert_eq!(ce.normalize_classification("L1//R3")?, "LEVEL 1//RESERVE THREE");
1741 assert_eq!(ce.normalize_classification("L1//R3/REL X")?, "LEVEL 1//XX/RESERVE THREE");
1742 assert!(ce.normalize_classification("L1//R3/REL A").is_err());
1743 assert!(ce.normalize_classification("L1//R3/REL A, X").is_err());
1744 Ok(())
1745 }
1746
1747 #[test]
1748 fn build_user_classification() -> Result<()> {
1749 let ce = setup();
1750
1751 let class = ce.build_user_classification("L1", "L0//LE", false)?;
1752 assert_eq!(class, "L1//LE");
1753
1754 let class = ce.build_user_classification(&class, "L0//REL A", false)?;
1755 assert_eq!(class, "L1//LE//REL A");
1756
1757 let class = ce.build_user_classification(&class, "L0//XX", false)?;
1758 assert_eq!(class, "L1//LE//REL A, X");
1759
1760 let class = ce.build_user_classification(&class, "L0//AC", false)?;
1761 assert_eq!(class, "L1//AC/LE//REL A, X");
1762
1763 let class = ce.build_user_classification(&class, "L2//R1", false)?;
1764 assert_eq!(class, "L2//AC/LE//REL A, X/R1");
1765
1766 let class = ce.build_user_classification(&class, "L0//R2", false)?;
1767 assert_eq!(class, "L2//AC/LE//REL A, X/R1/R2");
1768
1769 Ok(())
1770 }
1771}