Skip to main content

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