blob: 077ef84c6ea8df15af31a862a57152373c605523 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use super::*;
use maka_host_runtime::application::Caller;
use std::sync::Mutex;
struct Evidence {
allowed: bool,
caller: Option<Caller>,
}
struct CallerPolicy(Issuer);
impl Policy for CallerPolicy {
fn admit(&self, invocation: Invocation<'_>) -> Result<()> {
let evidence = self
.0
.evidence::<Evidence>(invocation.scope)
.ok_or("foreign source")?;
if evidence.allowed || invocation.provider.instance() == &id(4) {
Ok(())
} else {
Err("leaf denied".into())
}
}
fn caller(&self, scope: &Scope) -> Option<Caller> {
self.0.evidence::<Evidence>(scope)?.caller.clone()
}
}
struct Observe {
forward: bool,
observed: Arc<Mutex<Vec<Option<Caller>>>>,
}
impl Handler for Observe {
fn call(
&self,
method: String,
input: Value,
context: Context,
) -> BoxFuture<'static, Result<Value>> {
self.observed
.lock()
.unwrap()
.push(context.caller().cloned());
let forward = self.forward;
Box::pin(async move {
if forward {
context
.call("echo", 0, method, input, Attachments::default())?
.await
} else {
Ok(input)
}
})
}
}
#[tokio::test]
async fn caller_observation_survives_forwarding_without_granting_the_leaf() {
let provider = native_package();
let consumer = consumer();
let implementation = Implementation::Native {
implementation: "native.wrapper".into(),
};
let mut declaration = entry(true, implementation.clone(), MethodMode::Query);
declaration.imports = entry(false, implementation, MethodMode::Query).imports;
let wrapper = package("wrapper", declaration, None);
let mut composition = input(&provider, &consumer);
composition
.instances
.insert("wrapper".into(), node(&wrapper, 4, false));
composition
.instances
.get_mut("a")
.unwrap()
.bindings
.get_mut("echo")
.unwrap()[0] = Target::Instance {
path: vec!["wrapper".into()],
export: "echo".into(),
};
let observed = Arc::new(Mutex::new(Vec::new()));
let mut native = NativeRegistry::default();
for (name, forward) in [("native.echo", false), ("native.wrapper", true)] {
let observed = observed.clone();
native
.insert(
name.into(),
Arc::new(move |_, _| {
let observed = observed.clone();
Box::pin(async move {
NativeActivation {
exports: Ok(BTreeMap::from([(
"echo".into(),
Published {
contract: contract(MethodMode::Query),
value: Arc::new(Observe { forward, observed })
as Arc<dyn Handler>,
},
)])),
owner: native_owner(|| Box::pin(async { Ok(()) })),
}
})
}),
)
.unwrap();
}
let issuer = Issuer::default();
let app = Application::start(
composition,
vec![provider, consumer, wrapper],
native,
issuer.clone(),
Arc::new(CallerPolicy(issuer.clone())),
Some(maka_host_js::factory(Limits::default())),
)
.unwrap();
app.ready().await.unwrap();
let client = app.client();
let binding = client
.bind(client.activation(&id(2)).unwrap(), "echo".into())
.await
.unwrap()
.pop()
.unwrap();
let caller = Caller::new(
Arc::new(()),
maka_plugin_core::deployment::DeploymentRef {
state_domain: "original-ui-root".into(),
deployment: Uuid::new_v4(),
revision: 1,
},
id(2),
Uuid::new_v4(),
);
for (allowed, origin) in [
(true, Some(caller.clone())),
(false, Some(caller.clone())),
(true, None),
] {
observed.lock().unwrap().clear();
let scope = issuer
.issue_with(
Identity::Remote {
request_id: Uuid::new_v4(),
},
Evidence {
allowed,
caller: origin.clone(),
},
CancellationToken::new(),
)
.unwrap();
let result = client
.start(
binding.clone(),
"echo".into(),
json!("original"),
scope.clone(),
Attachments::default().into(),
)
.await
.unwrap()
.result()
.await;
if allowed {
assert_eq!(result.unwrap(), json!("original"));
} else {
assert!(result.unwrap_err().contains("leaf denied"));
}
scope.finish().await.unwrap();
let observations = observed.lock().unwrap();
assert_eq!(observations.len(), if allowed { 2 } else { 1 });
assert!(observations.iter().all(|value| value == &origin));
}
app.shutdown().await.unwrap();
}
#[tokio::test]
async fn ordinary_admission_does_not_create_an_authenticated_caller() {
let provider = native_package();
let consumer = consumer();
let observed = Arc::new(Mutex::new(Vec::new()));
let native = registry(Arc::new(Observe {
forward: false,
observed: observed.clone(),
}));
let issuer = Issuer::default();
let app = Application::start(
input(&provider, &consumer),
vec![provider, consumer],
native,
issuer.clone(),
Arc::new(Grant(issuer.clone())),
Some(maka_host_js::factory(Limits::default())),
)
.unwrap();
app.ready().await.unwrap();
let client = app.client();
let binding = client
.bind(client.activation(&id(2)).unwrap(), "echo".into())
.await
.unwrap()
.pop()
.unwrap();
let scope = scope(&issuer, true);
assert_eq!(
client
.start(
binding,
"echo".into(),
json!("allowed"),
scope.clone(),
Attachments::default().into()
)
.await
.unwrap()
.result()
.await
.unwrap(),
json!("allowed")
);
scope.finish().await.unwrap();
assert_eq!(*observed.lock().unwrap(), vec![None]);
app.shutdown().await.unwrap();
}