Skip to main content

cedar_policy_cli/command/
link.rs

1/*
2 * Copyright Cedar Contributors
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
17use std::{collections::HashMap, fs::OpenOptions, path::Path, str::FromStr};
18
19use cedar_policy::{PolicyId, SlotId};
20use clap::Args;
21use miette::{miette, IntoDiagnostic, Result};
22use serde::Deserialize;
23
24use crate::{
25    create_slot_env, load_links_from_file, parse_slot_id, CedarExitCode, PoliciesArgs,
26    TemplateLinked,
27};
28
29#[derive(Args, Debug)]
30pub struct LinkArgs {
31    /// Policies args (incorporated by reference)
32    #[command(flatten)]
33    pub policies: PoliciesArgs,
34    /// Id of the template to link
35    #[arg(long)]
36    pub template_id: String,
37    /// Id for the new template linked policy
38    #[arg(short, long)]
39    pub new_id: String,
40    /// Arguments to fill slots
41    #[arg(short, long)]
42    pub arguments: Arguments,
43}
44
45/// Wrapper struct
46#[derive(Clone, Debug, Deserialize)]
47#[serde(try_from = "HashMap<String,String>")]
48pub struct Arguments {
49    pub data: HashMap<SlotId, String>,
50}
51
52impl TryFrom<HashMap<String, String>> for Arguments {
53    type Error = String;
54
55    fn try_from(value: HashMap<String, String>) -> Result<Self, Self::Error> {
56        Ok(Self {
57            data: value
58                .into_iter()
59                .map(|(k, v)| parse_slot_id(k).map(|slot_id| (slot_id, v)))
60                .collect::<Result<HashMap<SlotId, String>, String>>()?,
61        })
62    }
63}
64
65impl FromStr for Arguments {
66    type Err = serde_json::Error;
67
68    fn from_str(s: &str) -> Result<Self, Self::Err> {
69        serde_json::from_str(s)
70    }
71}
72
73pub fn link(args: &LinkArgs) -> CedarExitCode {
74    if let Err(err) = link_inner(args) {
75        println!("{err:?}");
76        CedarExitCode::Failure
77    } else {
78        CedarExitCode::Success
79    }
80}
81
82fn link_inner(args: &LinkArgs) -> Result<()> {
83    let mut policies = args.policies.get_policy_set()?;
84    let slotenv = create_slot_env(&args.arguments.data)?;
85    policies.link(
86        PolicyId::new(&args.template_id),
87        PolicyId::new(&args.new_id),
88        slotenv,
89    )?;
90    let linked = policies
91        .policy(&PolicyId::new(&args.new_id))
92        .ok_or_else(|| miette!("Failed to find newly-added template-linked policy"))?;
93    println!("Template-linked policy added: {linked}");
94
95    // If a `--template-linked` / `-k` option was provided, update that file with the new link
96    if let Some(links_filename) = args.policies.template_linked_file.as_ref() {
97        update_template_linked_file(
98            links_filename,
99            TemplateLinked {
100                template_id: args.template_id.clone(),
101                link_id: args.new_id.clone(),
102                args: args.arguments.data.clone(),
103            },
104        )?;
105    }
106
107    Ok(())
108}
109
110/// Add a single template-linked policy to the linked file
111fn update_template_linked_file(path: impl AsRef<Path>, new_linked: TemplateLinked) -> Result<()> {
112    let mut template_linked = load_links_from_file(path.as_ref())?;
113    template_linked.push(new_linked);
114    write_template_linked_file(&template_linked, path.as_ref())
115}
116
117/// Write a slice of template-linked policies to the linked file
118fn write_template_linked_file(linked: &[TemplateLinked], path: impl AsRef<Path>) -> Result<()> {
119    let f = OpenOptions::new()
120        .write(true)
121        .truncate(true)
122        .create(true)
123        .open(path)
124        .into_diagnostic()?;
125    serde_json::to_writer(f, linked).into_diagnostic()
126}