1use krabka_client_admin::{AclEntry, AclOperation, PermissionType, ResourceType};
2
3#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
4pub struct Capabilities(u16);
5
6impl Capabilities {
7 const VIEW_TOPICS: u16 = 1 << 0;
8 const CREATE_TOPICS: u16 = 1 << 1;
9 const ALTER_TOPICS: u16 = 1 << 2;
10 const DELETE_TOPICS: u16 = 1 << 3;
11 const VIEW_GROUPS: u16 = 1 << 4;
12 const VIEW_ACLS: u16 = 1 << 5;
13 const ALTER_ACLS: u16 = 1 << 6;
14 const ALTER_USERS: u16 = 1 << 7;
15 const VIEW_QUOTAS: u16 = 1 << 8;
16 const ALTER_QUOTAS: u16 = 1 << 9;
17 const VIEW_LOG_DIRS: u16 = 1 << 10;
18 const VIEW_USERS: u16 = 1 << 11;
19 const ALTER_LOG_DIRS: u16 = 1 << 12;
20 const EVERY: u16 = (1 << 13) - 1;
22
23 #[must_use]
29 pub const fn all() -> Self {
30 Self(Self::EVERY)
31 }
32
33 #[must_use]
35 pub const fn none() -> Self {
36 Self(0)
37 }
38
39 const fn contains(self, capability: u16) -> bool {
40 self.0 & capability != 0
41 }
42
43 #[must_use]
44 pub const fn can_view_topics(self) -> bool {
45 self.contains(Self::VIEW_TOPICS)
46 }
47 #[must_use]
48 pub const fn can_create_topics(self) -> bool {
49 self.contains(Self::CREATE_TOPICS)
50 }
51 #[must_use]
52 pub const fn can_alter_topics(self) -> bool {
53 self.contains(Self::ALTER_TOPICS)
54 }
55 #[must_use]
56 pub const fn can_delete_topics(self) -> bool {
57 self.contains(Self::DELETE_TOPICS)
58 }
59 #[must_use]
60 pub const fn can_view_groups(self) -> bool {
61 self.contains(Self::VIEW_GROUPS)
62 }
63 #[must_use]
64 pub const fn can_view_acls(self) -> bool {
65 self.contains(Self::VIEW_ACLS)
66 }
67 #[must_use]
68 pub const fn can_alter_acls(self) -> bool {
69 self.contains(Self::ALTER_ACLS)
70 }
71 #[must_use]
72 pub const fn can_alter_users(self) -> bool {
73 self.contains(Self::ALTER_USERS)
74 }
75 #[must_use]
76 pub const fn can_view_quotas(self) -> bool {
77 self.contains(Self::VIEW_QUOTAS)
78 }
79 #[must_use]
80 pub const fn can_alter_quotas(self) -> bool {
81 self.contains(Self::ALTER_QUOTAS)
82 }
83 #[must_use]
84 pub const fn can_view_log_dirs(self) -> bool {
85 self.contains(Self::VIEW_LOG_DIRS)
86 }
87 #[must_use]
88 pub const fn can_view_users(self) -> bool {
89 self.contains(Self::VIEW_USERS)
90 }
91 #[must_use]
92 pub const fn can_alter_log_dirs(self) -> bool {
93 self.contains(Self::ALTER_LOG_DIRS)
94 }
95}
96
97#[must_use]
98pub fn derive_capabilities(principal: &str, entries: &[AclEntry]) -> Capabilities {
99 derive_capabilities_for_optional_host(principal, None, entries)
100}
101
102#[must_use]
103pub fn derive_capabilities_for_host(
104 principal: &str,
105 peer_host: &str,
106 entries: &[AclEntry],
107) -> Capabilities {
108 derive_capabilities_for_optional_host(principal, Some(peer_host), entries)
109}
110
111fn derive_capabilities_for_optional_host(
112 principal: &str,
113 peer_host: Option<&str>,
114 entries: &[AclEntry],
115) -> Capabilities {
116 let effective = |resource_type, operation| {
117 has_effective_permission(principal, peer_host, entries, resource_type, operation)
118 };
119
120 let mut bits = 0;
121 let mut grant = |capability, granted| {
122 if granted {
123 bits |= capability;
124 }
125 };
126 grant(
127 Capabilities::VIEW_TOPICS,
128 effective(ResourceType::Topic, AclOperation::Describe),
129 );
130 grant(
131 Capabilities::CREATE_TOPICS,
132 effective(ResourceType::Cluster, AclOperation::Create),
133 );
134 grant(
135 Capabilities::ALTER_TOPICS,
136 effective(ResourceType::Topic, AclOperation::Alter)
137 || effective(ResourceType::Topic, AclOperation::AlterConfigs),
138 );
139 grant(
140 Capabilities::DELETE_TOPICS,
141 effective(ResourceType::Topic, AclOperation::Delete),
142 );
143 grant(
144 Capabilities::VIEW_GROUPS,
145 effective(ResourceType::Group, AclOperation::Describe),
146 );
147 grant(
148 Capabilities::VIEW_ACLS,
149 effective(ResourceType::Cluster, AclOperation::Describe),
150 );
151 grant(
152 Capabilities::ALTER_ACLS,
153 effective(ResourceType::Cluster, AclOperation::Alter),
154 );
155 grant(
156 Capabilities::ALTER_USERS,
157 effective(ResourceType::Cluster, AclOperation::Alter),
158 );
159 grant(
160 Capabilities::VIEW_QUOTAS,
161 effective(ResourceType::Cluster, AclOperation::Describe),
162 );
163 grant(
164 Capabilities::ALTER_QUOTAS,
165 effective(ResourceType::Cluster, AclOperation::Alter),
166 );
167 grant(
168 Capabilities::VIEW_LOG_DIRS,
169 effective(ResourceType::Cluster, AclOperation::Describe),
170 );
171 grant(
172 Capabilities::VIEW_USERS,
173 effective(ResourceType::Cluster, AclOperation::Describe),
174 );
175 grant(
176 Capabilities::ALTER_LOG_DIRS,
177 effective(ResourceType::Cluster, AclOperation::Alter),
178 );
179 Capabilities(bits)
180}
181
182fn is_for_principal_and_host(entry: &AclEntry, principal: &str, peer_host: Option<&str>) -> bool {
183 is_for_principal(entry, principal) && is_for_host(entry, peer_host)
184}
185
186fn is_for_principal(entry: &AclEntry, principal: &str) -> bool {
187 entry.principal == principal || entry.principal == "User:*"
188}
189
190fn is_for_host(entry: &AclEntry, peer_host: Option<&str>) -> bool {
191 if entry.host == "*" {
192 return true;
193 }
194
195 peer_host.is_some_and(|host| entry.host == host)
196}
197
198fn has_effective_permission(
199 principal: &str,
200 peer_host: Option<&str>,
201 entries: &[AclEntry],
202 resource_type: ResourceType,
203 operation: AclOperation,
204) -> bool {
205 let mut saw_allow = false;
206
207 for entry in entries {
208 if entry.resource_type != resource_type
209 || !is_for_principal_and_host(entry, principal, peer_host)
210 || !matches_operation(entry.operation, operation)
211 {
212 continue;
213 }
214
215 match entry.permission_type {
216 PermissionType::Allow => saw_allow = true,
217 PermissionType::Deny => return false,
218 }
219 }
220
221 saw_allow
222}
223
224fn matches_operation(stored: AclOperation, requested: AclOperation) -> bool {
225 stored == requested || matches!(stored, AclOperation::All) || implies(stored, requested)
226}
227
228fn implies(stored: AclOperation, requested: AclOperation) -> bool {
229 matches!(
230 (stored, requested),
231 (
232 AclOperation::Read | AclOperation::Write | AclOperation::Delete | AclOperation::Alter,
233 AclOperation::Describe,
234 ) | (AclOperation::AlterConfigs, AclOperation::DescribeConfigs)
235 )
236}