blob: 67d131ae5ca786aafefc0d785695a980834c5f70 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::base::SettingType;
use crate::handler::base::{Payload as HandlerPayload, Request, Response as SettingResponse};
use crate::handler::device_storage::{DeviceStorage, DeviceStorageCompatible};
use crate::handler::setting_handler::{SettingHandlerResult, StorageFactory};
use crate::message::base::Audience;
use crate::policy::response::{Error as PolicyError, Response};
use crate::policy::{
BoxedHandler, Context, GenerateHandlerResult, PolicyType, Request as PolicyRequest,
};
use crate::service;
use anyhow::Error;
use async_trait::async_trait;
use futures::future::BoxFuture;
use std::sync::Arc;
/// PolicyHandlers are in charge of applying and persisting policies set by clients.
#[async_trait]
pub trait PolicyHandler {
/// Called when a policy client makes a request on the policy API this handler controls.
async fn handle_policy_request(&mut self, request: PolicyRequest) -> Response;
/// Called when a setting request is intercepted for the setting this policy handler supervises.
///
/// If there are no policies or the request does not need to be modified, `None` should be
/// returned.
///
/// If this handler wants to consume the request and respond to the client directly, it should
/// return [`RequestTransform::Result`].
///
/// If this handler wants to modify the request, then let the setting handler handle it,
/// [`RequestTransform::Request`] should be returned, with the modified request.
///
/// [`RequestTransform::Result`]: enum.RequestTransform.html
/// [`RequestTransform::Request`]: enum.RequestTransform.html
async fn handle_setting_request(&mut self, request: Request) -> Option<RequestTransform>;
/// Called when a setting response is intercepted from the setting this policy handler
/// supervises.
///
/// If there are no policies or the response does not need to be modified, `None` should be
/// returned.
///
/// If this handler wants to modify the response and still let the original audience handle it,
/// [`Response`] should be returned, containing the modified response.
///
/// [`Response`]: ResponseTransform::Response
async fn handle_setting_response(
&mut self,
response: SettingResponse,
) -> Option<ResponseTransform>;
}
/// `RequestTransform` is returned by a [`PolicyHandler`] in response to a setting request that a
/// [`PolicyProxy`] intercepted. The presence of this value indicates that the policy handler has
/// decided to take action in order to apply policies.
///
/// [`PolicyHandler`]: trait.PolicyHandler.html
/// [`PolicyProxy`]: ../policy_proxy/struct.PolicyProxy.html
///
#[derive(Clone, Debug, PartialEq)]
pub enum RequestTransform {
/// A new, modified request that should be forwarded to the setting handler for processing.
Request(Request),
/// A result to return directly to the settings client.
Result(SettingHandlerResult),
}
/// `ResponseTransform` is returned by a [`PolicyHandler`] in response to a setting response that a
/// [`PolicyProxy`] intercepted. The presence of this value indicates that the policy handler has
/// decided to take action in order to apply policies.
///
/// [`PolicyHandler`]: trait.PolicyHandler.html
/// [`PolicyProxy`]: ../policy_proxy/struct.PolicyProxy.html
///
#[derive(Clone, Debug, PartialEq)]
pub enum ResponseTransform {
/// A new, modified response that should be forwarded.
Response(SettingResponse),
}
/// Trait used to create policy handlers.
#[async_trait]
pub trait Create: Sized {
async fn create(handler: ClientProxy) -> Result<Self, Error>;
}
/// Creates a [`PolicyHandler`] from the given [`Context`].
///
/// [`PolicyHandler`]: trait.PolicyHandler.html
/// [`Context`]: ../base/struct.Context.html
pub fn create_handler<C, T: StorageFactory + 'static>(
context: Context<T>,
) -> BoxFuture<'static, GenerateHandlerResult>
where
C: Create + PolicyHandler + Send + Sync + 'static,
{
Box::pin(async move {
let storage = context.storage_factory.get_store().await;
let proxy = ClientProxy::new(context.service_messenger, storage, context.policy_type);
C::create(proxy).await.map(|handler| Box::new(handler) as BoxedHandler)
})
}
/// `ClientProxy` provides common functionality, like messaging and persistence to policy handlers.
#[derive(Clone)]
pub struct ClientProxy {
service_messenger: service::message::Messenger,
storage: Arc<DeviceStorage>,
policy_type: PolicyType,
}
impl ClientProxy {
/// Sends a setting request to the underlying setting proxy this policy handler controls.
pub fn send_setting_request(
&self,
setting_type: SettingType,
request: Request,
) -> service::message::Receptor {
self.service_messenger
.message(
HandlerPayload::Request(request).into(),
Audience::Address(service::Address::Handler(setting_type)),
)
.send()
}
/// Requests the setting handler to rebroadcast a settings changed event to its listeners.
pub fn request_rebroadcast(&self, setting_type: SettingType) {
self.service_messenger
.message(
HandlerPayload::Request(Request::Rebroadcast).into(),
Audience::Address(service::Address::Handler(setting_type)),
)
.send();
}
}
impl ClientProxy {
pub fn new(
service_messenger: service::message::Messenger,
storage: Arc<DeviceStorage>,
policy_type: PolicyType,
) -> Self {
Self { service_messenger, storage, policy_type }
}
pub fn policy_type(&self) -> PolicyType {
self.policy_type
}
pub async fn read<S>(&self) -> S
where
S: DeviceStorageCompatible,
{
self.storage.get().await
}
/// Returns Ok if the value was written, or an Error if the write failed. The argument
/// `write_through` will block returning until the value has been completely written to
/// persistent store, rather than any temporary in-memory caching.
pub async fn write<S>(&self, value: &S, write_through: bool) -> Result<(), PolicyError>
where
S: DeviceStorageCompatible,
{
if *value == self.read().await {
return Ok(());
}
match self.storage.write(value, write_through).await {
Ok(_) => Ok(()),
Err(_) => Err(PolicyError::WriteFailure(self.policy_type)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::base::SettingType;
use crate::handler::base::{Payload as HandlerPayload, Request};
use crate::handler::device_storage::testing::InMemoryStorageFactory;
use crate::handler::device_storage::DeviceStorageFactory;
use crate::message::base::MessengerType;
use crate::policy::PolicyType;
use crate::privacy::types::PrivacyInfo;
use crate::service;
use crate::tests::message_utils::verify_payload;
#[fuchsia_async::run_until_stalled(test)]
async fn test_client_proxy_send_setting_request() {
let policy_type = PolicyType::Unknown;
let setting_request = Request::Get;
let target_setting_type = SettingType::Unknown;
let service_messenger_factory = service::message::create_hub();
let (_, mut setting_proxy_receptor) = service_messenger_factory
.create(MessengerType::Addressable(service::Address::Handler(
policy_type.setting_type(),
)))
.await
.expect("setting proxy messenger created");
let storage_factory = InMemoryStorageFactory::new();
storage_factory.initialize_storage::<PrivacyInfo>().await;
let storage = storage_factory.get_store().await;
let client_proxy = ClientProxy {
service_messenger: service_messenger_factory
.create(MessengerType::Unbound)
.await
.expect("messenger should be created")
.0,
storage,
policy_type,
};
client_proxy.send_setting_request(target_setting_type, setting_request.clone());
verify_payload(
service::Payload::Setting(HandlerPayload::Request(setting_request)),
&mut setting_proxy_receptor,
None,
)
.await
}
#[fuchsia_async::run_until_stalled(test)]
async fn test_client_proxy_request_rebroadcast() {
let policy_type = PolicyType::Unknown;
let setting_type = SettingType::Unknown;
let service_messenger_factory = service::message::create_hub();
let (_, mut receptor) = service_messenger_factory
.create(MessengerType::Addressable(service::Address::Handler(setting_type)))
.await
.expect("service receptor created");
let client_proxy = ClientProxy {
service_messenger: service_messenger_factory
.create(MessengerType::Unbound)
.await
.expect("messenger should be created")
.0,
storage: InMemoryStorageFactory::new().get_store().await,
policy_type,
};
client_proxy.request_rebroadcast(setting_type);
verify_payload(HandlerPayload::Request(Request::Rebroadcast).into(), &mut receptor, None)
.await
}
}