routing/bedrock/
with_rights.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::bedrock::request_metadata::{InheritRights, IntermediateRights, Metadata};
6use crate::error::RoutingError;
7use crate::rights::{Rights, RightsWalker};
8use crate::walk_state::WalkStateUnit;
9use async_trait::async_trait;
10use moniker::ExtendedMoniker;
11use router_error::RouterError;
12use sandbox::{CapabilityBound, Request, Routable, Router, RouterResponse};
13
14struct RightsRouter<T: CapabilityBound> {
15    router: Router<T>,
16    rights: Rights,
17    moniker: ExtendedMoniker,
18}
19
20#[async_trait]
21impl<T: CapabilityBound> Routable<T> for RightsRouter<T> {
22    async fn route(
23        &self,
24        request: Option<Request>,
25        debug: bool,
26    ) -> Result<RouterResponse<T>, router_error::RouterError> {
27        let request = request.ok_or(RouterError::InvalidArgs)?;
28        let RightsRouter { router, rights, moniker } = self;
29        let InheritRights(inherit) =
30            request.metadata.get_metadata().ok_or(RouterError::InvalidArgs)?;
31        let request_rights: Rights = match request.metadata.get_metadata() {
32            Some(request_rights) => request_rights,
33            None => {
34                if inherit {
35                    request.metadata.set_metadata(*rights);
36                    *rights
37                } else {
38                    Err(RouterError::InvalidArgs)?
39                }
40            }
41        };
42        let intermediate_rights: Option<IntermediateRights> = request.metadata.get_metadata();
43        let request_rights = RightsWalker::new(request_rights, moniker.clone());
44        let router_rights = RightsWalker::new(*rights, moniker.clone());
45        // The rights of the previous step (if any) of the route must be
46        // compatible with this step of the route.
47        if let Some(IntermediateRights(intermediate_rights)) = intermediate_rights {
48            let intermediate_rights_walker =
49                RightsWalker::new(intermediate_rights, moniker.clone());
50            intermediate_rights_walker
51                .validate_next(&router_rights)
52                .map_err(|e| router_error::RouterError::from(RoutingError::from(e)))?;
53        };
54        request.metadata.set_metadata(IntermediateRights(*rights));
55        // The rights of the request must be compatible with the
56        // rights of this step of the route.
57        match request_rights.validate_next(&router_rights) {
58            Ok(()) => router.route(Some(request), debug).await,
59            Err(e) => Err(RoutingError::from(e).into()),
60        }
61    }
62}
63
64pub trait WithRights {
65    /// Returns a router that ensures the capability request does not request
66    /// greater rights than provided at this stage of the route.
67    fn with_rights(self, moniker: impl Into<ExtendedMoniker>, rights: Rights) -> Self;
68}
69
70impl<T: CapabilityBound> WithRights for Router<T> {
71    fn with_rights(self, moniker: impl Into<ExtendedMoniker>, rights: Rights) -> Self {
72        Router::<T>::new(RightsRouter { rights, router: self, moniker: moniker.into() })
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use assert_matches::assert_matches;
80    use fidl_fuchsia_io as fio;
81    use router_error::{DowncastErrorForTest, RouterError};
82    use sandbox::{Data, Dict, WeakInstanceToken};
83    use std::sync::Arc;
84
85    #[derive(Debug)]
86    struct FakeComponentToken {}
87
88    impl FakeComponentToken {
89        fn new() -> WeakInstanceToken {
90            WeakInstanceToken { inner: Arc::new(FakeComponentToken {}) }
91        }
92    }
93
94    impl sandbox::WeakInstanceTokenAny for FakeComponentToken {
95        fn as_any(&self) -> &dyn std::any::Any {
96            self
97        }
98    }
99
100    #[fuchsia::test]
101    async fn rights_good() {
102        let source = Data::String("hello".into());
103        let base = Router::<Data>::new_ok(source);
104        let proxy = base.with_rights(ExtendedMoniker::ComponentManager, fio::RW_STAR_DIR.into());
105        let metadata = Dict::new();
106        metadata.set_metadata(InheritRights(false));
107        metadata.set_metadata(Into::<Rights>::into(fio::R_STAR_DIR));
108        let capability = proxy
109            .route(Some(Request { target: FakeComponentToken::new(), metadata }), false)
110            .await
111            .unwrap();
112        let capability = match capability {
113            RouterResponse::<Data>::Capability(d) => d,
114            c => panic!("Bad enum {:#?}", c),
115        };
116        assert_eq!(capability, Data::String("hello".into()));
117    }
118
119    #[fuchsia::test]
120    async fn rights_bad() {
121        let source = Data::String("hello".into());
122        let base = Router::<Data>::new_ok(source);
123        let proxy = base.with_rights(ExtendedMoniker::ComponentManager, fio::R_STAR_DIR.into());
124        let metadata = Dict::new();
125        metadata.set_metadata(InheritRights(false));
126        metadata.set_metadata(Into::<Rights>::into(fio::RW_STAR_DIR));
127        let error = proxy
128            .route(Some(Request { target: FakeComponentToken::new(), metadata }), false)
129            .await
130            .unwrap_err();
131        assert_matches!(
132            error,
133            RouterError::NotFound(err)
134            if matches!(
135                err.downcast_for_test::<RoutingError>(),
136                RoutingError::RightsRoutingError(
137                    crate::error::RightsRoutingError::Invalid { moniker: ExtendedMoniker::ComponentManager, requested, provided }
138                ) if *requested == <fio::Operations as Into<Rights>>::into(fio::RW_STAR_DIR) && *provided == <fio::Operations as Into<Rights>>::into(fio::R_STAR_DIR)
139            )
140        );
141    }
142
143    #[fuchsia::test]
144    async fn invalid_intermediate_rights() {
145        let source = Data::String("hello".into());
146        let base = Router::<Data>::new_ok(source)
147            .with_rights(ExtendedMoniker::ComponentManager, fio::R_STAR_DIR.into());
148        let intermediate =
149            base.with_rights(ExtendedMoniker::ComponentManager, fio::RW_STAR_DIR.into());
150        let metadata = Dict::new();
151        metadata.set_metadata(InheritRights(false));
152        metadata.set_metadata(Into::<Rights>::into(fio::R_STAR_DIR));
153        let error = intermediate
154            .route(Some(Request { target: FakeComponentToken::new(), metadata }), false)
155            .await
156            .unwrap_err();
157        assert_matches!(
158            error,
159            RouterError::NotFound(err)
160            if matches!(
161                err.downcast_for_test::<RoutingError>(),
162                RoutingError::RightsRoutingError(
163                    crate::error::RightsRoutingError::Invalid { moniker: ExtendedMoniker::ComponentManager, requested, provided }
164                ) if *requested == <fio::Operations as Into<Rights>>::into(fio::RW_STAR_DIR) && *provided == <fio::Operations as Into<Rights>>::into(fio::R_STAR_DIR)
165            )
166        );
167    }
168}