authly_client/
access_control.rs1use std::{future::Future, pin::Pin, sync::Arc};
4
5use authly_common::{
6 id::{AttrId, EntityId, Id128DynamicArrayConv},
7 proto::service::{self as proto},
8 service::{NamespacePropertyMapping, NamespacedPropertyAttribute},
9};
10use fnv::FnvHashSet;
11use http::header::AUTHORIZATION;
12use tonic::Request;
13use tracing::debug;
14
15use crate::{error, id_codec_error, token::AccessToken, Client, Error};
16
17pub trait AccessControl {
19 fn access_control_request(&self) -> AccessControlRequestBuilder<'_>;
21
22 fn evaluate(
24 &self,
25 builder: AccessControlRequestBuilder<'_>,
26 ) -> Pin<Box<dyn Future<Output = Result<bool, Error>> + Send + '_>>;
27}
28
29pub struct AccessControlRequestBuilder<'c> {
36 access_control: &'c (dyn AccessControl + Send + Sync),
37 property_mapping: Arc<NamespacePropertyMapping>,
38 access_token: Option<Arc<AccessToken>>,
39 resource_attributes: FnvHashSet<AttrId>,
40 peer_entity_ids: FnvHashSet<EntityId>,
41}
42
43impl<'c> AccessControlRequestBuilder<'c> {
44 pub fn new(
46 access_control: &'c (dyn AccessControl + Send + Sync),
47 property_mapping: Arc<NamespacePropertyMapping>,
48 ) -> Self {
49 Self {
50 access_control,
51 property_mapping,
52 access_token: None,
53 resource_attributes: Default::default(),
54 peer_entity_ids: Default::default(),
55 }
56 }
57
58 pub fn resource_attribute(
80 mut self,
81 attr: impl NamespacedPropertyAttribute,
82 ) -> Result<Self, Error> {
83 let attr_id = self.property_mapping.attribute_id(&attr).ok_or_else(|| {
84 debug!(
85 "invalid namespace/property/attribute label: {}/{}/{}",
86 attr.namespace(),
87 attr.property(),
88 attr.attribute(),
89 );
90 Error::InvalidPropertyAttributeLabel
91 })?;
92
93 self.resource_attributes.insert(attr_id);
94 Ok(self)
95 }
96
97 pub fn access_token(mut self, token: Arc<AccessToken>) -> Self {
101 self.access_token = Some(token);
102 self
103 }
104
105 pub fn peer_entity_id(mut self, entity_id: EntityId) -> Self {
107 self.peer_entity_ids.insert(entity_id);
108 self
109 }
110
111 pub fn resource_attributes(&self) -> impl Iterator<Item = AttrId> + use<'_> {
113 self.resource_attributes.iter().copied()
114 }
115
116 pub async fn enforce(self) -> Result<(), Error> {
118 if self.access_control.evaluate(self).await? {
119 Ok(())
120 } else {
121 Err(Error::AccessDenied)
122 }
123 }
124
125 pub async fn evaluate(self) -> Result<bool, Error> {
129 self.access_control.evaluate(self).await
130 }
131}
132
133pub(crate) fn get_resource_property_mapping(
134 proto_namespaces: Vec<proto::PropertyMappingNamespace>,
135) -> Result<Arc<NamespacePropertyMapping>, Error> {
136 let mut property_mapping = NamespacePropertyMapping::default();
137
138 for namespace in proto_namespaces {
139 let ns = property_mapping.namespace_mut(namespace.label);
140
141 for property in namespace.properties {
142 let ns_prop = ns.property_mut(property.label);
143
144 for attribute in property.attributes {
145 ns_prop.put(
146 attribute.label,
147 AttrId::try_from_bytes_dynamic(&attribute.obj_id).ok_or_else(id_codec_error)?,
148 );
149 }
150 }
151 }
152
153 Ok(Arc::new(property_mapping))
154}
155
156impl AccessControl for Client {
157 fn access_control_request(&self) -> AccessControlRequestBuilder<'_> {
158 AccessControlRequestBuilder::new(
159 self,
160 self.state
161 .configuration
162 .load()
163 .resource_property_mapping
164 .clone(),
165 )
166 }
167
168 fn evaluate(
169 &self,
170 builder: AccessControlRequestBuilder<'_>,
171 ) -> Pin<Box<dyn Future<Output = Result<bool, Error>> + Send + '_>> {
172 Box::pin(async move {
173 let mut request = Request::new(proto::AccessControlRequest {
174 resource_attributes: builder
175 .resource_attributes
176 .into_iter()
177 .map(|attr| attr.to_array_dynamic().to_vec())
178 .collect(),
179 peer_entity_attributes: vec![],
181 peer_entity_ids: builder
182 .peer_entity_ids
183 .into_iter()
184 .map(|eid| eid.to_array_dynamic().to_vec())
185 .collect(),
186 });
187 if let Some(access_token) = builder.access_token {
188 request.metadata_mut().append(
189 AUTHORIZATION.as_str(),
190 format!("Bearer {}", access_token.token)
191 .parse()
192 .map_err(error::unclassified)?,
193 );
194 }
195
196 let access_control_response = self
197 .current_service()
198 .access_control(request)
199 .await
200 .map_err(error::tonic)?
201 .into_inner();
202
203 Ok(access_control_response.value > 0)
204 })
205 }
206}