/* * * Copyright (c) 2020-2022 Project CHIP Authors * * Licensed 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 std::sync::Arc; use num_derive::FromPrimitive; use crate::acl::{self, AclEntry, AclMgr}; use crate::data_model::objects::*; use crate::error::*; use crate::interaction_model::core::IMStatusCode; use crate::interaction_model::messages::ib::{attr_list_write, ListOperation}; use crate::tlv::{FromTLV, TLVElement, TagType, ToTLV}; use log::{error, info}; pub const ID: u32 = 0x001F; #[derive(FromPrimitive)] pub enum Attributes { Acl = 0, Extension = 1, SubjectsPerEntry = 2, TargetsPerEntry = 3, EntriesPerFabric = 4, } pub struct AccessControlCluster { base: Cluster, acl_mgr: Arc, } impl AccessControlCluster { pub fn new(acl_mgr: Arc) -> Result, Error> { let mut c = Box::new(AccessControlCluster { base: Cluster::new(ID)?, acl_mgr, }); c.base.add_attribute(attr_acl_new()?)?; c.base.add_attribute(attr_extension_new()?)?; c.base.add_attribute(attr_subjects_per_entry_new()?)?; c.base.add_attribute(attr_targets_per_entry_new()?)?; c.base.add_attribute(attr_entries_per_fabric_new()?)?; Ok(c) } /// Write the ACL Attribute /// /// This takes care of 4 things, add item, edit item, delete item, delete list. /// Care about fabric-scoped behaviour is taken fn write_acl_attr( &mut self, op: ListOperation, data: &TLVElement, fab_idx: u8, ) -> Result<(), IMStatusCode> { info!("Performing ACL operation {:?}", op); let result = match op { ListOperation::AddItem | ListOperation::EditItem(_) => { let mut acl_entry = AclEntry::from_tlv(data).map_err(|_| IMStatusCode::ConstraintError)?; info!("ACL {:?}", acl_entry); // Overwrite the fabric index with our accessing fabric index acl_entry.fab_idx = Some(fab_idx); if let ListOperation::EditItem(index) = op { self.acl_mgr.edit(index as u8, fab_idx, acl_entry) } else { self.acl_mgr.add(acl_entry) } } ListOperation::DeleteItem(index) => self.acl_mgr.delete(index as u8, fab_idx), ListOperation::DeleteList => self.acl_mgr.delete_for_fabric(fab_idx), }; match result { Ok(_) => Ok(()), Err(Error::NoSpace) => Err(IMStatusCode::ResourceExhausted), _ => Err(IMStatusCode::ConstraintError), } } } impl ClusterType for AccessControlCluster { fn base(&self) -> &Cluster { &self.base } fn base_mut(&mut self) -> &mut Cluster { &mut self.base } fn read_custom_attribute(&self, encoder: &mut dyn Encoder, attr: &AttrDetails) { match num::FromPrimitive::from_u16(attr.attr_id) { Some(Attributes::Acl) => encoder.encode(EncodeValue::Closure(&|tag, tw| { let _ = tw.start_array(tag); let _ = self.acl_mgr.for_each_acl(|entry| { if !attr.fab_filter || Some(attr.fab_idx) == entry.fab_idx { let _ = entry.to_tlv(tw, TagType::Anonymous); } }); let _ = tw.end_container(); })), Some(Attributes::Extension) => encoder.encode(EncodeValue::Closure(&|tag, tw| { // Empty for now let _ = tw.start_array(tag); let _ = tw.end_container(); })), _ => { error!("Attribute not yet supported: this shouldn't happen"); } } } fn write_attribute( &mut self, attr: &AttrDetails, data: &TLVElement, ) -> Result<(), IMStatusCode> { let result = match num::FromPrimitive::from_u16(attr.attr_id) { Some(Attributes::Acl) => attr_list_write(attr, data, |op, data| { self.write_acl_attr(op, data, attr.fab_idx) }), _ => { error!("Attribute not yet supported: this shouldn't happen"); Err(IMStatusCode::NotFound) } }; if result.is_ok() { self.base.cluster_changed(); } result } } fn attr_acl_new() -> Result { Attribute::new( Attributes::Acl as u16, AttrValue::Custom, Access::RWFA, Quality::NONE, ) } fn attr_extension_new() -> Result { Attribute::new( Attributes::Extension as u16, AttrValue::Custom, Access::RWFA, Quality::NONE, ) } fn attr_subjects_per_entry_new() -> Result { Attribute::new( Attributes::SubjectsPerEntry as u16, AttrValue::Uint16(acl::SUBJECTS_PER_ENTRY as u16), Access::RV, Quality::FIXED, ) } fn attr_targets_per_entry_new() -> Result { Attribute::new( Attributes::TargetsPerEntry as u16, AttrValue::Uint16(acl::TARGETS_PER_ENTRY as u16), Access::RV, Quality::FIXED, ) } fn attr_entries_per_fabric_new() -> Result { Attribute::new( Attributes::EntriesPerFabric as u16, AttrValue::Uint16(acl::ENTRIES_PER_FABRIC as u16), Access::RV, Quality::FIXED, ) } #[cfg(test)] mod tests { use std::sync::Arc; use crate::{ acl::{AclEntry, AclMgr, AuthMode}, data_model::{ core::AttrReadEncoder, objects::{AttrDetails, ClusterType, Privilege}, }, interaction_model::messages::ib::ListOperation, tlv::{get_root_node_struct, ElementType, TLVElement, TLVWriter, TagType, ToTLV}, utils::writebuf::WriteBuf, }; use super::AccessControlCluster; #[test] /// Add an ACL entry fn acl_cluster_add() { let mut buf: [u8; 100] = [0; 100]; let buf_len = buf.len(); let mut writebuf = WriteBuf::new(&mut buf, buf_len); let mut tw = TLVWriter::new(&mut writebuf); let acl_mgr = Arc::new(AclMgr::new_with(false).unwrap()); let mut acl = AccessControlCluster::new(acl_mgr.clone()).unwrap(); let new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case); new.to_tlv(&mut tw, TagType::Anonymous).unwrap(); let data = get_root_node_struct(writebuf.as_borrow_slice()).unwrap(); // Test, ACL has fabric index 2, but the accessing fabric is 1 // the fabric index in the TLV should be ignored and the ACL should be created with entry 1 let result = acl.write_acl_attr(ListOperation::AddItem, &data, 1); assert_eq!(result, Ok(())); let verifier = AclEntry::new(1, Privilege::VIEW, AuthMode::Case); acl_mgr .for_each_acl(|a| { assert_eq!(*a, verifier); }) .unwrap(); } #[test] /// - The listindex used for edit should be relative to the current fabric fn acl_cluster_edit() { let mut buf: [u8; 100] = [0; 100]; let buf_len = buf.len(); let mut writebuf = WriteBuf::new(&mut buf, buf_len); let mut tw = TLVWriter::new(&mut writebuf); // Add 3 ACLs, belonging to fabric index 2, 1 and 2, in that order let acl_mgr = Arc::new(AclMgr::new_with(false).unwrap()); let mut verifier = [ AclEntry::new(2, Privilege::VIEW, AuthMode::Case), AclEntry::new(1, Privilege::VIEW, AuthMode::Case), AclEntry::new(2, Privilege::ADMIN, AuthMode::Case), ]; for i in verifier { acl_mgr.add(i).unwrap(); } let mut acl = AccessControlCluster::new(acl_mgr.clone()).unwrap(); let new = AclEntry::new(2, Privilege::VIEW, AuthMode::Case); new.to_tlv(&mut tw, TagType::Anonymous).unwrap(); let data = get_root_node_struct(writebuf.as_borrow_slice()).unwrap(); // Test, Edit Fabric 2's index 1 - with accessing fabring as 2 - allow let result = acl.write_acl_attr(ListOperation::EditItem(1), &data, 2); // Fabric 2's index 1, is actually our index 2, update the verifier verifier[2] = new; assert_eq!(result, Ok(())); // Also validate in the acl_mgr that the entries are in the right order let mut index = 0; acl_mgr .for_each_acl(|a| { assert_eq!(*a, verifier[index]); index += 1; }) .unwrap(); } #[test] /// - The listindex used for delete should be relative to the current fabric fn acl_cluster_delete() { // Add 3 ACLs, belonging to fabric index 2, 1 and 2, in that order let acl_mgr = Arc::new(AclMgr::new_with(false).unwrap()); let input = [ AclEntry::new(2, Privilege::VIEW, AuthMode::Case), AclEntry::new(1, Privilege::VIEW, AuthMode::Case), AclEntry::new(2, Privilege::ADMIN, AuthMode::Case), ]; for i in input { acl_mgr.add(i).unwrap(); } let mut acl = AccessControlCluster::new(acl_mgr.clone()).unwrap(); // data is don't-care actually let data = TLVElement::new(TagType::Anonymous, ElementType::True); // Test , Delete Fabric 1's index 0 let result = acl.write_acl_attr(ListOperation::DeleteItem(0), &data, 1); assert_eq!(result, Ok(())); let verifier = [input[0], input[2]]; // Also validate in the acl_mgr that the entries are in the right order let mut index = 0; acl_mgr .for_each_acl(|a| { assert_eq!(*a, verifier[index]); index += 1; }) .unwrap(); } #[test] /// - acl read with and without fabric filtering fn acl_cluster_read() { let mut buf: [u8; 100] = [0; 100]; let buf_len = buf.len(); let mut writebuf = WriteBuf::new(&mut buf, buf_len); // Add 3 ACLs, belonging to fabric index 2, 1 and 2, in that order let acl_mgr = Arc::new(AclMgr::new_with(false).unwrap()); let input = [ AclEntry::new(2, Privilege::VIEW, AuthMode::Case), AclEntry::new(1, Privilege::VIEW, AuthMode::Case), AclEntry::new(2, Privilege::ADMIN, AuthMode::Case), ]; for i in input { acl_mgr.add(i).unwrap(); } let acl = AccessControlCluster::new(acl_mgr.clone()).unwrap(); // Test 1, all 3 entries are read in the response without fabric filtering { let mut tw = TLVWriter::new(&mut writebuf); let mut encoder = AttrReadEncoder::new(&mut tw); let attr_details = AttrDetails { attr_id: 0, list_index: None, fab_idx: 1, fab_filter: false, }; acl.read_custom_attribute(&mut encoder, &attr_details); assert_eq!( &[ 21, 53, 1, 36, 0, 0, 55, 1, 24, 54, 2, 21, 36, 1, 1, 36, 2, 2, 54, 3, 24, 54, 4, 24, 36, 254, 2, 24, 21, 36, 1, 1, 36, 2, 2, 54, 3, 24, 54, 4, 24, 36, 254, 1, 24, 21, 36, 1, 5, 36, 2, 2, 54, 3, 24, 54, 4, 24, 36, 254, 2, 24, 24, 24, 24 ], writebuf.as_borrow_slice() ); } writebuf.reset(0); // Test 2, only single entry is read in the response with fabric filtering and fabric idx 1 { let mut tw = TLVWriter::new(&mut writebuf); let mut encoder = AttrReadEncoder::new(&mut tw); let attr_details = AttrDetails { attr_id: 0, list_index: None, fab_idx: 1, fab_filter: true, }; acl.read_custom_attribute(&mut encoder, &attr_details); assert_eq!( &[ 21, 53, 1, 36, 0, 0, 55, 1, 24, 54, 2, 21, 36, 1, 1, 36, 2, 2, 54, 3, 24, 54, 4, 24, 36, 254, 1, 24, 24, 24, 24 ], writebuf.as_borrow_slice() ); } writebuf.reset(0); // Test 3, only single entry is read in the response with fabric filtering and fabric idx 2 { let mut tw = TLVWriter::new(&mut writebuf); let mut encoder = AttrReadEncoder::new(&mut tw); let attr_details = AttrDetails { attr_id: 0, list_index: None, fab_idx: 2, fab_filter: true, }; acl.read_custom_attribute(&mut encoder, &attr_details); assert_eq!( &[ 21, 53, 1, 36, 0, 0, 55, 1, 24, 54, 2, 21, 36, 1, 1, 36, 2, 2, 54, 3, 24, 54, 4, 24, 36, 254, 2, 24, 21, 36, 1, 5, 36, 2, 2, 54, 3, 24, 54, 4, 24, 36, 254, 2, 24, 24, 24, 24 ], writebuf.as_borrow_slice() ); } } }