cedar_policy_cli/
lib.rs

1/*
2 * Copyright 2022-2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17// This modules makes use of `return` to exit early with a particular exit code.
18// For consistency, it also uses `return` in some places where it could be
19// omitted.
20#![allow(clippy::needless_return)]
21
22use anyhow::{Context as _, Error, Result};
23use cedar_policy::*;
24use cedar_policy_formatter::{policies_str_to_pretty, Config};
25use clap::{Args, Parser, Subcommand};
26use serde::{Deserialize, Serialize};
27use std::{
28    collections::HashMap,
29    fs::OpenOptions,
30    path::Path,
31    process::{ExitCode, Termination},
32    str::FromStr,
33    time::Instant,
34};
35
36/// Basic Cedar CLI for evaluating authorization queries
37#[derive(Parser)]
38#[command(author, version, about, long_about = None)] // Pull from `Cargo.toml`
39pub struct Cli {
40    #[command(subcommand)]
41    pub command: Commands,
42}
43
44#[derive(Subcommand, Debug)]
45pub enum Commands {
46    /// Evaluate an authorization request
47    Authorize(AuthorizeArgs),
48    /// Evaluate a Cedar expression
49    Evaluate(EvaluateArgs),
50    /// Validate a policy set against a schema
51    Validate(ValidateArgs),
52    /// Check that policies successfully parse
53    CheckParse(CheckParseArgs),
54    /// Link a template
55    Link(LinkArgs),
56    /// Format a policy set
57    Format(FormatArgs),
58}
59
60#[derive(Args, Debug)]
61pub struct ValidateArgs {
62    /// File containing the schema
63    #[arg(short, long = "schema", value_name = "FILE")]
64    pub schema_file: String,
65    /// File containing the policy set
66    #[arg(short, long = "policies", value_name = "FILE")]
67    pub policies_file: String,
68}
69
70#[derive(Args, Debug)]
71pub struct CheckParseArgs {
72    /// File containing the policy set
73    #[clap(short, long = "policies", value_name = "FILE")]
74    pub policies_file: String,
75}
76
77/// This struct contains the arguments that together specify a request.
78#[derive(Args, Debug)]
79pub struct RequestArgs {
80    /// Principal for the request, e.g., User::"alice"
81    #[arg(short, long)]
82    pub principal: Option<String>,
83    /// Action for the request, e.g., Action::"view"
84    #[arg(short, long)]
85    pub action: Option<String>,
86    /// Resource for the request, e.g., File::"myfile.txt"
87    #[arg(short, long)]
88    pub resource: Option<String>,
89    /// File containing a JSON object representing the context for the request.
90    /// Should be a (possibly empty) map from keys to values.
91    #[arg(short, long = "context", value_name = "FILE")]
92    pub context_json_file: Option<String>,
93    /// File containing a JSON object representing the entire request. Must have
94    /// fields "principal", "action", "resource", and "context", where "context"
95    /// is a (possibly empty) map from keys to values. This option replaces
96    /// --principal, --action, etc.
97    #[arg(long = "request-json", value_name = "FILE", conflicts_with_all = &["principal", "action", "resource", "context_json_file"])]
98    pub request_json_file: Option<String>,
99}
100
101impl RequestArgs {
102    /// Turn this `RequestArgs` into the appropriate `Request` object
103    fn get_request(&self, schema: Option<&Schema>) -> Result<Request> {
104        match &self.request_json_file {
105            Some(jsonfile) => {
106                let jsonstring = std::fs::read_to_string(jsonfile)
107                    .context(format!("failed to open request-json file {jsonfile}"))?;
108                let qjson: RequestJSON = serde_json::from_str(&jsonstring)
109                    .context(format!("failed to parse request-json file {jsonfile}"))?;
110                let principal = qjson
111                    .principal
112                    .map(|s| {
113                        s.parse().context(format!(
114                            "failed to parse principal in {jsonfile} as entity Uid"
115                        ))
116                    })
117                    .transpose()?;
118                let action = qjson
119                    .action
120                    .map(|s| {
121                        s.parse().context(format!(
122                            "failed to parse action in {jsonfile} as entity Uid"
123                        ))
124                    })
125                    .transpose()?;
126                let resource = qjson
127                    .resource
128                    .map(|s| {
129                        s.parse().context(format!(
130                            "failed to parse resource in {jsonfile} as entity Uid"
131                        ))
132                    })
133                    .transpose()?;
134                let context = Context::from_json_value(
135                    qjson.context,
136                    schema.and_then(|s| Some((s, action.as_ref()?))),
137                )?;
138                Ok(Request::new(principal, action, resource, context))
139            }
140            None => {
141                let principal = self
142                    .principal
143                    .as_ref()
144                    .map(|s| {
145                        s.parse()
146                            .context(format!("failed to parse principal {s} as entity Uid"))
147                    })
148                    .transpose()?;
149                let action = self
150                    .action
151                    .as_ref()
152                    .map(|s| {
153                        s.parse()
154                            .context(format!("failed to parse action {s} as entity Uid"))
155                    })
156                    .transpose()?;
157                let resource = self
158                    .resource
159                    .as_ref()
160                    .map(|s| {
161                        s.parse()
162                            .context(format!("failed to parse resource {s} as entity Uid"))
163                    })
164                    .transpose()?;
165                let context: Context = match &self.context_json_file {
166                    None => Context::empty(),
167                    Some(jsonfile) => match std::fs::OpenOptions::new().read(true).open(jsonfile) {
168                        Ok(f) => Context::from_json_file(
169                            f,
170                            schema.and_then(|s| Some((s, action.as_ref()?))),
171                        )?,
172                        Err(e) => Err(Error::from(e)
173                            .context(format!("error while loading context from {jsonfile}")))?,
174                    },
175                };
176                Ok(Request::new(principal, action, resource, context))
177            }
178        }
179    }
180}
181
182#[derive(Args, Debug)]
183pub struct AuthorizeArgs {
184    /// Request args (incorporated by reference)
185    #[command(flatten)]
186    pub request: RequestArgs,
187    /// File containing the static Cedar policies and templates to evaluate against
188    #[arg(long = "policies", value_name = "FILE")]
189    pub policies_file: String,
190    /// File containing template linked policies
191    #[arg(long = "template-linked", value_name = "FILE")]
192    pub template_linked_file: Option<String>,
193    /// File containing schema information
194    // Used for schema-based parsing of entity hierarchy, if present
195    #[arg(long = "schema", value_name = "FILE")]
196    pub schema_file: Option<String>,
197    /// File containing JSON representation of the Cedar entity hierarchy
198    #[arg(long = "entities", value_name = "FILE")]
199    pub entities_file: String,
200    /// More verbose output. (For instance, indicate which policies applied to the request, if any.)
201    #[arg(short, long)]
202    pub verbose: bool,
203    /// Time authorization and report timing information
204    #[arg(short, long)]
205    pub timing: bool,
206}
207
208#[derive(Args, Debug)]
209pub struct LinkArgs {
210    /// File containing static policies and templates.
211    #[arg(short, long)]
212    pub policies_file: String,
213    /// File containing template-linked policies
214    #[arg(short, long)]
215    pub template_linked_file: String,
216    /// Id of the template to instantiate
217    #[arg(long)]
218    pub template_id: String,
219    /// Id for the new template linked policy
220    #[arg(short, long)]
221    pub new_id: String,
222    /// Arguments to fill slots
223    #[arg(short, long)]
224    pub arguments: Arguments,
225}
226
227#[derive(Args, Debug)]
228pub struct FormatArgs {
229    /// Optional policy file name. If none is provided, read input from stdin.
230    #[arg(value_name = "FILE")]
231    pub file_name: Option<String>,
232
233    /// Custom line width (default: 80).
234    #[arg(short, long, value_name = "UINT", default_value_t = 80)]
235    pub line_width: usize,
236
237    /// Custom indentation width (default: 2).
238    #[arg(short, long, value_name = "INT", default_value_t = 2)]
239    pub indent_width: isize,
240}
241
242/// Wrapper struct
243#[derive(Clone, Debug, Deserialize)]
244#[serde(try_from = "HashMap<String,String>")]
245pub struct Arguments {
246    pub data: HashMap<SlotId, String>,
247}
248
249impl TryFrom<HashMap<String, String>> for Arguments {
250    type Error = String;
251
252    fn try_from(value: HashMap<String, String>) -> Result<Self, Self::Error> {
253        Ok(Self {
254            data: value
255                .into_iter()
256                .map(|(k, v)| parse_slot_id(k).map(|slot_id| (slot_id, v)))
257                .collect::<Result<HashMap<SlotId, String>, String>>()?,
258        })
259    }
260}
261
262impl FromStr for Arguments {
263    type Err = serde_json::Error;
264
265    fn from_str(s: &str) -> Result<Self, Self::Err> {
266        serde_json::from_str(s)
267    }
268}
269
270/// This struct is the serde structure expected for --request-json
271#[derive(Deserialize)]
272struct RequestJSON {
273    /// Principal for the request
274    #[serde(default)]
275    principal: Option<String>,
276    /// Action for the request
277    #[serde(default)]
278    action: Option<String>,
279    /// Resource for the request
280    #[serde(default)]
281    resource: Option<String>,
282    /// Context for the request
283    context: serde_json::Value,
284}
285
286#[derive(Args, Debug)]
287pub struct EvaluateArgs {
288    /// Request args (incorporated by reference)
289    #[command(flatten)]
290    pub request: RequestArgs,
291    /// File containing schema information
292    // Used for schema-based parsing of entity hierarchy, if present
293    #[arg(long = "schema", value_name = "FILE")]
294    pub schema_file: Option<String>,
295    /// File containing JSON representation of the Cedar entity hierarchy.
296    /// This is optional; if not present, we'll just use an empty hierarchy.
297    #[arg(long = "entities", value_name = "FILE")]
298    pub entities_file: Option<String>,
299    /// Expression to evaluate
300    #[arg(value_name = "EXPRESSION")]
301    pub expression: String,
302}
303
304#[derive(Eq, PartialEq, Debug)]
305pub enum CedarExitCode {
306    // The command completed successfully with a result other than a
307    // authorization deny or validation failure.
308    Success,
309    // The command failed to complete successfully.
310    Failure,
311    // The command completed successfully, but the result of the authorization
312    // request was DENY.
313    AuthorizeDeny,
314    // The command completed successfully, but it detected a validation failure
315    // in the given schema and policies.
316    ValidationFailure,
317}
318
319impl Termination for CedarExitCode {
320    fn report(self) -> ExitCode {
321        match self {
322            CedarExitCode::Success => ExitCode::SUCCESS,
323            CedarExitCode::Failure => ExitCode::FAILURE,
324            CedarExitCode::AuthorizeDeny => ExitCode::from(2),
325            CedarExitCode::ValidationFailure => ExitCode::from(3),
326        }
327    }
328}
329
330pub fn check_parse(args: &CheckParseArgs) -> CedarExitCode {
331    match read_policy_file(&args.policies_file) {
332        Ok(_) => CedarExitCode::Success,
333        Err(e) => {
334            println!("{:#}", e);
335            CedarExitCode::Failure
336        }
337    }
338}
339
340pub fn validate(args: &ValidateArgs) -> CedarExitCode {
341    let pset = match read_policy_file(&args.policies_file) {
342        Ok(pset) => pset,
343        Err(e) => {
344            println!("{:#}", e);
345            return CedarExitCode::Failure;
346        }
347    };
348
349    let schema = match read_schema_file(&args.schema_file) {
350        Ok(schema) => schema,
351        Err(e) => {
352            println!("{:#}", e);
353            return CedarExitCode::Failure;
354        }
355    };
356
357    let validator = Validator::new(schema);
358    let result = validator.validate(&pset, ValidationMode::default());
359    if result.validation_passed() {
360        println!("Validation Passed");
361        return CedarExitCode::Success;
362    } else {
363        println!("Validation Results:");
364        for note in result.validation_errors() {
365            println!("{}", note);
366        }
367        return CedarExitCode::ValidationFailure;
368    }
369}
370
371pub fn evaluate(args: &EvaluateArgs) -> (CedarExitCode, EvalResult) {
372    println!();
373    let schema = match args.schema_file.as_ref().map(read_schema_file) {
374        None => None,
375        Some(Ok(schema)) => Some(schema),
376        Some(Err(e)) => {
377            println!("{:#}", e);
378            return (CedarExitCode::Failure, EvalResult::Bool(false));
379        }
380    };
381    let request = match args.request.get_request(schema.as_ref()) {
382        Ok(q) => q,
383        Err(e) => {
384            println!("error: {:#}", e);
385            return (CedarExitCode::Failure, EvalResult::Bool(false));
386        }
387    };
388    let expr = match Expression::from_str(&args.expression) {
389        Ok(expr) => expr,
390        Err(e) => {
391            println!("error while parsing the expression: {e}");
392            return (CedarExitCode::Failure, EvalResult::Bool(false));
393        }
394    };
395    let entities = match &args.entities_file {
396        None => Entities::empty(),
397        Some(file) => match load_entities(file, schema.as_ref()) {
398            Ok(entities) => entities,
399            Err(e) => {
400                println!("error: {:#}", e);
401                return (CedarExitCode::Failure, EvalResult::Bool(false));
402            }
403        },
404    };
405    match eval_expression(&request, &entities, &expr) {
406        Err(e) => {
407            println!("error while evaluating the expression: {e}");
408            return (CedarExitCode::Failure, EvalResult::Bool(false));
409        }
410        Ok(result) => {
411            println!("{result}");
412            return (CedarExitCode::Success, result);
413        }
414    }
415}
416
417pub fn link(args: &LinkArgs) -> CedarExitCode {
418    if let Err(msg) = link_inner(args) {
419        eprintln!("{:#}", msg);
420        CedarExitCode::Failure
421    } else {
422        CedarExitCode::Success
423    }
424}
425
426fn format_policies_inner(args: &FormatArgs) -> Result<()> {
427    let mut policies_str = String::new();
428    match &args.file_name {
429        Some(path) => {
430            policies_str = std::fs::read_to_string(path)?;
431        }
432        None => {
433            std::io::Read::read_to_string(&mut std::io::stdin(), &mut policies_str)?;
434        }
435    };
436    let config = Config {
437        line_width: args.line_width,
438        indent_width: args.indent_width,
439    };
440    println!("{}", policies_str_to_pretty(&policies_str, &config)?);
441    Ok(())
442}
443
444pub fn format_policies(args: &FormatArgs) -> CedarExitCode {
445    if let Err(msg) = format_policies_inner(args) {
446        eprintln!("{:#}", msg);
447        CedarExitCode::Failure
448    } else {
449        CedarExitCode::Success
450    }
451}
452
453fn create_slot_env(data: &HashMap<SlotId, String>) -> Result<HashMap<SlotId, EntityUid>> {
454    data.iter()
455        .map(|(key, value)| Ok(EntityUid::from_str(value).map(|euid| (key.clone(), euid))?))
456        .collect::<Result<HashMap<SlotId, EntityUid>>>()
457}
458
459fn link_inner(args: &LinkArgs) -> Result<()> {
460    let mut policies = read_policy_file(&args.policies_file)?;
461    let slotenv = create_slot_env(&args.arguments.data)?;
462    policies.link(
463        PolicyId::from_str(&args.template_id)?,
464        PolicyId::from_str(&args.new_id)?,
465        slotenv,
466    )?;
467    let linked = policies
468        .policy(&PolicyId::from_str(&args.new_id)?)
469        .context("Failed to add template-linked policy")?;
470    println!("Template Linked Policy Added: {linked}");
471    let linked = TemplateLinked {
472        template_id: args.template_id.clone(),
473        link_id: args.new_id.clone(),
474        args: args.arguments.data.clone(),
475    };
476
477    update_template_linked_file(&args.template_linked_file, linked)
478}
479
480#[derive(Clone, Serialize, Deserialize, Debug)]
481#[serde(try_from = "LiteralTemplateLinked")]
482#[serde(into = "LiteralTemplateLinked")]
483struct TemplateLinked {
484    template_id: String,
485    link_id: String,
486    args: HashMap<SlotId, String>,
487}
488
489impl TryFrom<LiteralTemplateLinked> for TemplateLinked {
490    type Error = String;
491
492    fn try_from(value: LiteralTemplateLinked) -> Result<Self, Self::Error> {
493        Ok(Self {
494            template_id: value.template_id,
495            link_id: value.link_id,
496            args: value
497                .args
498                .into_iter()
499                .map(|(k, v)| parse_slot_id(k).map(|slot_id| (slot_id, v)))
500                .collect::<Result<HashMap<SlotId, String>, Self::Error>>()?,
501        })
502    }
503}
504
505fn parse_slot_id<S: AsRef<str>>(s: S) -> Result<SlotId, String> {
506    match s.as_ref() {
507        "?principal" => Ok(SlotId::principal()),
508        "?resource" => Ok(SlotId::resource()),
509        _ => Err(format!(
510            "Invalid SlotId! Expected ?principal|?resource, got: {}",
511            s.as_ref()
512        )),
513    }
514}
515
516#[derive(Serialize, Deserialize)]
517struct LiteralTemplateLinked {
518    template_id: String,
519    link_id: String,
520    args: HashMap<String, String>,
521}
522
523impl From<TemplateLinked> for LiteralTemplateLinked {
524    fn from(i: TemplateLinked) -> Self {
525        Self {
526            template_id: i.template_id,
527            link_id: i.link_id,
528            args: i
529                .args
530                .into_iter()
531                .map(|(k, v)| (format!("{k}"), v))
532                .collect(),
533        }
534    }
535}
536
537/// Iterate over links in the template-linked file and add them to the set
538fn add_template_links_to_set(path: impl AsRef<Path>, policy_set: &mut PolicySet) -> Result<()> {
539    for template_linked in load_liked_file(path)? {
540        let slot_env = create_slot_env(&template_linked.args)?;
541        policy_set.link(
542            PolicyId::from_str(&template_linked.template_id)?,
543            PolicyId::from_str(&template_linked.link_id)?,
544            slot_env,
545        )?;
546    }
547    Ok(())
548}
549
550/// Read template linked set to a Vec
551fn load_liked_file(path: impl AsRef<Path>) -> Result<Vec<TemplateLinked>> {
552    let f = match std::fs::File::open(path) {
553        Ok(f) => f,
554        Err(_) => {
555            // If the file doesn't exist, then give back the empty entity set
556            return Ok(vec![]);
557        }
558    };
559    if f.metadata().context("Failed to read metadata")?.len() == 0 {
560        // File is empty, return empty set
561        Ok(vec![])
562    } else {
563        // File has contents, deserialize
564        serde_json::from_reader(f).context("Deserialization error")
565    }
566}
567
568/// Add a single template-linked policy to the linked file
569fn update_template_linked_file(path: impl AsRef<Path>, new_linked: TemplateLinked) -> Result<()> {
570    let mut template_linked = load_liked_file(path.as_ref())?;
571    template_linked.push(new_linked);
572    write_template_linked_file(&template_linked, path.as_ref())
573}
574
575/// Write a slice of template-linked policies to the linked file
576fn write_template_linked_file(linked: &[TemplateLinked], path: impl AsRef<Path>) -> Result<()> {
577    let f = OpenOptions::new()
578        .write(true)
579        .truncate(true)
580        .create(true)
581        .open(path)?;
582    Ok(serde_json::to_writer(f, linked)?)
583}
584
585pub fn authorize(args: &AuthorizeArgs) -> CedarExitCode {
586    println!();
587    let ans = execute_request(
588        &args.request,
589        &args.policies_file,
590        args.template_linked_file.as_ref(),
591        &args.entities_file,
592        args.schema_file.as_ref(),
593        args.timing,
594    );
595    match ans {
596        Ok(ans) => {
597            let status = match ans.decision() {
598                Decision::Allow => {
599                    println!("ALLOW");
600                    CedarExitCode::Success
601                }
602                Decision::Deny => {
603                    println!("DENY");
604                    CedarExitCode::AuthorizeDeny
605                }
606            };
607            if ans.diagnostics().errors().peekable().peek().is_some() {
608                println!();
609                for err in ans.diagnostics().errors() {
610                    println!("{}", err);
611                }
612            }
613            if args.verbose {
614                println!();
615                if ans.diagnostics().reason().peekable().peek().is_none() {
616                    println!("note: no policies applied to this request");
617                } else {
618                    println!("note: this decision was due to the following policies:");
619                    for reason in ans.diagnostics().reason() {
620                        println!("  {}", reason);
621                    }
622                    println!();
623                }
624            }
625            status
626        }
627        Err(errs) => {
628            for err in errs {
629                println!("{:#}", err);
630            }
631            CedarExitCode::Failure
632        }
633    }
634}
635
636/// Load an `Entities` object from the given JSON filename and optional schema.
637fn load_entities(entities_filename: impl AsRef<Path>, schema: Option<&Schema>) -> Result<Entities> {
638    match std::fs::OpenOptions::new()
639        .read(true)
640        .open(entities_filename.as_ref())
641    {
642        Ok(f) => Entities::from_json_file(f, schema).context(format!(
643            "failed to parse entities from file {}",
644            entities_filename.as_ref().display()
645        )),
646        Err(e) => Err(e).context(format!(
647            "failed to open entities file {}",
648            entities_filename.as_ref().display()
649        )),
650    }
651}
652
653/// Renames policies and templates based on (@id("new_id") annotation.
654/// If no such annotation exists, it keeps the current id.
655///
656/// This will rename template-linked policies to the id of their template, which may
657/// cause id conflicts, so only call this function before instancing
658/// templates into the policy set.
659fn rename_from_id_annotation(ps: PolicySet) -> PolicySet {
660    let mut new_ps = PolicySet::new();
661    let t_iter = ps.templates().map(|t| match t.annotation("id") {
662        None => t.clone(),
663        Some(anno) => t.new_id(anno.parse().expect("id annotation should be valid id")),
664    });
665    for t in t_iter {
666        new_ps.add_template(t).expect("should still be a template");
667    }
668    let p_iter = ps.policies().map(|p| match p.annotation("id") {
669        None => p.clone(),
670        Some(anno) => p.new_id(anno.parse().expect("id annotation should be valid id")),
671    });
672    for p in p_iter {
673        new_ps.add(p).expect("should still be a policy");
674    }
675    new_ps
676}
677
678fn read_policy_and_links(
679    policies_filename: impl AsRef<Path>,
680    links_filename: Option<impl AsRef<Path>>,
681) -> Result<PolicySet> {
682    let mut pset = read_policy_file(policies_filename.as_ref())?;
683    if let Some(links_filename) = links_filename {
684        add_template_links_to_set(links_filename.as_ref(), &mut pset)?;
685    }
686    Ok(pset)
687}
688
689fn read_policy_file(filename: impl AsRef<Path>) -> Result<PolicySet> {
690    let src = std::fs::read_to_string(filename.as_ref()).context(format!(
691        "failed to open policy file {}",
692        filename.as_ref().display()
693    ))?;
694    let ps = PolicySet::from_str(&src).context(format!(
695        "failed to parse policies from file {}",
696        filename.as_ref().display()
697    ))?;
698    Ok(rename_from_id_annotation(ps))
699}
700
701fn read_schema_file(filename: impl AsRef<Path>) -> Result<Schema> {
702    let schema_src = std::fs::read_to_string(filename.as_ref()).context(format!(
703        "failed to open schema file {}",
704        filename.as_ref().display()
705    ))?;
706    Schema::from_str(&schema_src).context(format!(
707        "failed to parse schema from file {}",
708        filename.as_ref().display()
709    ))
710}
711
712/// This uses the Cedar API to call the authorization engine.
713fn execute_request(
714    request: &RequestArgs,
715    policies_filename: impl AsRef<Path>,
716    links_filename: Option<impl AsRef<Path>>,
717    entities_filename: impl AsRef<Path>,
718    schema_filename: Option<impl AsRef<Path>>,
719    compute_duration: bool,
720) -> Result<Response, Vec<Error>> {
721    let mut errs = vec![];
722    let policies = match read_policy_and_links(policies_filename.as_ref(), links_filename) {
723        Ok(pset) => pset,
724        Err(e) => {
725            errs.push(e);
726            PolicySet::new()
727        }
728    };
729    let schema = match schema_filename.map(read_schema_file) {
730        None => None,
731        Some(Ok(schema)) => Some(schema),
732        Some(Err(e)) => {
733            errs.push(e);
734            None
735        }
736    };
737    let entities = match load_entities(entities_filename, schema.as_ref()) {
738        Ok(entities) => entities,
739        Err(e) => {
740            errs.push(e);
741            Entities::empty()
742        }
743    };
744    let request = match request.get_request(schema.as_ref()) {
745        Ok(q) => Some(q),
746        Err(e) => {
747            errs.push(e.context("failed to parse request"));
748            None
749        }
750    };
751    if errs.is_empty() {
752        let request = request.expect("if errs is empty, we should have a request");
753        let authorizer = Authorizer::new();
754        let auth_start = Instant::now();
755        let ans = authorizer.is_authorized(&request, &policies, &entities);
756        let auth_dur = auth_start.elapsed();
757        if compute_duration {
758            println!(
759                "Authorization Time (micro seconds) : {}",
760                auth_dur.as_micros()
761            );
762        }
763        Ok(ans)
764    } else {
765        Err(errs)
766    }
767}