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