1use std::collections::HashMap;
4use std::fmt;
5use std::fmt::{Display, Formatter};
6use std::time::Instant;
7
8use cedar_policy::{Diagnostics, Entities, EntityUid, Request, Response};
9use chrono::{Local, Utc};
10use derive_builder::Builder;
11use serde::{Deserialize, Serialize};
12use serde_json::{to_value, Map, Value};
13use serde_repr::{Deserialize_repr, Serialize_repr};
14
15use crate::public::log::error::OcsfException;
16use crate::public::log::error::OcsfException::OcsfFieldsValidationError;
17use crate::public::log::{FieldLevel, FieldSet};
18
19const ALLOWED_ENRICHMENT_ARRAY_LEN: usize = 5;
21const ALLOWED_ACTIVITY_NAME_LEN: usize = 35;
23
24const OCSF_SCHEMA_VERSION: &str = "1.0.0";
26const LOG_VERSION: &str = "1.0.0";
28const VENDOR_NAME: &str = "cedar::simple::authorizer";
30const SECRET_STRING: &str = "Sensitive<REDACTED>";
32
33#[derive(Default, Builder, Serialize, Deserialize, Eq, PartialEq, Debug, Clone)]
40#[builder(
41 setter(into),
42 build_fn(validate = "Self::validate_ocsf_fields", error = "OcsfException")
43)]
44pub struct OpenCyberSecurityFramework {
45 #[builder(default)]
47 #[serde(skip_serializing_if = "Option::is_none")]
48 pub activity_name: Option<String>,
49 pub activity_id: ActivityId,
51 #[builder(default = "Some(\"Identity & Access Management\".to_string())")]
53 #[serde(skip_serializing_if = "Option::is_none")]
54 pub category_name: Option<String>,
55 #[builder(default = "3u8")]
57 pub category_uid: u8,
58 #[builder(default = "Some(\"Entity Management\".to_string())")]
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub class_name: Option<String>,
62 #[builder(default = "3004u64")]
64 pub class_uid: u64,
65 #[builder(default)]
67 #[serde(skip_serializing_if = "Option::is_none")]
68 pub comment: Option<String>,
69 #[builder(default)]
72 #[serde(skip_serializing_if = "Option::is_none")]
73 pub count: Option<u64>,
74 #[builder(default)]
76 #[serde(skip_serializing_if = "Option::is_none")]
77 pub duration: Option<i64>,
78 #[builder(default)]
80 #[serde(skip_serializing_if = "Option::is_none")]
81 pub end_time: Option<i64>,
82 #[builder(default)]
85 #[serde(skip_serializing_if = "Option::is_none")]
86 pub enrichments: Option<Vec<EnrichmentArray>>,
87 pub entity: ManagedEntity,
89 #[builder(default)]
91 #[serde(skip_serializing_if = "Option::is_none")]
92 pub entity_result: Option<ManagedEntity>,
93 pub time: i64,
95 #[builder(default)]
98 #[serde(skip_serializing_if = "Option::is_none")]
99 pub message: Option<String>,
100 pub metadata: MetaData,
102 #[builder(default)]
104 #[serde(skip_serializing_if = "Option::is_none")]
105 pub observables: Option<Vec<Observable>>,
106 #[builder(default)]
108 #[serde(skip_serializing_if = "Option::is_none")]
109 pub raw_data: Option<String>,
110 #[builder(default)]
112 #[serde(skip_serializing_if = "Option::is_none")]
113 pub severity: Option<String>,
114 pub severity_id: SeverityId,
116 #[builder(default)]
118 #[serde(skip_serializing_if = "Option::is_none")]
119 pub start_time: Option<i64>,
120 #[builder(default)]
122 #[serde(skip_serializing_if = "Option::is_none")]
123 pub status: Option<String>,
124 #[builder(default)]
126 #[serde(skip_serializing_if = "Option::is_none")]
127 pub status_code: Option<String>,
128 #[builder(default)]
130 #[serde(skip_serializing_if = "Option::is_none")]
131 pub status_detail: Option<String>,
132 #[builder(default)]
134 #[serde(skip_serializing_if = "Option::is_none")]
135 pub status_id: Option<StatusId>,
136 #[builder(default)]
139 #[serde(skip_serializing_if = "Option::is_none")]
140 pub timezone_offset: Option<i32>,
141 pub type_uid: TypeUid,
144 #[builder(default)]
146 #[serde(skip_serializing_if = "Option::is_none")]
147 pub type_name: Option<String>,
148 #[builder(default)]
151 #[serde(skip_serializing_if = "Option::is_none")]
152 pub unmapped: Option<Value>,
153}
154
155impl OpenCyberSecurityFramework {
156 pub fn create(
164 request: &Request,
165 response: &Response,
166 entities: &Entities,
167 fields: &FieldSet,
168 authorizer_name: &str,
169 ) -> Result<Self, OcsfException> {
170 let decision = response.decision();
171 Self::create_generic(
172 request,
173 response.diagnostics(),
174 format!("decision is {decision:?}").as_str(),
175 format!("{decision:?}"),
176 entities,
177 fields,
178 authorizer_name,
179 )
180 }
181
182 pub fn create_generic(
189 request: &Request,
190 diagnostics: &Diagnostics,
191 outcome: &str,
192 status_code: String,
193 entities: &Entities,
194 fields: &FieldSet,
195 authorizer_name: &str,
196 ) -> Result<Self, OcsfException> {
197 let filtered_request = filter_request(request, entities, fields);
198 let start_time = Instant::now();
199
200 let mut unmapped = Map::new();
201 if let Some(context_str) = filtered_request.clone().context {
202 unmapped.insert("context".to_string(), to_value(context_str)?);
203 } else {
204 unmapped.insert("context".to_string(), to_value(SECRET_STRING)?);
205 }
206
207 let principal = filtered_request.principal.get_id();
211 let action = filtered_request.action.get_id();
212 let resource = filtered_request.resource.get_id();
213
214 let reasons: Vec<String> = diagnostics.reason().map(ToString::to_string).collect();
215 unmapped.insert(
216 "determined_policies".to_string(),
217 to_value(reasons.clone())?,
218 );
219
220 let response_error: Vec<String> = diagnostics
221 .errors()
222 .map(std::string::ToString::to_string)
223 .collect();
224 unmapped.insert(
225 "evaluation_errors".to_string(),
226 to_value(response_error.clone())?,
227 );
228
229 let (status_id, status, status_details);
230
231 if response_error.is_empty() {
232 status_id = StatusId::Success;
233 status = "Success".to_string();
234 status_details = reasons.join(",");
235 } else {
236 status_id = StatusId::Failure;
237 status = "Failure".to_string();
238 status_details = response_error.join(",");
239 }
240
241 let message = format!(
242 "Principal {principal} performed action \
243 {action} on {resource}, the {outcome} \
244 determined by policy id {reasons:?} and errors {response_error:?}",
245 );
246
247 let product = ProductBuilder::default()
248 .vendor_name(VENDOR_NAME)
249 .name(authorizer_name.to_string())
250 .lang("en".to_string())
251 .build()?;
252
253 let (severity_id, severity) = build_ocsf_severity(response_error.len());
254
255 let source_entity = generate_managed_entity(
256 filtered_request.entities.as_ref(),
257 &filtered_request.principal,
258 )?;
259 let resource_entity = generate_managed_entity(
260 filtered_request.entities.as_ref(),
261 &filtered_request.resource,
262 )?;
263 let action_entity =
264 generate_managed_entity(filtered_request.entities.as_ref(), &filtered_request.action)?;
265 unmapped.insert(
266 "action_entity_details".to_string(),
267 to_value(action_entity)?,
268 );
269
270 let timezone_offset = Local::now().offset().local_minus_utc() / 60;
271
272 let activity_id = ActivityId::from(action.to_string());
273 let type_uid = TypeUid::from(activity_id.clone());
274
275 let metadata = MetaDataBuilder::default()
276 .version(OCSF_SCHEMA_VERSION)
277 .product(product)
278 .log_provider(VENDOR_NAME.to_string())
279 .logged_time(Utc::now().timestamp())
280 .log_version(LOG_VERSION.to_string())
281 .processed_time(start_time.elapsed().as_millis())
282 .build()?;
283
284 OpenCyberSecurityFrameworkBuilder::default()
285 .activity_name(action)
286 .activity_id(activity_id)
287 .entity(source_entity)
288 .entity_result(resource_entity)
289 .message(message)
290 .type_uid(type_uid.clone())
291 .type_name(type_uid.to_string())
292 .severity(severity)
293 .severity_id(severity_id)
294 .metadata(metadata)
295 .time(Utc::now().timestamp())
296 .timezone_offset(timezone_offset)
297 .status_id(status_id)
298 .status(status)
299 .status_detail(status_details)
300 .status_code(status_code)
301 .unmapped(to_value(unmapped)?)
302 .build()
303 }
304
305 pub fn error(error_message: String, authorizer_name: String) -> Self {
307 let product = ProductBuilder::default()
308 .vendor_name(VENDOR_NAME)
309 .name(authorizer_name)
310 .build()
311 .unwrap_or_default();
312
313 OpenCyberSecurityFrameworkBuilder::default()
314 .type_uid(TypeUid::Other)
315 .severity_id(SeverityId::Other)
316 .metadata(
317 MetaDataBuilder::default()
318 .version(OCSF_SCHEMA_VERSION)
319 .product(product)
320 .build()
321 .unwrap_or_default(),
322 )
323 .time(Utc::now().timestamp())
324 .entity(
325 ManagedEntityBuilder::default()
326 .name("N/A".to_string())
327 .build()
328 .unwrap_or_default(),
329 )
330 .activity_id(ActivityId::Other)
331 .message(error_message)
332 .build()
333 .unwrap_or_default()
334 }
335}
336
337fn filter_request(request: &Request, entities: &Entities, fields: &FieldSet) -> FilteredRequest {
338 let mut builder = FilteredRequestBuilder::default();
339
340 if fields.principal {
341 builder.principal(request.principal().cloned());
342 }
343 if fields.action {
344 builder.action(request.action().cloned());
345 }
346 if fields.resource {
347 builder.resource(request.resource().cloned());
348 }
349
350 if fields.context {
353 builder.context(request.to_string());
354 }
355
356 let entities = match fields.entities {
357 FieldLevel::All => Some(entities.clone()),
358 FieldLevel::Custom(filter_fn) => Some(filter_fn(entities)),
359 FieldLevel::None | FieldLevel::Unknown => None,
360 };
361
362 builder.entities(entities);
363 builder.build().unwrap_or_default()
364}
365
366fn generate_managed_entity(
367 entities: Option<&Entities>,
368 component: &EntityComponent,
369) -> Result<ManagedEntity, OcsfException> {
370 let mut entity_details_map = Map::new();
373
374 let mut parents = Vec::<String>::new();
375 if let EntityComponent::Concrete(entity_uid) = component {
376 parents = entities.as_ref().map_or_else(Vec::<String>::new, |e| {
377 e.ancestors(entity_uid)
378 .map_or_else(Vec::<String>::new, |e| e.map(ToString::to_string).collect())
379 });
380 }
381
382 entity_details_map.insert("Parents".to_string(), to_value(parents)?);
383 Ok(ManagedEntityBuilder::default()
384 .name(component.get_id())
385 .entity_type(component.get_type_name())
386 .data(to_value(entity_details_map)?)
387 .build()?)
388}
389
390fn build_ocsf_severity(num_of_errors: usize) -> (SeverityId, String) {
391 match num_of_errors {
392 0 => (SeverityId::Informational, "Informational".to_string()),
393 1 => (SeverityId::Low, "Low".to_string()),
394 _ => (SeverityId::Medium, "Medium".to_string()),
395 }
396}
397
398impl OpenCyberSecurityFrameworkBuilder {
399 fn validate_ocsf_fields(&self) -> Result<(), OcsfException> {
406 let is_enrichments_valid: bool = self.enrichments.as_ref().is_none_or(|enrichments| {
407 enrichments
408 .as_ref()
409 .is_none_or(|vec| vec.len() < ALLOWED_ENRICHMENT_ARRAY_LEN)
410 });
411
412 let is_activity_name_valid: bool =
413 self.activity_name.as_ref().is_none_or(|activity_name| {
414 activity_name
415 .as_ref()
416 .is_none_or(|s| s.len() < ALLOWED_ACTIVITY_NAME_LEN)
417 });
418
419 if is_enrichments_valid && is_activity_name_valid {
420 Ok(())
421 } else {
422 Err(OcsfFieldsValidationError(format!(
423 "Either the Enrichments array exceeds the maximum allowed size of \
424 {ALLOWED_ENRICHMENT_ARRAY_LEN} elements, or the Activity name exceeds the \
425 maximum allowed length of {ALLOWED_ACTIVITY_NAME_LEN} characters... "
426 )))
427 }
428 }
429}
430
431#[derive(Debug, Serialize_repr, Deserialize_repr, Clone, Eq, PartialEq, Default)]
433#[repr(u8)]
434pub enum ActivityId {
435 #[default]
437 Unknown = 0,
438 Create = 1,
440 Read = 2,
442 Update = 3,
444 Delete = 4,
446 Other = 99,
448}
449
450impl From<String> for ActivityId {
452 fn from(activity_name: String) -> Self {
453 let activity_name_lower_case = activity_name.to_lowercase();
454
455 match activity_name_lower_case.as_str() {
456 "read" => Self::Read,
457 "update" => Self::Update,
458 "delete" => Self::Delete,
459 "unknown" => Self::Unknown,
460 _ => Self::Other,
461 }
462 }
463}
464
465#[derive(Debug, Serialize_repr, Deserialize_repr, Clone, Eq, PartialEq, Default)]
468#[repr(u8)]
469pub enum SeverityId {
470 #[default]
472 Unknown = 0,
473 Informational = 1,
475 Low = 2,
477 Medium = 3,
479 High = 4,
481 Critical = 5,
483 Fatal = 6,
485 Other = 99,
487}
488
489#[derive(Debug, Serialize_repr, Deserialize_repr, Clone, Eq, PartialEq, Default)]
491#[repr(u8)]
492pub enum StatusId {
493 #[default]
495 Unknown = 0,
496 Success = 1,
498 Failure = 2,
500 Other = 99,
502}
503
504#[derive(Debug, Serialize_repr, Deserialize_repr, Clone, Eq, PartialEq, Default)]
506#[repr(u64)]
507pub enum TypeUid {
508 #[default]
510 Unknown = 300_400,
511 Create = 300_401,
513 Read = 300_402,
515 Update = 300_403,
517 Delete = 300_404,
519 Other = 300_499,
521}
522
523impl fmt::Display for TypeUid {
525 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
526 write!(f, "{self:?}")
527 }
528}
529
530impl From<ActivityId> for TypeUid {
532 fn from(activity_id: ActivityId) -> Self {
533 match activity_id {
534 ActivityId::Unknown => Self::Unknown,
535 ActivityId::Create => Self::Create,
536 ActivityId::Read => Self::Read,
537 ActivityId::Update => Self::Update,
538 ActivityId::Delete => Self::Delete,
539 ActivityId::Other => Self::Other,
540 }
541 }
542}
543
544#[derive(Debug, Serialize_repr, Deserialize_repr, Clone, Eq, PartialEq, Default)]
546#[repr(u8)]
547pub enum ObservableTypeId {
548 #[default]
550 Unknown = 0,
551 Hostname = 1,
553 IPAddress = 2,
555 MACAddress = 3,
557 #[allow(clippy::doc_markdown)]
558 UserName = 4,
560 #[allow(clippy::doc_markdown)]
561 EmailAddress = 5,
563 URLString = 6,
565 FileName = 7,
567 FileHash = 8,
569 ProcessName = 9,
571 ResourceUID = 10,
573 Endpoint = 20,
577 User = 21,
580 Email = 22,
583 UniformResourceLocator = 23,
586 File = 24,
589 Process = 25,
592 GeoLocation = 26,
595 Container = 27,
597 RegistryKey = 28,
600 RegistryValue = 29,
602 Fingerprint = 30,
606 Other = 99,
608}
609
610#[derive(Debug, Serialize_repr, Deserialize_repr, Clone, Eq, PartialEq, Default)]
612#[repr(u8)]
613pub enum ReputationScoreId {
614 #[default]
616 Unknown = 0,
617 VerySafe = 1,
619 Safe = 2,
621 ProbablySafe = 3,
623 LeansSafe = 4,
625 MayNotBeSafe = 5,
627 ExerciseCaution = 6,
629 SuspiciousRisky = 7,
631 PossiblyMalicious = 8,
634 ProbablyMalicious = 9,
636 Malicious = 10,
638 Other = 99,
640}
641
642#[derive(Default, Serialize, Deserialize, Builder, Eq, PartialEq, Debug, Clone)]
645#[builder(setter(into))]
646pub struct EnrichmentArray {
647 pub data: HashMap<String, Vec<String>>,
649 pub name: String,
651 pub value: String,
653 #[builder(default)]
655 #[serde(skip_serializing_if = "Option::is_none")]
656 pub provider: Option<String>,
657 #[serde(rename = "type")]
659 #[builder(default)]
660 #[serde(skip_serializing_if = "Option::is_none")]
661 pub enrichment_type: Option<String>,
662}
663
664#[derive(Default, Serialize, Deserialize, Builder, Eq, PartialEq, Debug, Clone)]
667#[builder(setter(into))]
668pub struct Observable {
669 pub type_id: ObservableTypeId,
671 pub name: String,
673 #[builder(default)]
676 #[serde(skip_serializing_if = "Option::is_none")]
677 pub value: Option<String>,
678 #[serde(rename = "type")]
680 #[builder(default)]
681 #[serde(skip_serializing_if = "Option::is_none")]
682 pub observable_type: Option<String>,
683 #[builder(default)]
685 #[serde(skip_serializing_if = "Option::is_none")]
686 pub reputation: Option<Reputation>,
687}
688
689#[derive(Default, Serialize, Deserialize, Builder, Eq, PartialEq, Debug, Clone)]
692#[builder(setter(into))]
693pub struct Reputation {
694 pub score_id: ReputationScoreId,
696 pub base_score: u8,
698 #[builder(default)]
700 #[serde(skip_serializing_if = "Option::is_none")]
701 pub provider: Option<String>,
702 #[builder(default)]
705 #[serde(skip_serializing_if = "Option::is_none")]
706 pub score: Option<String>,
707}
708
709#[derive(Default, Serialize, Deserialize, Builder, Eq, PartialEq, Debug, Clone)]
712#[builder(setter(into))]
713pub struct ManagedEntity {
714 #[builder(default)]
716 #[serde(skip_serializing_if = "Option::is_none")]
717 pub data: Option<Value>,
718 #[builder(default)]
720 #[serde(skip_serializing_if = "Option::is_none")]
721 pub name: Option<String>,
722 #[serde(rename = "type")]
724 #[builder(default)]
725 #[serde(skip_serializing_if = "Option::is_none")]
726 pub entity_type: Option<String>,
727 #[builder(default)]
729 #[serde(skip_serializing_if = "Option::is_none")]
730 pub unique_id: Option<String>,
731 #[builder(default)]
733 #[serde(skip_serializing_if = "Option::is_none")]
734 pub version: Option<String>,
735}
736
737#[derive(Default, Serialize, Deserialize, Builder, Eq, PartialEq, Debug, Clone)]
740#[builder(setter(into))]
741pub struct MetaData {
742 pub version: String,
744 pub product: Product,
746 #[builder(default)]
749 #[serde(skip_serializing_if = "Option::is_none")]
750 pub original_time: Option<String>,
751 #[builder(default)]
753 #[serde(skip_serializing_if = "Option::is_none")]
754 pub log_provider: Option<String>,
755 #[builder(default)]
757 #[serde(skip_serializing_if = "Option::is_none")]
758 pub log_name: Option<String>,
759 #[builder(default)]
762 #[serde(skip_serializing_if = "Option::is_none")]
763 pub sequence: Option<u64>,
764 #[builder(default)]
766 #[serde(skip_serializing_if = "Option::is_none")]
767 pub extension: Option<Extension>,
768 #[builder(default)]
770 #[serde(skip_serializing_if = "Option::is_none")]
771 pub profiles: Option<Vec<String>>,
772 #[builder(default)]
774 #[serde(skip_serializing_if = "Option::is_none")]
775 pub processed_time: Option<u128>,
776 #[builder(default)]
778 #[serde(skip_serializing_if = "Option::is_none")]
779 pub modified_time: Option<i64>,
780 #[builder(default)]
782 #[serde(skip_serializing_if = "Option::is_none")]
783 pub logged_time: Option<i64>,
784 #[builder(default)]
786 #[serde(skip_serializing_if = "Option::is_none")]
787 pub log_version: Option<String>,
788 #[builder(default)]
791 #[serde(skip_serializing_if = "Option::is_none")]
792 pub labels: Option<Vec<String>>,
793 #[builder(default)]
795 #[serde(skip_serializing_if = "Option::is_none")]
796 pub uid: Option<String>,
797 #[builder(default)]
799 #[serde(skip_serializing_if = "Option::is_none")]
800 pub event_code: Option<String>,
801 #[builder(default)]
803 #[serde(skip_serializing_if = "Option::is_none")]
804 pub correlation_uid: Option<String>,
805}
806
807#[derive(Default, Serialize, Deserialize, Builder, Eq, PartialEq, Debug, Clone)]
810#[builder(setter(into))]
811pub struct Product {
812 pub vendor_name: String,
814 #[builder(default)]
816 #[serde(skip_serializing_if = "Option::is_none")]
817 pub version: Option<String>,
818 #[builder(default)]
820 #[serde(skip_serializing_if = "Option::is_none")]
821 pub uid: Option<String>,
822 #[builder(default)]
824 #[serde(skip_serializing_if = "Option::is_none")]
825 pub name: Option<String>,
826 #[builder(default)]
828 #[serde(skip_serializing_if = "Option::is_none")]
829 pub lang: Option<String>,
830 #[builder(default)]
832 #[serde(skip_serializing_if = "Option::is_none")]
833 pub url_string: Option<String>,
834 #[builder(default)]
836 #[serde(skip_serializing_if = "Option::is_none")]
837 pub path: Option<String>,
838 #[builder(default)]
840 #[serde(skip_serializing_if = "Option::is_none")]
841 pub feature: Option<Feature>,
842}
843
844#[derive(Default, Serialize, Deserialize, Builder, Eq, PartialEq, Debug, Clone)]
849#[builder(setter(into))]
850pub struct Feature {
851 #[builder(default)]
853 #[serde(skip_serializing_if = "Option::is_none")]
854 pub name: Option<String>,
855 #[builder(default)]
857 #[serde(skip_serializing_if = "Option::is_none")]
858 pub uid: Option<String>,
859 #[builder(default)]
861 #[serde(skip_serializing_if = "Option::is_none")]
862 pub version: Option<String>,
863}
864
865#[derive(Default, Serialize, Deserialize, Builder, Eq, PartialEq, Debug, Clone)]
868#[builder(setter(into))]
869pub struct Extension {
870 pub name: String,
872 pub uid: String,
874 pub version: String,
876}
877
878#[derive(Default, Debug, Clone, Builder)]
881#[builder(setter(into), default)]
882struct FilteredRequest {
883 pub principal: EntityComponent,
884 pub action: EntityComponent,
885 pub resource: EntityComponent,
886 pub context: Option<String>,
887 pub entities: Option<Entities>,
888}
889
890#[derive(Default, Debug, Clone, PartialEq, Eq)]
893pub(crate) enum EntityComponent {
894 Concrete(EntityUid),
896 Unspecified,
898 #[default]
899 None,
901}
902
903impl Display for EntityComponent {
904 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
905 match self {
906 Self::Concrete(euid) => {
907 write!(f, "{}", euid.id().escaped())
908 }
909 Self::None => {
910 write!(f, "{SECRET_STRING}")
911 }
912 Self::Unspecified => {
913 write!(f, "*")
914 }
915 }
916 }
917}
918
919impl EntityComponent {
920 pub fn get_type_name(&self) -> String {
922 match self {
923 Self::Concrete(euid) => euid.type_name().to_string(),
924 Self::None => SECRET_STRING.to_string(),
925 Self::Unspecified => "*".to_string(),
926 }
927 }
928
929 pub fn get_id(&self) -> String {
931 match self {
932 Self::Concrete(euid) => euid.to_string(),
933 Self::None => SECRET_STRING.to_string(),
934 Self::Unspecified => "*".to_string(),
935 }
936 }
937}
938
939impl From<Option<EntityUid>> for EntityComponent {
940 fn from(value: Option<EntityUid>) -> Self {
941 value.map_or_else(|| Self::Unspecified, Self::Concrete)
942 }
943}
944
945#[cfg(test)]
946mod test {
947 use std::collections::{HashMap, HashSet};
948 use std::str::FromStr;
949
950 use cedar_policy::{
951 AuthorizationError, Authorizer, Context, Decision, Entities, EntityId, EntityTypeName,
952 EntityUid, PolicyId, PolicySet, Request, Response,
953 };
954 use serde_json::{from_str, to_string, to_value, Map};
955
956 use crate::public::log::error::OcsfException;
957 use crate::public::log::error::OcsfException::{OcsfFieldsValidationError, UninitializedField};
958 use crate::public::log::schema::{
959 filter_request, ActivityId, EnrichmentArray, EnrichmentArrayBuilder, EntityComponent,
960 ManagedEntity, ManagedEntityBuilder, MetaData, MetaDataBuilder, OpenCyberSecurityFramework,
961 OpenCyberSecurityFrameworkBuilder, ProductBuilder, SeverityId, TypeUid,
962 ALLOWED_ACTIVITY_NAME_LEN, ALLOWED_ENRICHMENT_ARRAY_LEN, OCSF_SCHEMA_VERSION,
963 SECRET_STRING, VENDOR_NAME,
964 };
965 use crate::public::log::{FieldLevel, FieldSet, FieldSetBuilder};
966
967 use super::build_ocsf_severity;
968
969 fn generate_metadata() -> MetaData {
970 MetaDataBuilder::default()
971 .version("1.0.0")
972 .product(
973 ProductBuilder::default()
974 .vendor_name("cedar-local-agent")
975 .build()
976 .unwrap(),
977 )
978 .build()
979 .unwrap()
980 }
981
982 fn generate_entity(entity_type: String, name: String) -> ManagedEntity {
983 ManagedEntityBuilder::default()
984 .version("1.0.0".to_string())
985 .entity_type(entity_type)
986 .name(name)
987 .build()
988 .unwrap()
989 }
990
991 fn generate_default_ocsf_model() -> OpenCyberSecurityFramework {
992 OpenCyberSecurityFrameworkBuilder::default()
993 .type_uid(TypeUid::Read)
994 .severity_id(SeverityId::Unknown)
995 .metadata(generate_metadata())
996 .time(1_695_275_741_i64)
997 .entity(generate_entity("user".to_string(), "alice".to_string()))
998 .activity_id(ActivityId::Read)
999 .build()
1000 .unwrap()
1001 }
1002
1003 fn generate_validation_error() -> Result<OpenCyberSecurityFramework, OcsfException> {
1004 Err(OcsfFieldsValidationError(format!(
1005 "Either the Enrichments array exceeds the maximum allowed size of \
1006 {ALLOWED_ENRICHMENT_ARRAY_LEN} elements, or the Activity name exceeds the \
1007 maximum allowed length of {ALLOWED_ACTIVITY_NAME_LEN} characters... "
1008 )))
1009 }
1010
1011 fn generate_enrichment_array_vec(num_items: usize) -> Vec<EnrichmentArray> {
1012 (0..num_items).map(|_| EnrichmentArray::default()).collect()
1013 }
1014
1015 fn generate_entity_uid(entity_id: &str) -> EntityUid {
1016 EntityUid::from_type_name_and_id(
1017 EntityTypeName::from_str("CedarLocalAgent::User").unwrap(),
1018 EntityId::from_str(entity_id).unwrap(),
1019 )
1020 }
1021
1022 fn generate_mock_request(principal_name: &str) -> Request {
1023 let principal = generate_entity_uid(principal_name);
1024 let action = generate_entity_uid("read");
1025 let resource = generate_entity_uid("Box");
1026
1027 Request::new(principal, action, resource, Context::empty(), None).unwrap()
1028 }
1029
1030 fn generate_entities() -> Entities {
1031 let entities_data = r#"
1032 [
1033 {
1034 "uid": { "type": "CedarLocalAgent::User", "id": "alice" },
1035 "attrs": {},
1036 "parents": [
1037 { "type": "CedarLocalAgent::UserGroup", "id": "alice_friends" },
1038 { "type": "CedarLocalAgent::UserGroup", "id": "bob_friends" }
1039 ]
1040 },
1041 {
1042 "uid": { "type": "CedarLocalAgent::User", "id": "bob"},
1043 "attrs" : {},
1044 "parents": []
1045 }
1046 ]"#;
1047 Entities::from_json_str(entities_data, None).unwrap()
1048 }
1049
1050 fn generate_custom_count_entities(count: i32) -> Entities {
1051 let mut entities_data = r#"
1052 [
1053 {
1054 "uid": { "type": "CedarLocalAgent::User", "id": "alice" },
1055 "attrs": {},
1056 "parents": [
1057 { "type": "CedarLocalAgent::UserGroup", "id": "alice_friends" },
1058 { "type": "CedarLocalAgent::UserGroup", "id": "bob_friends" }
1059 ]
1060 },
1061 "#
1062 .to_owned();
1063 for i in 0..count {
1064 let append = r#"{
1065 "uid": { "type": "CedarLocalAgent::User", "id": "bob"#
1066 .to_owned()
1067 + i.to_string().as_str()
1068 + r#""},
1069 "attrs" : {},
1070 "parents": []
1071 },"#;
1072
1073 entities_data.push_str(&append);
1074 }
1075 entities_data.pop(); entities_data.push(']');
1077
1078 Entities::from_json_str(&entities_data, None).unwrap()
1079 }
1080
1081 #[allow(clippy::default_trait_access)]
1082 fn generate_response(num_of_error: usize, decision: Decision) -> Response {
1083 let mut policy_ids = HashSet::new();
1084 policy_ids.insert(PolicyId::from_str("policy1").unwrap());
1085 policy_ids.insert(PolicyId::from_str("policy2").unwrap());
1086
1087 let authorizer = Authorizer::new();
1088 let policy_set = PolicySet::from_str(
1089 r"permit(
1090 principal,
1091 action,
1092 resource
1093 ) when {
1094 resource.admins.contains(principal)
1095 };",
1096 )
1097 .unwrap();
1098
1099 let euid_type = EntityTypeName::from_str("Veris::User").unwrap();
1100 let euid_id = EntityId::from_str("test").unwrap();
1101 let euid = EntityUid::from_type_name_and_id(euid_type, euid_id);
1102
1103 let request =
1104 Request::new(euid.clone(), euid.clone(), euid, Context::empty(), None).unwrap();
1105
1106 let auth_res = authorizer.is_authorized(&request, &policy_set, &Entities::empty());
1107 let auth_err = auth_res.diagnostics().errors().next().unwrap();
1108
1109 let errors: Vec<AuthorizationError> = (0..num_of_error).map(|_| auth_err.clone()).collect();
1110
1111 Response::new(decision, policy_ids, errors)
1112 }
1113
1114 #[test]
1115 fn ocsf_field_mapping_allow_case() {
1116 let request = generate_mock_request("alice");
1117 let entities = generate_entities();
1118 let response = generate_response(0, Decision::Allow);
1119 let ocsf = OpenCyberSecurityFramework::create(
1120 &request,
1121 &response,
1122 &entities,
1123 &FieldSet::default(),
1124 "cedar::local::agent::library",
1125 );
1126 assert!(ocsf.is_ok());
1127 let ocsf_log = ocsf.unwrap();
1128 assert_eq!(ocsf_log.severity_id, SeverityId::Informational);
1129 assert_eq!(ocsf_log.status.unwrap(), "Success".to_string());
1130 assert_eq!(ocsf_log.status_code.unwrap(), "Allow".to_string());
1131 }
1132
1133 #[test]
1134 fn ocsf_field_mapping_deny_case() {
1135 let request = generate_mock_request("alice");
1136 let entities = generate_entities();
1137 let response = generate_response(1, Decision::Deny);
1138 let ocsf = OpenCyberSecurityFramework::create(
1139 &request,
1140 &response,
1141 &entities,
1142 &FieldSet::default(),
1143 "cedar::local::agent::library",
1144 );
1145 assert!(ocsf.is_ok());
1146 let ocsf_log = ocsf.unwrap();
1147 assert_eq!(ocsf_log.severity_id, SeverityId::Low);
1148 assert_eq!(ocsf_log.status.unwrap(), "Failure".to_string());
1149
1150 let response = generate_response(2, Decision::Deny);
1151 let ocsf = OpenCyberSecurityFramework::create(
1152 &request,
1153 &response,
1154 &entities,
1155 &FieldSet::default(),
1156 "cedar::local::agent::library",
1157 );
1158
1159 assert!(ocsf.is_ok());
1160 let ocsf_log = ocsf.unwrap();
1161 assert_eq!(ocsf_log.severity_id, SeverityId::Medium);
1162 assert_eq!(ocsf_log.status.unwrap(), "Failure".to_string());
1163 assert_eq!(ocsf_log.status_code.unwrap(), "Deny".to_string());
1164 }
1165
1166 #[test]
1167 fn build_ocsf_severity_multiple_errors() {
1168 assert_eq!(build_ocsf_severity(1), (SeverityId::Low, "Low".to_string()));
1169 assert_eq!(
1170 build_ocsf_severity(4),
1171 (SeverityId::Medium, "Medium".to_string())
1172 );
1173 }
1174
1175 #[test]
1176 fn activity_id_conversion() {
1177 assert_eq!(ActivityId::from("update".to_string()), ActivityId::Update);
1178 assert_eq!(ActivityId::from("delete".to_string()), ActivityId::Delete);
1179 assert_eq!(ActivityId::from("unknown".to_string()), ActivityId::Unknown);
1180 assert_eq!(
1181 ActivityId::from("any_other_activity".to_string()),
1182 ActivityId::Other
1183 );
1184 }
1185
1186 #[test]
1187 fn type_uid_conversion() {
1188 assert_eq!(TypeUid::from(ActivityId::Update), TypeUid::Update);
1189 assert_eq!(TypeUid::from(ActivityId::Delete), TypeUid::Delete);
1190 assert_eq!(TypeUid::from(ActivityId::Unknown), TypeUid::Unknown);
1191 assert_eq!(TypeUid::from(ActivityId::Create), TypeUid::Create);
1192 assert_eq!(TypeUid::from(ActivityId::Other), TypeUid::Other);
1193 }
1194
1195 #[test]
1196 fn ocsf_model_with_property_access_test() {
1197 let ocsf_model = generate_default_ocsf_model();
1198 assert_eq!(ocsf_model.severity_id, SeverityId::Unknown);
1199 assert_eq!(ocsf_model.activity_id, ActivityId::Read);
1200 assert_eq!(
1201 ocsf_model.metadata.product.vendor_name,
1202 "cedar-local-agent".to_string()
1203 );
1204 assert_eq!(
1205 ocsf_model.entity,
1206 generate_entity("user".to_string(), "alice".to_string())
1207 );
1208 assert!(ocsf_model.duration.is_none());
1209 }
1210
1211 #[test]
1212 fn ocsf_test_default() {
1213 let ocsf_model = generate_default_ocsf_model();
1214 println!("{:?}", serde_json::to_string(&ocsf_model).unwrap());
1215 assert_eq!(ocsf_model.class_uid, 3004u64);
1216 assert_eq!(ocsf_model.category_uid, 3u8);
1217 }
1218
1219 #[test]
1220 fn ocsf_test_serialization_and_rename() {
1221 let mut ocsf_model = generate_default_ocsf_model();
1222 ocsf_model.entity.entity_type = Some("Principal".to_string());
1223 let serialized = to_string(&ocsf_model).unwrap();
1224 let deserialized = from_str(&serialized).unwrap();
1225 assert_eq!(ocsf_model, deserialized);
1226 assert!(serialized.contains("\"type\":\"Principal\""));
1227 assert!(serialized.contains("\"activity_id\":2"));
1228 }
1229
1230 #[test]
1231 fn ocsf_test_equality() {
1232 let ocsf_model = generate_default_ocsf_model();
1233 let ocsf_model_2 = generate_default_ocsf_model();
1234 assert_eq!(ocsf_model, ocsf_model_2);
1235 }
1236
1237 #[test]
1238 fn ocsf_test_complex_type() {
1239 let mut ocsf_model = generate_default_ocsf_model();
1240 let mut enrichment_array: HashMap<String, Vec<String>> = HashMap::new();
1241 enrichment_array.insert(
1242 "key1".to_string(),
1243 vec!["value1.1".to_string(), "value1.2".to_string()],
1244 );
1245
1246 ocsf_model.enrichments = Some(Vec::from([EnrichmentArrayBuilder::default()
1247 .name("data1")
1248 .value("value2")
1249 .data(enrichment_array)
1250 .build()
1251 .unwrap()]));
1252 ocsf_model.enrichments.as_ref().map_or_else(
1253 || {
1254 panic!("Enrichment Array is None");
1255 },
1256 |enrichments| {
1257 assert!(!enrichments[0].data.is_empty());
1258 assert_eq!(
1259 enrichments[0].data["key1"],
1260 vec!["value1.1".to_string(), "value1.2".to_string()]
1261 );
1262 },
1263 );
1264
1265 let mut unmapped = Map::new();
1266 unmapped.insert("k1".to_string(), to_value("v1").unwrap());
1267 unmapped.insert("k2".to_string(), to_value("v2").unwrap());
1268 let unmapped_obj = to_value(unmapped).unwrap();
1269 ocsf_model.unmapped = Some(unmapped_obj.clone());
1270 assert!(ocsf_model.unmapped.is_some());
1271 assert_eq!(
1272 ocsf_model.unmapped.unwrap().to_string(),
1273 unmapped_obj.to_string()
1274 );
1275 }
1276
1277 #[test]
1278 fn ocsf_validate_required_fields() {
1279 let model_with_no_activity_id = OpenCyberSecurityFrameworkBuilder::default()
1280 .type_uid(TypeUid::Read)
1281 .severity_id(SeverityId::Informational)
1282 .metadata(generate_metadata())
1283 .time(1_695_275_741_i64)
1284 .entity(generate_entity("user".to_string(), "alice".to_string()))
1285 .build();
1286 assert!(model_with_no_activity_id.is_err());
1287 assert!(matches!(
1288 model_with_no_activity_id,
1289 Err(UninitializedField(_))
1290 ));
1291 }
1292
1293 #[test]
1294 fn ocsf_validate_activity_name() {
1295 let log_result = OpenCyberSecurityFrameworkBuilder::default()
1296 .type_uid(TypeUid::Read)
1297 .severity_id(SeverityId::Unknown)
1298 .metadata(generate_metadata())
1299 .time(1_695_275_741_i64)
1300 .entity(generate_entity("user".to_string(), "alice".to_string()))
1301 .activity_id(ActivityId::Read)
1302 .enrichments(generate_enrichment_array_vec(1))
1303 .activity_name(
1304 "this is an invalid activity name with \
1305 len larger than 35"
1306 .to_string(),
1307 )
1308 .build();
1309 assert!(log_result.is_err());
1310 let _expected = generate_validation_error();
1311 assert!(matches!(log_result, _expected));
1312 }
1313
1314 #[test]
1315 fn ocsf_validate_activity_name_enrichment_none() {
1316 let log_result = OpenCyberSecurityFrameworkBuilder::default()
1317 .type_uid(TypeUid::Read)
1318 .severity_id(SeverityId::Unknown)
1319 .metadata(generate_metadata())
1320 .time(1_695_275_741_i64)
1321 .entity(generate_entity("user".to_string(), "alice".to_string()))
1322 .activity_id(ActivityId::Read)
1323 .activity_name(
1324 "this is an invalid activity name with \
1325 len larger than 35"
1326 .to_string(),
1327 )
1328 .build();
1329 assert!(log_result.is_err());
1330 let _expected = generate_validation_error();
1331 assert!(matches!(log_result, _expected));
1332 }
1333
1334 #[test]
1335 fn ocsf_validate_activity_name_none() {
1336 let log_result = OpenCyberSecurityFrameworkBuilder::default()
1337 .type_uid(TypeUid::Read)
1338 .severity_id(SeverityId::Unknown)
1339 .metadata(generate_metadata())
1340 .time(1_695_275_741_i64)
1341 .entity(generate_entity("user".to_string(), "alice".to_string()))
1342 .activity_id(ActivityId::Read)
1343 .enrichments(generate_enrichment_array_vec(1))
1344 .activity_name(None)
1345 .build();
1346 assert!(log_result.is_ok());
1347 }
1348
1349 #[test]
1350 fn ocsf_validate_activity_enrichments() {
1351 let log_result = OpenCyberSecurityFrameworkBuilder::default()
1352 .type_uid(TypeUid::Read)
1353 .severity_id(SeverityId::Unknown)
1354 .metadata(generate_metadata())
1355 .time(1_695_275_741_i64)
1356 .entity(generate_entity("user".to_string(), "alice".to_string()))
1357 .activity_id(ActivityId::Read)
1358 .enrichments(generate_enrichment_array_vec(10))
1359 .activity_name("safe-string".to_string())
1360 .build();
1361 assert!(log_result.is_err());
1362 let _expected = generate_validation_error();
1363 assert!(matches!(log_result, _expected));
1364 }
1365
1366 #[test]
1368 fn validate_user_input_no_effect_on_log_size() {
1369 let response = generate_response(0, Decision::Allow);
1370 let fields = FieldSet::default();
1371 let authorizer_name = "cedar::local::agent::library";
1372
1373 let request_json_1 = {
1374 let request = generate_mock_request("alice111");
1375 let entities = generate_custom_count_entities(100);
1376
1377 let ocsf = OpenCyberSecurityFramework::create(
1378 &request,
1379 &response,
1380 &entities,
1381 &fields,
1382 authorizer_name,
1383 );
1384
1385 serde_json::to_string(&ocsf.unwrap()).unwrap()
1386 };
1387
1388 assert!(!request_json_1.contains("alice111"));
1389 assert!(!request_json_1.contains("bob"));
1390
1391 let request_json_2 = {
1392 let request = generate_mock_request("alice");
1393 let entities = generate_custom_count_entities(50);
1394 let ocsf = OpenCyberSecurityFramework::create(
1395 &request,
1396 &response,
1397 &entities,
1398 &fields,
1399 authorizer_name,
1400 );
1401
1402 serde_json::to_string(&ocsf.unwrap()).unwrap()
1403 };
1404
1405 assert!(!request_json_2.contains("alice"));
1406 assert!(!request_json_2.contains("bob"));
1407
1408 assert_eq!(request_json_1.len(), request_json_2.len());
1409 }
1410
1411 #[test]
1412 fn ocsf_validate_activity_enrichments_activity_name_none() {
1413 let log_result = OpenCyberSecurityFrameworkBuilder::default()
1414 .type_uid(TypeUid::Read)
1415 .severity_id(SeverityId::Unknown)
1416 .metadata(generate_metadata())
1417 .time(1_695_275_741_i64)
1418 .entity(generate_entity("user".to_string(), "alice".to_string()))
1419 .activity_id(ActivityId::Read)
1420 .enrichments(generate_enrichment_array_vec(10))
1421 .build();
1422 let _expected = generate_validation_error();
1423 assert!(matches!(log_result, _expected));
1424 }
1425
1426 #[test]
1427 fn ocsf_validate_activity_enrichments_none() {
1428 let log_result = OpenCyberSecurityFrameworkBuilder::default()
1429 .type_uid(TypeUid::Read)
1430 .severity_id(SeverityId::Unknown)
1431 .metadata(generate_metadata())
1432 .time(1_695_275_741_i64)
1433 .entity(generate_entity("user".to_string(), "alice".to_string()))
1434 .activity_id(ActivityId::Read)
1435 .enrichments(None)
1436 .activity_name("safe-string".to_string())
1437 .build();
1438 assert!(log_result.is_ok());
1439 }
1440
1441 fn create_mock_request() -> Request {
1442 let principal = EntityUid::from_type_name_and_id(
1443 EntityTypeName::from_str("User").unwrap(),
1444 EntityId::from_str("Alice").unwrap(),
1445 );
1446 let action = EntityUid::from_type_name_and_id(
1447 EntityTypeName::from_str("Action").unwrap(),
1448 EntityId::from_str("Read").unwrap(),
1449 );
1450 let resource = EntityUid::from_type_name_and_id(
1451 EntityTypeName::from_str("Photo").unwrap(),
1452 EntityId::from_str("vacation.jpg").unwrap(),
1453 );
1454 Request::new(principal, action, resource, Context::empty(), None).unwrap()
1455 }
1456
1457 fn create_mock_entities() -> Entities {
1458 let entities_data = r#"
1459 [
1460 {
1461 "uid": { "type": "User", "id": "Alice" },
1462 "attrs": {},
1463 "parents": []
1464 },
1465 {
1466 "uid": { "type": "User", "id": "Bob"},
1467 "attrs" : {},
1468 "parents": []
1469 }
1470 ]"#;
1471 Entities::from_json_str(entities_data, None).unwrap()
1472 }
1473
1474 #[test]
1475 fn filter_request_default_field_set() {
1476 let request = create_mock_request();
1477 let entities = create_mock_entities();
1478 let field_set = FieldSetBuilder::default().build().unwrap();
1479 let filtered_request = filter_request(&request, &entities, &field_set);
1480
1481 assert_eq!(filtered_request.principal, EntityComponent::None);
1482 assert_eq!(filtered_request.action, EntityComponent::None);
1483 assert_eq!(filtered_request.resource, EntityComponent::None);
1484 assert!(filtered_request.context.is_none());
1485 assert!(filtered_request.entities.is_none());
1486 }
1487
1488 #[test]
1489 fn filter_request_all_fields_set() {
1490 let request = create_mock_request();
1491 let entities = create_mock_entities();
1492 let field_set = FieldSetBuilder::default()
1493 .principal(true)
1494 .action(true)
1495 .resource(true)
1496 .context(true)
1497 .entities(FieldLevel::All)
1498 .build()
1499 .unwrap();
1500 let filtered_request = filter_request(&request, &entities, &field_set);
1501
1502 assert!(matches!(
1503 filtered_request.principal,
1504 EntityComponent::Concrete(_)
1505 ));
1506 assert!(matches!(
1507 filtered_request.action,
1508 EntityComponent::Concrete(_)
1509 ));
1510 assert!(matches!(
1511 filtered_request.resource,
1512 EntityComponent::Concrete(_)
1513 ));
1514 assert!(filtered_request.context.is_some());
1515 assert!(filtered_request.entities.is_some());
1516 }
1517
1518 #[test]
1519 fn filter_request_custom_field_set() {
1520 let request = create_mock_request();
1521 let entities = create_mock_entities();
1522 let filter_fn = |_entities: &Entities| -> Entities { Entities::empty() };
1523 let field_set = FieldSetBuilder::default()
1524 .principal(true)
1525 .context(true)
1526 .entities(FieldLevel::Custom(filter_fn))
1527 .build()
1528 .unwrap();
1529
1530 let filtered_request = filter_request(&request, &entities, &field_set);
1531
1532 assert!(matches!(
1533 filtered_request.principal,
1534 EntityComponent::Concrete(_)
1535 ));
1536 assert!(matches!(filtered_request.action, EntityComponent::None));
1537 assert!(matches!(filtered_request.resource, EntityComponent::None));
1538
1539 assert_eq!(filtered_request.context, Some(request.to_string()));
1540 assert_eq!(filtered_request.entities, Some(Entities::empty()));
1541 }
1542
1543 #[test]
1544 fn ocsf_error_log() {
1545 let ocsf = OpenCyberSecurityFramework::error(
1546 "Failed to create error".to_string(),
1547 "some_authorizer".to_string(),
1548 );
1549
1550 assert_eq!(ocsf.type_uid, TypeUid::Other);
1551 assert_eq!(ocsf.severity_id, SeverityId::Other);
1552 assert_eq!(ocsf.activity_id, ActivityId::Other);
1553 assert_eq!(
1554 ocsf.entity,
1555 ManagedEntityBuilder::default()
1556 .name("N/A".to_string())
1557 .build()
1558 .unwrap()
1559 );
1560 assert_eq!(ocsf.message.unwrap(), "Failed to create error".to_string());
1561 assert_eq!(
1562 ocsf.metadata,
1563 MetaDataBuilder::default()
1564 .version(OCSF_SCHEMA_VERSION)
1565 .product(
1566 ProductBuilder::default()
1567 .vendor_name(VENDOR_NAME)
1568 .name("some_authorizer".to_string())
1569 .build()
1570 .unwrap()
1571 )
1572 .build()
1573 .unwrap()
1574 );
1575 }
1576
1577 #[test]
1578 fn display_entity_component_concrete() {
1579 let component = EntityComponent::Concrete(EntityUid::from_str("Action::\"test\"").unwrap());
1580 assert_eq!("test", component.to_string());
1581 }
1582
1583 #[test]
1584 fn display_entity_component_unspecified() {
1585 let component = EntityComponent::Unspecified;
1586 assert_eq!("*", component.to_string());
1587 assert_eq!("*", component.get_id());
1588 assert_eq!("*", component.get_type_name());
1589 }
1590
1591 #[test]
1592 fn display_entity_component_filtered_out() {
1593 let component = EntityComponent::None;
1594 assert_eq!(SECRET_STRING.to_string(), component.to_string());
1595 assert_eq!(SECRET_STRING.to_string(), component.get_id());
1596 assert_eq!(SECRET_STRING.to_string(), component.get_type_name());
1597 }
1598}