1#![allow(clippy::needless_return)]
21
22mod err;
23
24use clap::{ArgAction, Args, Parser, Subcommand, ValueEnum};
25use miette::{miette, IntoDiagnostic, NamedSource, Report, Result, WrapErr};
26use serde::{Deserialize, Serialize};
27use std::{
28 collections::HashMap,
29 fmt::{self, Display},
30 fs::OpenOptions,
31 path::Path,
32 process::{ExitCode, Termination},
33 str::FromStr,
34 time::Instant,
35};
36
37use cedar_policy::*;
38use cedar_policy_formatter::{policies_str_to_pretty, Config};
39
40#[derive(Parser)]
42#[command(author, version, about, long_about = None)] pub struct Cli {
44 #[command(subcommand)]
45 pub command: Commands,
46 #[arg(
48 global = true,
49 short = 'f',
50 long = "error-format",
51 env = "CEDAR_ERROR_FORMAT",
52 default_value_t,
53 value_enum
54 )]
55 pub err_fmt: ErrorFormat,
56}
57
58#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, ValueEnum)]
59pub enum ErrorFormat {
60 #[default]
63 Human,
64 Plain,
67 Json,
69}
70
71impl Display for ErrorFormat {
72 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73 write!(
74 f,
75 "{}",
76 match self {
77 ErrorFormat::Human => "human",
78 ErrorFormat::Plain => "plain",
79 ErrorFormat::Json => "json",
80 }
81 )
82 }
83}
84
85#[derive(Subcommand, Debug)]
86pub enum Commands {
87 Authorize(AuthorizeArgs),
89 Evaluate(EvaluateArgs),
91 Validate(ValidateArgs),
93 CheckParse(CheckParseArgs),
95 Link(LinkArgs),
97 Format(FormatArgs),
99 New(NewArgs),
101}
102
103#[derive(Args, Debug)]
104pub struct ValidateArgs {
105 #[arg(short, long = "schema", value_name = "FILE")]
107 pub schema_file: String,
108 #[arg(short, long = "policies", value_name = "FILE")]
110 pub policies_file: String,
111 #[arg(long)]
113 pub deny_warnings: bool,
114 #[arg(long = "partial-validate")]
118 pub partial_validate: bool,
119}
120
121#[derive(Args, Debug)]
122pub struct CheckParseArgs {
123 #[arg(short, long = "policies", value_name = "FILE")]
125 pub policies_file: Option<String>,
126}
127
128#[derive(Args, Debug)]
130pub struct RequestArgs {
131 #[arg(short = 'l', long)]
133 pub principal: Option<String>,
134 #[arg(short, long)]
136 pub action: Option<String>,
137 #[arg(short, long)]
139 pub resource: Option<String>,
140 #[arg(short, long = "context", value_name = "FILE")]
143 pub context_json_file: Option<String>,
144 #[arg(long = "request-json", value_name = "FILE", conflicts_with_all = &["principal", "action", "resource", "context_json_file"])]
149 pub request_json_file: Option<String>,
150 #[arg(long = "request-validation", action = ArgAction::Set, default_value_t = true)]
153 pub request_validation: bool,
154}
155
156impl RequestArgs {
157 fn get_request(&self, schema: Option<&Schema>) -> Result<Request> {
164 match &self.request_json_file {
165 Some(jsonfile) => {
166 let jsonstring = std::fs::read_to_string(jsonfile)
167 .into_diagnostic()
168 .wrap_err_with(|| format!("failed to open request-json file {jsonfile}"))?;
169 let qjson: RequestJSON = serde_json::from_str(&jsonstring)
170 .into_diagnostic()
171 .wrap_err_with(|| format!("failed to parse request-json file {jsonfile}"))?;
172 let principal = qjson
173 .principal
174 .map(|s| {
175 s.parse().wrap_err_with(|| {
176 format!("failed to parse principal in {jsonfile} as entity Uid")
177 })
178 })
179 .transpose()?;
180 let action = qjson
181 .action
182 .map(|s| {
183 s.parse().wrap_err_with(|| {
184 format!("failed to parse action in {jsonfile} as entity Uid")
185 })
186 })
187 .transpose()?;
188 let resource = qjson
189 .resource
190 .map(|s| {
191 s.parse().wrap_err_with(|| {
192 format!("failed to parse resource in {jsonfile} as entity Uid")
193 })
194 })
195 .transpose()?;
196 let context = Context::from_json_value(
197 qjson.context,
198 schema.and_then(|s| Some((s, action.as_ref()?))),
199 )
200 .into_diagnostic()
201 .wrap_err_with(|| format!("failed to create a context from {jsonfile}"))?;
202 Request::new(
203 principal,
204 action,
205 resource,
206 context,
207 if self.request_validation {
208 schema
209 } else {
210 None
211 },
212 )
213 .map_err(|e| miette!("{e}"))
214 }
215 None => {
216 let principal = self
217 .principal
218 .as_ref()
219 .map(|s| {
220 s.parse().wrap_err_with(|| {
221 format!("failed to parse principal {s} as entity Uid")
222 })
223 })
224 .transpose()?;
225 let action = self
226 .action
227 .as_ref()
228 .map(|s| {
229 s.parse()
230 .wrap_err_with(|| format!("failed to parse action {s} as entity Uid"))
231 })
232 .transpose()?;
233 let resource = self
234 .resource
235 .as_ref()
236 .map(|s| {
237 s.parse()
238 .wrap_err_with(|| format!("failed to parse resource {s} as entity Uid"))
239 })
240 .transpose()?;
241 let context: Context = match &self.context_json_file {
242 None => Context::empty(),
243 Some(jsonfile) => match std::fs::OpenOptions::new().read(true).open(jsonfile) {
244 Ok(f) => Context::from_json_file(
245 f,
246 schema.and_then(|s| Some((s, action.as_ref()?))),
247 )
248 .into_diagnostic()
249 .wrap_err_with(|| format!("failed to create a context from {jsonfile}"))?,
250 Err(e) => Err(e).into_diagnostic().wrap_err_with(|| {
251 format!("error while loading context from {jsonfile}")
252 })?,
253 },
254 };
255 Request::new(
256 principal,
257 action,
258 resource,
259 context,
260 if self.request_validation {
261 schema
262 } else {
263 None
264 },
265 )
266 .map_err(|e| miette!("{e}"))
267 }
268 }
269 }
270}
271
272#[derive(Args, Debug)]
273pub struct AuthorizeArgs {
274 #[command(flatten)]
276 pub request: RequestArgs,
277 #[arg(short, long = "policies", value_name = "FILE")]
279 pub policies_file: String,
280 #[arg(short = 'k', long = "template-linked", value_name = "FILE")]
282 pub template_linked_file: Option<String>,
283 #[arg(short, long = "schema", value_name = "FILE")]
287 pub schema_file: Option<String>,
288 #[arg(long = "entities", value_name = "FILE")]
290 pub entities_file: String,
291 #[arg(short, long)]
293 pub verbose: bool,
294 #[arg(short, long)]
296 pub timing: bool,
297}
298
299#[derive(Args, Debug)]
300pub struct LinkArgs {
301 #[arg(short, long = "policies", value_name = "FILE")]
303 pub policies_file: String,
304 #[arg(short = 'k', long = "template-linked", value_name = "FILE")]
306 pub template_linked_file: String,
307 #[arg(long)]
309 pub template_id: String,
310 #[arg(short, long)]
312 pub new_id: String,
313 #[arg(short, long)]
315 pub arguments: Arguments,
316}
317
318#[derive(Args, Debug)]
319pub struct FormatArgs {
320 #[arg(short, long = "policies", value_name = "FILE")]
322 pub policy_file: Option<String>,
323
324 #[arg(short, long, value_name = "UINT", default_value_t = 80)]
326 pub line_width: usize,
327
328 #[arg(short, long, value_name = "INT", default_value_t = 2)]
330 pub indent_width: isize,
331}
332
333#[derive(Args, Debug)]
334pub struct NewArgs {
335 #[arg(short, long, value_name = "DIR")]
337 pub name: String,
338}
339
340#[derive(Clone, Debug, Deserialize)]
342#[serde(try_from = "HashMap<String,String>")]
343pub struct Arguments {
344 pub data: HashMap<SlotId, String>,
345}
346
347impl TryFrom<HashMap<String, String>> for Arguments {
348 type Error = String;
349
350 fn try_from(value: HashMap<String, String>) -> Result<Self, Self::Error> {
351 Ok(Self {
352 data: value
353 .into_iter()
354 .map(|(k, v)| parse_slot_id(k).map(|slot_id| (slot_id, v)))
355 .collect::<Result<HashMap<SlotId, String>, String>>()?,
356 })
357 }
358}
359
360impl FromStr for Arguments {
361 type Err = serde_json::Error;
362
363 fn from_str(s: &str) -> Result<Self, Self::Err> {
364 serde_json::from_str(s)
365 }
366}
367
368#[derive(Deserialize)]
370struct RequestJSON {
371 #[serde(default)]
373 principal: Option<String>,
374 #[serde(default)]
376 action: Option<String>,
377 #[serde(default)]
379 resource: Option<String>,
380 context: serde_json::Value,
382}
383
384#[derive(Args, Debug)]
385pub struct EvaluateArgs {
386 #[command(flatten)]
388 pub request: RequestArgs,
389 #[arg(short, long = "schema", value_name = "FILE")]
393 pub schema_file: Option<String>,
394 #[arg(long = "entities", value_name = "FILE")]
397 pub entities_file: Option<String>,
398 #[arg(value_name = "EXPRESSION")]
400 pub expression: String,
401}
402
403#[derive(Eq, PartialEq, Debug)]
404pub enum CedarExitCode {
405 Success,
408 Failure,
410 AuthorizeDeny,
413 ValidationFailure,
416}
417
418impl Termination for CedarExitCode {
419 fn report(self) -> ExitCode {
420 match self {
421 CedarExitCode::Success => ExitCode::SUCCESS,
422 CedarExitCode::Failure => ExitCode::FAILURE,
423 CedarExitCode::AuthorizeDeny => ExitCode::from(2),
424 CedarExitCode::ValidationFailure => ExitCode::from(3),
425 }
426 }
427}
428
429pub fn check_parse(args: &CheckParseArgs) -> CedarExitCode {
430 match read_policy_set(args.policies_file.as_ref()) {
431 Ok(_) => CedarExitCode::Success,
432 Err(e) => {
433 println!("Error: {e:?}");
434 CedarExitCode::Failure
435 }
436 }
437}
438
439pub fn validate(args: &ValidateArgs) -> CedarExitCode {
440 let mode = if args.partial_validate {
441 #[cfg(not(feature = "partial-validate"))]
442 {
443 println!("Error: arguments include the experimental option `--partial-validate`, but this executable was not built with `partial-validate` experimental feature enabled");
444 return CedarExitCode::Failure;
445 }
446 #[cfg(feature = "partial-validate")]
447 ValidationMode::Partial
448 } else {
449 ValidationMode::default()
450 };
451
452 let pset = match read_policy_set(Some(&args.policies_file)) {
453 Ok(pset) => pset,
454 Err(e) => {
455 println!("Error: {e:?}");
456 return CedarExitCode::Failure;
457 }
458 };
459
460 let schema = match read_schema_file(&args.schema_file) {
461 Ok(schema) => schema,
462 Err(e) => {
463 println!("Error: {e:?}");
464 return CedarExitCode::Failure;
465 }
466 };
467
468 let validator = Validator::new(schema);
469 let result = validator.validate(&pset, mode);
470
471 let exit_code = if !result.validation_passed()
472 || (args.deny_warnings && !result.validation_passed_without_warnings())
473 {
474 println!("Validation Failed");
475 CedarExitCode::ValidationFailure
476 } else {
477 println!("Validation Passed");
478 CedarExitCode::Success
479 };
480
481 let mut errors = result.validation_errors().peekable();
482 if errors.peek().is_some() {
483 println!("Validation Errors:");
484 for note in errors {
485 println!("{}", note);
486 }
487 }
488
489 let mut warnings = result.validation_warnings().peekable();
490 if warnings.peek().is_some() {
491 println!("Validation Warnings:");
492 for note in warnings {
493 println!("{}", note);
494 }
495 }
496
497 exit_code
498}
499
500pub fn evaluate(args: &EvaluateArgs) -> (CedarExitCode, EvalResult) {
501 println!();
502 let schema = match args.schema_file.as_ref().map(read_schema_file) {
503 None => None,
504 Some(Ok(schema)) => Some(schema),
505 Some(Err(e)) => {
506 println!("Error: {e:?}");
507 return (CedarExitCode::Failure, EvalResult::Bool(false));
508 }
509 };
510 let request = match args.request.get_request(schema.as_ref()) {
511 Ok(q) => q,
512 Err(e) => {
513 println!("Error: {e:?}");
514 return (CedarExitCode::Failure, EvalResult::Bool(false));
515 }
516 };
517 let expr =
518 match Expression::from_str(&args.expression).wrap_err("failed to parse the expression") {
519 Ok(expr) => expr,
520 Err(e) => {
521 println!("Error: {e:?}");
522 return (CedarExitCode::Failure, EvalResult::Bool(false));
523 }
524 };
525 let entities = match &args.entities_file {
526 None => Entities::empty(),
527 Some(file) => match load_entities(file, schema.as_ref()) {
528 Ok(entities) => entities,
529 Err(e) => {
530 println!("Error: {e:?}");
531 return (CedarExitCode::Failure, EvalResult::Bool(false));
532 }
533 },
534 };
535 match eval_expression(&request, &entities, &expr)
536 .into_diagnostic()
537 .wrap_err("failed to evaluate the expression")
538 {
539 Err(e) => {
540 println!("Error: {e:?}");
541 return (CedarExitCode::Failure, EvalResult::Bool(false));
542 }
543 Ok(result) => {
544 println!("{result}");
545 return (CedarExitCode::Success, result);
546 }
547 }
548}
549
550pub fn link(args: &LinkArgs) -> CedarExitCode {
551 if let Err(err) = link_inner(args) {
552 println!("Error: {err:?}");
553 CedarExitCode::Failure
554 } else {
555 CedarExitCode::Success
556 }
557}
558
559fn format_policies_inner(args: &FormatArgs) -> Result<()> {
560 let policies_str = read_from_file_or_stdin(args.policy_file.as_ref(), "policy set")?;
561 let config = Config {
562 line_width: args.line_width,
563 indent_width: args.indent_width,
564 };
565 println!("{}", policies_str_to_pretty(&policies_str, &config)?);
566 Ok(())
567}
568
569pub fn format_policies(args: &FormatArgs) -> CedarExitCode {
570 if let Err(err) = format_policies_inner(args) {
571 println!("Error: {err:?}");
572 CedarExitCode::Failure
573 } else {
574 CedarExitCode::Success
575 }
576}
577
578fn generate_schema(path: &Path) -> Result<()> {
579 std::fs::write(
580 path,
581 serde_json::to_string_pretty(&serde_json::json!(
582 {
583 "": {
584 "entityTypes": {
585 "A": {
586 "memberOfTypes": [
587 "B"
588 ]
589 },
590 "B": {
591 "memberOfTypes": []
592 },
593 "C": {
594 "memberOfTypes": []
595 }
596 },
597 "actions": {
598 "action": {
599 "appliesTo": {
600 "resourceTypes": [
601 "C"
602 ],
603 "principalTypes": [
604 "A",
605 "B"
606 ]
607 }
608 }
609 }
610 }
611 }))
612 .into_diagnostic()?,
613 )
614 .into_diagnostic()
615}
616
617fn generate_policy(path: &Path) -> Result<()> {
618 std::fs::write(
619 path,
620 r#"permit (
621 principal in A::"a",
622 action == Action::"action",
623 resource == C::"c"
624) when { true };
625"#,
626 )
627 .into_diagnostic()
628}
629
630fn generate_entities(path: &Path) -> Result<()> {
631 std::fs::write(
632 path,
633 serde_json::to_string_pretty(&serde_json::json!(
634 [
635 {
636 "uid": { "type": "A", "id": "a"} ,
637 "attrs": {},
638 "parents": [{"type": "B", "id": "b"}]
639 },
640 {
641 "uid": { "type": "B", "id": "b"} ,
642 "attrs": {},
643 "parents": []
644 },
645 {
646 "uid": { "type": "C", "id": "c"} ,
647 "attrs": {},
648 "parents": []
649 }
650 ]))
651 .into_diagnostic()?,
652 )
653 .into_diagnostic()
654}
655
656fn new_inner(args: &NewArgs) -> Result<()> {
657 let dir = &std::env::current_dir().into_diagnostic()?.join(&args.name);
658 std::fs::create_dir(dir).into_diagnostic()?;
659 let schema_path = dir.join("schema.cedarschema.json");
660 let policy_path = dir.join("policy.cedar");
661 let entities_path = dir.join("entities.jon");
662 generate_schema(&schema_path)?;
663 generate_policy(&policy_path)?;
664 generate_entities(&entities_path)
665}
666
667pub fn new(args: &NewArgs) -> CedarExitCode {
668 if let Err(err) = new_inner(args) {
669 println!("Error: {err:?}");
670 CedarExitCode::Failure
671 } else {
672 CedarExitCode::Success
673 }
674}
675
676fn create_slot_env(data: &HashMap<SlotId, String>) -> Result<HashMap<SlotId, EntityUid>> {
677 data.iter()
678 .map(|(key, value)| Ok(EntityUid::from_str(value).map(|euid| (key.clone(), euid))?))
679 .collect::<Result<HashMap<SlotId, EntityUid>>>()
680}
681
682fn link_inner(args: &LinkArgs) -> Result<()> {
683 let mut policies = read_policy_set(Some(&args.policies_file))?;
684 let slotenv = create_slot_env(&args.arguments.data)?;
685 policies
686 .link(
687 PolicyId::from_str(&args.template_id)?,
688 PolicyId::from_str(&args.new_id)?,
689 slotenv,
690 )
691 .into_diagnostic()?;
692 let linked = policies
693 .policy(&PolicyId::from_str(&args.new_id)?)
694 .ok_or_else(|| miette!("Failed to add template-linked policy"))?;
695 println!("Template Linked Policy Added: {linked}");
696 let linked = TemplateLinked {
697 template_id: args.template_id.clone(),
698 link_id: args.new_id.clone(),
699 args: args.arguments.data.clone(),
700 };
701
702 update_template_linked_file(&args.template_linked_file, linked)
703}
704
705#[derive(Clone, Serialize, Deserialize, Debug)]
706#[serde(try_from = "LiteralTemplateLinked")]
707#[serde(into = "LiteralTemplateLinked")]
708struct TemplateLinked {
709 template_id: String,
710 link_id: String,
711 args: HashMap<SlotId, String>,
712}
713
714impl TryFrom<LiteralTemplateLinked> for TemplateLinked {
715 type Error = String;
716
717 fn try_from(value: LiteralTemplateLinked) -> Result<Self, Self::Error> {
718 Ok(Self {
719 template_id: value.template_id,
720 link_id: value.link_id,
721 args: value
722 .args
723 .into_iter()
724 .map(|(k, v)| parse_slot_id(k).map(|slot_id| (slot_id, v)))
725 .collect::<Result<HashMap<SlotId, String>, Self::Error>>()?,
726 })
727 }
728}
729
730fn parse_slot_id<S: AsRef<str>>(s: S) -> Result<SlotId, String> {
731 match s.as_ref() {
732 "?principal" => Ok(SlotId::principal()),
733 "?resource" => Ok(SlotId::resource()),
734 _ => Err(format!(
735 "Invalid SlotId! Expected ?principal|?resource, got: {}",
736 s.as_ref()
737 )),
738 }
739}
740
741#[derive(Serialize, Deserialize)]
742struct LiteralTemplateLinked {
743 template_id: String,
744 link_id: String,
745 args: HashMap<String, String>,
746}
747
748impl From<TemplateLinked> for LiteralTemplateLinked {
749 fn from(i: TemplateLinked) -> Self {
750 Self {
751 template_id: i.template_id,
752 link_id: i.link_id,
753 args: i
754 .args
755 .into_iter()
756 .map(|(k, v)| (format!("{k}"), v))
757 .collect(),
758 }
759 }
760}
761
762fn add_template_links_to_set(path: impl AsRef<Path>, policy_set: &mut PolicySet) -> Result<()> {
764 for template_linked in load_liked_file(path)? {
765 let slot_env = create_slot_env(&template_linked.args)?;
766 policy_set
767 .link(
768 PolicyId::from_str(&template_linked.template_id)?,
769 PolicyId::from_str(&template_linked.link_id)?,
770 slot_env,
771 )
772 .into_diagnostic()?;
773 }
774 Ok(())
775}
776
777fn load_liked_file(path: impl AsRef<Path>) -> Result<Vec<TemplateLinked>> {
779 let f = match std::fs::File::open(path) {
780 Ok(f) => f,
781 Err(_) => {
782 return Ok(vec![]);
784 }
785 };
786 if f.metadata()
787 .into_diagnostic()
788 .wrap_err("Failed to read metadata")?
789 .len()
790 == 0
791 {
792 Ok(vec![])
794 } else {
795 serde_json::from_reader(f)
797 .into_diagnostic()
798 .wrap_err("Deserialization error")
799 }
800}
801
802fn update_template_linked_file(path: impl AsRef<Path>, new_linked: TemplateLinked) -> Result<()> {
804 let mut template_linked = load_liked_file(path.as_ref())?;
805 template_linked.push(new_linked);
806 write_template_linked_file(&template_linked, path.as_ref())
807}
808
809fn write_template_linked_file(linked: &[TemplateLinked], path: impl AsRef<Path>) -> Result<()> {
811 let f = OpenOptions::new()
812 .write(true)
813 .truncate(true)
814 .create(true)
815 .open(path)
816 .into_diagnostic()?;
817 serde_json::to_writer(f, linked).into_diagnostic()
818}
819
820pub fn authorize(args: &AuthorizeArgs) -> CedarExitCode {
821 println!();
822 let ans = execute_request(
823 &args.request,
824 &args.policies_file,
825 args.template_linked_file.as_ref(),
826 &args.entities_file,
827 args.schema_file.as_ref(),
828 args.timing,
829 );
830 match ans {
831 Ok(ans) => {
832 let status = match ans.decision() {
833 Decision::Allow => {
834 println!("ALLOW");
835 CedarExitCode::Success
836 }
837 Decision::Deny => {
838 println!("DENY");
839 CedarExitCode::AuthorizeDeny
840 }
841 };
842 if ans.diagnostics().errors().peekable().peek().is_some() {
843 println!();
844 for err in ans.diagnostics().errors() {
845 println!("{err}");
846 }
847 }
848 if args.verbose {
849 println!();
850 if ans.diagnostics().reason().peekable().peek().is_none() {
851 println!("note: no policies applied to this request");
852 } else {
853 println!("note: this decision was due to the following policies:");
854 for reason in ans.diagnostics().reason() {
855 println!(" {}", reason);
856 }
857 println!();
858 }
859 }
860 status
861 }
862 Err(errs) => {
863 for err in errs {
864 println!("{err:?}");
865 }
866 CedarExitCode::Failure
867 }
868 }
869}
870
871fn load_entities(entities_filename: impl AsRef<Path>, schema: Option<&Schema>) -> Result<Entities> {
873 match std::fs::OpenOptions::new()
874 .read(true)
875 .open(entities_filename.as_ref())
876 {
877 Ok(f) => Entities::from_json_file(f, schema)
878 .into_diagnostic()
879 .wrap_err_with(|| {
880 format!(
881 "failed to parse entities from file {}",
882 entities_filename.as_ref().display()
883 )
884 }),
885 Err(e) => Err(e).into_diagnostic().wrap_err_with(|| {
886 format!(
887 "failed to open entities file {}",
888 entities_filename.as_ref().display()
889 )
890 }),
891 }
892}
893
894fn rename_from_id_annotation(ps: PolicySet) -> Result<PolicySet> {
901 let mut new_ps = PolicySet::new();
902 let t_iter = ps.templates().map(|t| match t.annotation("id") {
903 None => Ok(t.clone()),
904 Some(anno) => anno.parse().map(|a| t.new_id(a)),
905 });
906 for t in t_iter {
907 let template = t
908 .into_diagnostic()
909 .wrap_err("failed to parse policy id annotation")?;
910 new_ps
911 .add_template(template)
912 .into_diagnostic()
913 .wrap_err("failed to add template to policy set")?;
914 }
915 let p_iter = ps.policies().map(|p| match p.annotation("id") {
916 None => Ok(p.clone()),
917 Some(anno) => anno.parse().map(|a| p.new_id(a)),
918 });
919 for p in p_iter {
920 let policy = p
921 .into_diagnostic()
922 .wrap_err("failed to parse policy id annotation")?;
923 new_ps
924 .add(policy)
925 .into_diagnostic()
926 .wrap_err("failed to add template to policy set")?;
927 }
928 Ok(new_ps)
929}
930
931fn read_policy_and_links(
932 policies_filename: impl AsRef<Path>,
933 links_filename: Option<impl AsRef<Path>>,
934) -> Result<PolicySet> {
935 let mut pset = read_policy_set(Some(policies_filename.as_ref()))?;
936 if let Some(links_filename) = links_filename {
937 add_template_links_to_set(links_filename.as_ref(), &mut pset)?;
938 }
939 Ok(pset)
940}
941
942fn read_from_file_or_stdin(filename: Option<impl AsRef<Path>>, context: &str) -> Result<String> {
944 let mut src_str = String::new();
945 match filename.as_ref() {
946 Some(path) => {
947 src_str = std::fs::read_to_string(path)
948 .into_diagnostic()
949 .wrap_err_with(|| {
950 format!(
951 "failed to open {} file {}",
952 context,
953 path.as_ref().display()
954 )
955 })?;
956 }
957 None => {
958 std::io::Read::read_to_string(&mut std::io::stdin(), &mut src_str)
959 .into_diagnostic()
960 .wrap_err_with(|| format!("failed to read {} from stdin", context))?;
961 }
962 };
963 Ok(src_str)
964}
965
966fn read_from_file(filename: impl AsRef<Path>, context: &str) -> Result<String> {
968 read_from_file_or_stdin(Some(filename), context)
969}
970
971fn read_policy_set(
972 filename: Option<impl AsRef<Path> + std::marker::Copy>,
973) -> miette::Result<PolicySet> {
974 let context = "policy set";
975 let ps_str = read_from_file_or_stdin(filename, context)?;
976 let ps = PolicySet::from_str(&ps_str)
977 .map_err(|err| {
978 let name = filename.map_or_else(
979 || "<stdin>".to_owned(),
980 |n| n.as_ref().display().to_string(),
981 );
982 Report::new(err).with_source_code(NamedSource::new(name, ps_str))
983 })
984 .wrap_err_with(|| format!("failed to parse {context}"))?;
985 rename_from_id_annotation(ps)
986}
987
988fn read_schema_file(filename: impl AsRef<Path> + std::marker::Copy) -> Result<Schema> {
989 let schema_src = read_from_file(filename, "schema")?;
990 Schema::from_str(&schema_src)
991 .into_diagnostic()
992 .wrap_err_with(|| {
993 format!(
994 "failed to parse schema from file {}",
995 filename.as_ref().display()
996 )
997 })
998}
999
1000fn execute_request(
1002 request: &RequestArgs,
1003 policies_filename: impl AsRef<Path> + std::marker::Copy,
1004 links_filename: Option<impl AsRef<Path>>,
1005 entities_filename: impl AsRef<Path>,
1006 schema_filename: Option<impl AsRef<Path> + std::marker::Copy>,
1007 compute_duration: bool,
1008) -> Result<Response, Vec<Report>> {
1009 let mut errs = vec![];
1010 let policies = match read_policy_and_links(policies_filename.as_ref(), links_filename) {
1011 Ok(pset) => pset,
1012 Err(e) => {
1013 errs.push(e);
1014 PolicySet::new()
1015 }
1016 };
1017 let schema = match schema_filename.map(read_schema_file) {
1018 None => None,
1019 Some(Ok(schema)) => Some(schema),
1020 Some(Err(e)) => {
1021 errs.push(e);
1022 None
1023 }
1024 };
1025 let entities = match load_entities(entities_filename, schema.as_ref()) {
1026 Ok(entities) => entities,
1027 Err(e) => {
1028 errs.push(e);
1029 Entities::empty()
1030 }
1031 };
1032 match request.get_request(schema.as_ref()) {
1033 Ok(request) if errs.is_empty() => {
1034 let authorizer = Authorizer::new();
1035 let auth_start = Instant::now();
1036 let ans = authorizer.is_authorized(&request, &policies, &entities);
1037 let auth_dur = auth_start.elapsed();
1038 if compute_duration {
1039 println!(
1040 "Authorization Time (micro seconds) : {}",
1041 auth_dur.as_micros()
1042 );
1043 }
1044 Ok(ans)
1045 }
1046 Ok(_) => Err(errs),
1047 Err(e) => {
1048 errs.push(e.wrap_err("failed to parse request"));
1049 Err(errs)
1050 }
1051 }
1052}