1use std::collections::{BTreeMap, BTreeSet, VecDeque};
11use std::iter;
12use std::sync::LazyLock;
13
14use itertools::Itertools;
15use maplit::btreeset;
16use mz_controller_types::ClusterId;
17use mz_expr::CollectionPlan;
18use mz_ore::str::StrExt;
19use mz_repr::CatalogItemId;
20use mz_repr::adt::mz_acl_item::{AclMode, MzAclItem};
21use mz_repr::role_id::RoleId;
22use mz_sql_parser::ast::{Ident, QualifiedReplica};
23use tracing::debug;
24
25use crate::catalog::{
26 CatalogItemType, ErrorMessageObjectDescription, ObjectType, SessionCatalog, SystemObjectType,
27};
28use crate::names::{
29 CommentObjectId, ObjectId, QualifiedItemName, ResolvedDatabaseSpecifier, ResolvedIds,
30 SchemaSpecifier, SystemObjectId,
31};
32use crate::plan::{self, PlanKind};
33use crate::plan::{
34 DataSourceDesc, Explainee, MutationKind, Plan, SideEffectingFunc, UpdatePrivilege,
35};
36use crate::session::metadata::SessionMetadata;
37use crate::session::user::{MZ_SUPPORT_ROLE_ID, MZ_SYSTEM_ROLE_ID, SUPPORT_USER, SYSTEM_USER};
38use crate::session::vars::SystemVars;
39
40fn rbac_check_preamble(
42 catalog: &impl SessionCatalog,
43 session_meta: &dyn SessionMetadata,
44) -> Result<(), UnauthorizedError> {
45 if catalog
50 .try_get_role(&session_meta.role_metadata().current_role)
51 .is_none()
52 {
53 return Err(UnauthorizedError::ConcurrentRoleDrop(
54 session_meta.role_metadata().current_role.clone(),
55 ));
56 };
57 if catalog
58 .try_get_role(&session_meta.role_metadata().session_role)
59 .is_none()
60 {
61 return Err(UnauthorizedError::ConcurrentRoleDrop(
62 session_meta.role_metadata().session_role.clone(),
63 ));
64 };
65 if catalog
66 .try_get_role(&session_meta.role_metadata().authenticated_role)
67 .is_none()
68 {
69 return Err(UnauthorizedError::ConcurrentRoleDrop(
70 session_meta.role_metadata().authenticated_role.clone(),
71 ));
72 };
73
74 Ok(())
75}
76
77fn filter_requirements(
79 catalog: &impl SessionCatalog,
80 session_meta: &dyn SessionMetadata,
81 rbac_requirements: RbacRequirements,
82) -> RbacRequirements {
83 let is_rbac_disabled = !is_rbac_enabled_for_session(catalog.system_vars(), session_meta)
86 && !session_meta.role_metadata().current_role.is_system()
87 && !session_meta.role_metadata().session_role.is_system();
88 let is_superuser = session_meta.is_superuser();
90 if is_rbac_disabled || is_superuser {
91 return rbac_requirements.filter_to_mandatory_requirements();
92 }
93
94 rbac_requirements
95}
96
97static DEFAULT_ITEM_USAGE: LazyLock<BTreeSet<CatalogItemType>> = LazyLock::new(|| {
99 btreeset! {CatalogItemType::Secret, CatalogItemType::Connection}
100});
101pub static CREATE_ITEM_USAGE: LazyLock<BTreeSet<CatalogItemType>> = LazyLock::new(|| {
104 let mut items = DEFAULT_ITEM_USAGE.clone();
105 items.insert(CatalogItemType::Type);
106 items
107});
108pub static EMPTY_ITEM_USAGE: LazyLock<BTreeSet<CatalogItemType>> = LazyLock::new(BTreeSet::new);
109
110static RESTRICT_TO_USER_OBJECTS_ALLOWED_OIDS: LazyLock<BTreeSet<u32>> = LazyLock::new(|| {
120 use mz_pgrepr::oid;
121 btreeset! {
122 oid::VIEW_MZ_MCP_DATA_PRODUCTS_OID,
123 oid::VIEW_MZ_MCP_DATA_PRODUCT_DETAILS_OID,
124 oid::VIEW_MZ_SHOW_MY_CLUSTER_PRIVILEGES_OID,
125 }
126});
127
128#[derive(Debug, thiserror::Error)]
130pub enum UnauthorizedError {
131 #[error("permission denied to {action}")]
133 Superuser { action: String },
134 #[error("must be owner of {}", objects.iter().map(|(object_type, object_name)| format!("{object_type} {object_name}")).join(", "))]
136 Ownership { objects: Vec<(ObjectType, String)> },
137 #[error("must be a member of {}", role_names.iter().map(|role| role.quoted()).join(", "))]
139 RoleMembership { role_names: Vec<String> },
140 #[error("permission denied for {object_description}")]
142 Privilege {
143 object_description: ErrorMessageObjectDescription,
144 role_name: String,
145 privileges: String,
146 },
147 #[error("permission denied to {action}")]
151 MzSystem { action: String },
152 #[error("permission denied to {action}")]
154 MzSupport { action: String },
155 #[error("role {0} was concurrently dropped")]
157 ConcurrentRoleDrop(RoleId),
158 #[error("access to system object {object_name} is restricted")]
160 RestrictedSystemObject { object_name: String },
161}
162
163impl UnauthorizedError {
164 pub fn detail(&self) -> Option<String> {
165 match &self {
166 UnauthorizedError::Superuser { action } => {
167 Some(format!("You must be a superuser to {}", action))
168 }
169 UnauthorizedError::Privilege {
170 object_description,
171 role_name,
172 privileges,
173 } => Some(format!(
174 "The '{role_name}' role needs {privileges} privileges on {object_description}"
175 )),
176 UnauthorizedError::MzSystem { .. } => {
177 Some(format!("You must be the '{}' role", SYSTEM_USER.name))
178 }
179 UnauthorizedError::MzSupport { .. } => Some(format!(
180 "The '{}' role has very limited privileges",
181 SUPPORT_USER.name
182 )),
183 UnauthorizedError::ConcurrentRoleDrop(_) => {
184 Some("Please disconnect and re-connect with a valid role.".into())
185 }
186 UnauthorizedError::RestrictedSystemObject { .. } => Some(
187 "Access to system catalog objects is restricted for this role. \
188 Contact your administrator if you need access."
189 .into(),
190 ),
191 UnauthorizedError::Ownership { .. } | UnauthorizedError::RoleMembership { .. } => None,
192 }
193 }
194}
195
196#[derive(Debug)]
198struct RbacRequirements {
199 role_membership: BTreeSet<RoleId>,
201 ownership: Vec<ObjectId>,
203 privileges: Vec<(SystemObjectId, AclMode, RoleId)>,
206 item_usage: &'static BTreeSet<CatalogItemType>,
211 superuser_action: Option<String>,
213}
214
215impl RbacRequirements {
216 fn empty() -> RbacRequirements {
217 RbacRequirements {
218 role_membership: BTreeSet::new(),
219 ownership: Vec::new(),
220 privileges: Vec::new(),
221 item_usage: &EMPTY_ITEM_USAGE,
222 superuser_action: None,
223 }
224 }
225
226 fn validate(
227 self,
228 catalog: &impl SessionCatalog,
229 session: &dyn SessionMetadata,
230 resolved_ids: &ResolvedIds,
231 ) -> Result<(), UnauthorizedError> {
232 let role_membership =
234 catalog.collect_role_membership(&session.role_metadata().current_role);
235
236 check_usage(catalog, session, resolved_ids, self.item_usage)?;
237
238 let unheld_membership: Vec<_> = self.role_membership.difference(&role_membership).collect();
241 if !unheld_membership.is_empty() {
242 let role_names = unheld_membership
243 .into_iter()
244 .map(|role_id| {
245 catalog
247 .try_get_role(role_id)
248 .map(|role| role.name().to_string())
249 .unwrap_or_else(|| role_id.to_string())
250 })
251 .collect();
252 return Err(UnauthorizedError::RoleMembership { role_names });
253 }
254
255 let unheld_ownership = self
258 .ownership
259 .into_iter()
260 .filter(|ownership| !check_owner_roles(ownership, &role_membership, catalog))
261 .collect();
262 ownership_err(unheld_ownership, catalog)?;
263
264 check_object_privileges(
265 catalog,
266 self.privileges,
267 role_membership,
268 session.role_metadata().current_role,
269 )?;
270
271 if let Some(action) = self.superuser_action {
272 return Err(UnauthorizedError::Superuser { action });
273 }
274
275 Ok(())
276 }
277
278 fn filter_to_mandatory_requirements(self) -> RbacRequirements {
279 let RbacRequirements {
280 role_membership,
281 ownership,
282 privileges,
283 item_usage,
284 superuser_action: _,
285 } = self;
286 let role_membership = role_membership
287 .into_iter()
288 .filter(|id| id.is_system())
289 .collect();
290 let ownership = ownership.into_iter().filter(|id| id.is_system()).collect();
291 let privileges = privileges
292 .into_iter()
293 .filter(|(id, _, _)| matches!(id, SystemObjectId::Object(oid) if oid.is_system()))
294 .update(|(_, acl_mode, _)| acl_mode.remove(AclMode::SELECT))
296 .filter(|(_, acl_mode, _)| !acl_mode.is_empty())
297 .collect();
298 let superuser_action = None;
299 RbacRequirements {
300 role_membership,
301 ownership,
302 privileges,
303 item_usage,
304 superuser_action,
305 }
306 }
307}
308
309impl Default for RbacRequirements {
310 fn default() -> Self {
311 RbacRequirements {
312 role_membership: BTreeSet::new(),
313 ownership: Vec::new(),
314 privileges: Vec::new(),
315 item_usage: &DEFAULT_ITEM_USAGE,
316 superuser_action: None,
317 }
318 }
319}
320
321fn check_restrict_to_user_objects(
329 catalog: &impl SessionCatalog,
330 session: &dyn SessionMetadata,
331 resolved_ids: &ResolvedIds,
332) -> Result<(), UnauthorizedError> {
333 if !session.restrict_to_user_objects() {
334 return Ok(());
335 }
336 for item_id in resolved_ids.items() {
337 if item_id.is_system() {
338 if let Some(item) = catalog.try_get_item(item_id) {
339 match item.item_type() {
340 CatalogItemType::Func | CatalogItemType::Type => {}
341 _ => {
342 if RESTRICT_TO_USER_OBJECTS_ALLOWED_OIDS.contains(&item.oid()) {
343 continue;
344 }
345 return Err(UnauthorizedError::RestrictedSystemObject {
346 object_name: item.name().item.clone(),
347 });
348 }
349 }
350 }
351 }
352 }
353 Ok(())
354}
355
356pub fn check_usage(
358 catalog: &impl SessionCatalog,
359 session: &dyn SessionMetadata,
360 resolved_ids: &ResolvedIds,
361 item_types: &BTreeSet<CatalogItemType>,
362) -> Result<(), UnauthorizedError> {
363 rbac_check_preamble(catalog, session)?;
364
365 check_restrict_to_user_objects(catalog, session, resolved_ids)?;
367
368 let role_membership = catalog.collect_role_membership(&session.role_metadata().current_role);
370
371 let existing_resolved_ids =
374 resolved_ids.retain_items(|item_id| catalog.try_get_item(item_id).is_some());
375
376 let required_privileges = generate_usage_privileges(
377 catalog,
378 &existing_resolved_ids,
379 session.role_metadata().current_role,
380 item_types,
381 )
382 .into_iter()
383 .collect();
384
385 let mut rbac_requirements = RbacRequirements::empty();
386 rbac_requirements.privileges = required_privileges;
387 let rbac_requirements = filter_requirements(catalog, session, rbac_requirements);
388 let required_privileges = rbac_requirements.privileges;
389
390 check_object_privileges(
391 catalog,
392 required_privileges,
393 role_membership,
394 session.role_metadata().current_role,
395 )?;
396
397 Ok(())
398}
399
400pub fn check_plan(
407 catalog: &impl SessionCatalog,
408 target_conn_role: Option<RoleId>,
412 session: &dyn SessionMetadata,
413 plan: &Plan,
414 target_cluster_id: Option<ClusterId>,
415 resolved_ids: &ResolvedIds,
416 sql_impl_resolved_ids: &ResolvedIds,
417) -> Result<(), UnauthorizedError> {
418 rbac_check_preamble(catalog, session)?;
419
420 check_restrict_to_user_objects(catalog, session, sql_impl_resolved_ids)?;
424
425 let rbac_requirements = generate_rbac_requirements(
426 catalog,
427 plan,
428 target_conn_role,
429 target_cluster_id,
430 session.role_metadata().current_role,
431 );
432 let rbac_requirements = filter_requirements(catalog, session, rbac_requirements);
433 debug!(
434 "rbac requirements {rbac_requirements:?} for plan {:?}",
435 PlanKind::from(plan)
436 );
437 rbac_requirements.validate(catalog, session, resolved_ids)
438}
439
440pub fn is_rbac_enabled_for_session(
442 system_vars: &SystemVars,
443 session: &dyn SessionMetadata,
444) -> bool {
445 let server_enabled = system_vars.enable_rbac_checks();
446 let session_enabled = session.enable_session_rbac_checks();
447
448 server_enabled || session_enabled
451}
452
453fn generate_rbac_requirements(
455 catalog: &impl SessionCatalog,
456 plan: &Plan,
457 target_conn_role: Option<RoleId>,
458 target_cluster_id: Option<ClusterId>,
459 role_id: RoleId,
460) -> RbacRequirements {
461 match plan {
462 Plan::CreateConnection(plan::CreateConnectionPlan {
463 name,
464 if_not_exists: _,
465 connection: _,
466 validate: _,
467 }) => RbacRequirements {
468 privileges: vec![(
469 SystemObjectId::Object(name.qualifiers.clone().into()),
470 AclMode::CREATE,
471 role_id,
472 )],
473 item_usage: &CREATE_ITEM_USAGE,
474 ..Default::default()
475 },
476 Plan::CreateDatabase(plan::CreateDatabasePlan {
477 name: _,
478 if_not_exists: _,
479 }) => RbacRequirements {
480 privileges: vec![(SystemObjectId::System, AclMode::CREATE_DB, role_id)],
481 item_usage: &CREATE_ITEM_USAGE,
482 ..Default::default()
483 },
484 Plan::CreateSchema(plan::CreateSchemaPlan {
485 database_spec,
486 schema_name: _,
487 if_not_exists: _,
488 }) => {
489 let privileges = match database_spec {
490 ResolvedDatabaseSpecifier::Ambient => Vec::new(),
491 ResolvedDatabaseSpecifier::Id(database_id) => {
492 vec![(
493 SystemObjectId::Object(database_id.into()),
494 AclMode::CREATE,
495 role_id,
496 )]
497 }
498 };
499 RbacRequirements {
500 privileges,
501 item_usage: &CREATE_ITEM_USAGE,
502 ..Default::default()
503 }
504 }
505 Plan::CreateRole(plan::CreateRolePlan {
506 name: _,
507 attributes,
508 }) => {
509 if attributes.superuser.unwrap_or(false) {
510 RbacRequirements {
511 superuser_action: Some("create superuser role".to_string()),
512 ..Default::default()
513 }
514 } else {
515 RbacRequirements {
516 privileges: vec![(SystemObjectId::System, AclMode::CREATE_ROLE, role_id)],
517 item_usage: &CREATE_ITEM_USAGE,
518 ..Default::default()
519 }
520 }
521 }
522 Plan::CreateNetworkPolicy(plan::CreateNetworkPolicyPlan { .. }) => RbacRequirements {
523 privileges: vec![(
524 SystemObjectId::System,
525 AclMode::CREATE_NETWORK_POLICY,
526 role_id,
527 )],
528 item_usage: &CREATE_ITEM_USAGE,
529 ..Default::default()
530 },
531 Plan::CreateCluster(plan::CreateClusterPlan {
532 name: _,
533 variant: _,
534 workload_class: _,
535 if_not_exists: _,
536 }) => RbacRequirements {
537 privileges: vec![(SystemObjectId::System, AclMode::CREATE_CLUSTER, role_id)],
538 item_usage: &CREATE_ITEM_USAGE,
539 ..Default::default()
540 },
541 Plan::CreateClusterReplica(plan::CreateClusterReplicaPlan {
542 cluster_id,
543 name: _,
544 config: _,
545 if_not_exists: _,
546 }) => RbacRequirements {
547 ownership: vec![ObjectId::Cluster(*cluster_id)],
548 item_usage: &CREATE_ITEM_USAGE,
549 ..Default::default()
550 },
551 Plan::CreateSource(plan::CreateSourcePlan {
552 name,
553 source,
554 if_not_exists: _,
555 timeline: _,
556 in_cluster,
557 }) => RbacRequirements {
558 privileges: generate_required_source_privileges(
559 name,
560 &source.data_source,
561 *in_cluster,
562 role_id,
563 ),
564 item_usage: &CREATE_ITEM_USAGE,
565 ..Default::default()
566 },
567 Plan::CreateSources(plans) => RbacRequirements {
568 privileges: plans
569 .iter()
570 .flat_map(
571 |plan::CreateSourcePlanBundle {
572 item_id: _,
573 global_id: _,
574 plan:
575 plan::CreateSourcePlan {
576 name,
577 source,
578 if_not_exists: _,
579 timeline: _,
580 in_cluster,
581 },
582 resolved_ids: _,
583 available_source_references: _,
584 }| {
585 generate_required_source_privileges(
586 name,
587 &source.data_source,
588 *in_cluster,
589 role_id,
590 )
591 .into_iter()
592 },
593 )
594 .collect(),
595 item_usage: &CREATE_ITEM_USAGE,
596 ..Default::default()
597 },
598 Plan::CreateSecret(plan::CreateSecretPlan {
599 name,
600 secret: _,
601 if_not_exists: _,
602 }) => RbacRequirements {
603 privileges: vec![(
604 SystemObjectId::Object(name.qualifiers.clone().into()),
605 AclMode::CREATE,
606 role_id,
607 )],
608 item_usage: &CREATE_ITEM_USAGE,
609 ..Default::default()
610 },
611 Plan::CreateSink(plan::CreateSinkPlan {
612 name,
613 sink,
614 with_snapshot: _,
615 if_not_exists: _,
616 in_cluster,
617 }) => {
618 let mut privileges = vec![(
619 SystemObjectId::Object(name.qualifiers.clone().into()),
620 AclMode::CREATE,
621 role_id,
622 )];
623 let items = iter::once(sink.from).map(|gid| catalog.resolve_item_id(&gid));
624 privileges.extend_from_slice(&generate_read_privileges(catalog, items, role_id));
625 privileges.push((
626 SystemObjectId::Object(in_cluster.into()),
627 AclMode::CREATE,
628 role_id,
629 ));
630 RbacRequirements {
631 privileges,
632 item_usage: &CREATE_ITEM_USAGE,
633 ..Default::default()
634 }
635 }
636 Plan::CreateTable(plan::CreateTablePlan {
637 name,
638 table: _,
639 if_not_exists: _,
640 }) => RbacRequirements {
641 privileges: vec![(
642 SystemObjectId::Object(name.qualifiers.clone().into()),
643 AclMode::CREATE,
644 role_id,
645 )],
646 item_usage: &CREATE_ITEM_USAGE,
647 ..Default::default()
648 },
649 Plan::CreateView(plan::CreateViewPlan {
650 name,
651 view: _,
652 replace,
653 drop_ids: _,
654 if_not_exists: _,
655 ambiguous_columns: _,
656 }) => RbacRequirements {
657 ownership: replace
658 .map(|id| vec![ObjectId::Item(id)])
659 .unwrap_or_default(),
660 privileges: vec![(
661 SystemObjectId::Object(name.qualifiers.clone().into()),
662 AclMode::CREATE,
663 role_id,
664 )],
665 item_usage: &CREATE_ITEM_USAGE,
666 ..Default::default()
667 },
668 Plan::CreateMaterializedView(plan::CreateMaterializedViewPlan {
669 name,
670 materialized_view,
671 replace,
672 drop_ids: _,
673 if_not_exists: _,
674 ambiguous_columns: _,
675 }) => RbacRequirements {
676 ownership: replace
681 .iter()
682 .chain(materialized_view.replacement_target.iter())
683 .map(|id| ObjectId::Item(*id))
684 .collect(),
685 privileges: vec![
686 (
687 SystemObjectId::Object(name.qualifiers.clone().into()),
688 AclMode::CREATE,
689 role_id,
690 ),
691 (
692 SystemObjectId::Object(materialized_view.cluster_id.into()),
693 AclMode::CREATE,
694 role_id,
695 ),
696 ],
697 item_usage: &CREATE_ITEM_USAGE,
698 ..Default::default()
699 },
700 Plan::CreateIndex(plan::CreateIndexPlan {
701 name,
702 index,
703 if_not_exists: _,
704 }) => {
705 let index_on_item = catalog.resolve_item_id(&index.on);
706 RbacRequirements {
707 ownership: vec![ObjectId::Item(index_on_item)],
708 privileges: vec![
709 (
710 SystemObjectId::Object(name.qualifiers.clone().into()),
711 AclMode::CREATE,
712 role_id,
713 ),
714 (
715 SystemObjectId::Object(index.cluster_id.into()),
716 AclMode::CREATE,
717 role_id,
718 ),
719 ],
720 item_usage: &CREATE_ITEM_USAGE,
721 ..Default::default()
722 }
723 }
724 Plan::CreateMetricSink(plan::CreateMetricSinkPlan {
725 name,
726 metric_sink,
727 if_not_exists: _,
728 }) => {
729 let mut privileges = vec![(
734 SystemObjectId::Object(name.qualifiers.clone().into()),
735 AclMode::CREATE,
736 role_id,
737 )];
738 let items = iter::once(metric_sink.from).map(|gid| catalog.resolve_item_id(&gid));
739 privileges.extend_from_slice(&generate_read_privileges(catalog, items, role_id));
740 privileges.push((
741 SystemObjectId::Object(metric_sink.cluster_id.into()),
742 AclMode::CREATE,
743 role_id,
744 ));
745 RbacRequirements {
746 privileges,
747 item_usage: &CREATE_ITEM_USAGE,
748 ..Default::default()
749 }
750 }
751 Plan::CreateType(plan::CreateTypePlan { name, typ: _ }) => RbacRequirements {
752 privileges: vec![(
753 SystemObjectId::Object(name.qualifiers.clone().into()),
754 AclMode::CREATE,
755 role_id,
756 )],
757 item_usage: &CREATE_ITEM_USAGE,
758 ..Default::default()
759 },
760 Plan::Comment(plan::CommentPlan {
761 object_id,
762 sub_component: _,
763 comment: _,
764 }) => {
765 let (ownership, privileges) = match object_id {
766 CommentObjectId::Role(_) => (
769 Vec::new(),
770 vec![(SystemObjectId::System, AclMode::CREATE_ROLE, role_id)],
771 ),
772 _ => (vec![ObjectId::from(*object_id)], Vec::new()),
773 };
774 RbacRequirements {
775 ownership,
776 privileges,
777 ..Default::default()
778 }
779 }
780 Plan::DropObjects(plan::DropObjectsPlan {
781 referenced_ids,
782 drop_ids: _,
783 object_type,
784 }) => {
785 let privileges = if object_type == &ObjectType::Role {
786 vec![(SystemObjectId::System, AclMode::CREATE_ROLE, role_id)]
787 } else {
788 referenced_ids
789 .iter()
790 .filter_map(|id| match id {
791 ObjectId::ClusterReplica((cluster_id, _)) => Some((
792 SystemObjectId::Object(cluster_id.into()),
793 AclMode::USAGE,
794 role_id,
795 )),
796 ObjectId::Schema((database_spec, _)) => match database_spec {
797 ResolvedDatabaseSpecifier::Ambient => None,
798 ResolvedDatabaseSpecifier::Id(database_id) => Some((
799 SystemObjectId::Object(database_id.into()),
800 AclMode::USAGE,
801 role_id,
802 )),
803 },
804 ObjectId::Item(item_id) => {
805 let item = catalog.get_item(item_id);
806 Some((
807 SystemObjectId::Object(item.name().qualifiers.clone().into()),
808 AclMode::USAGE,
809 role_id,
810 ))
811 }
812 ObjectId::Cluster(_)
813 | ObjectId::Database(_)
814 | ObjectId::Role(_)
815 | ObjectId::NetworkPolicy(_) => None,
816 })
817 .collect()
818 };
819 RbacRequirements {
820 ownership: referenced_ids.clone(),
822 privileges,
823 ..Default::default()
824 }
825 }
826 Plan::DropOwned(plan::DropOwnedPlan {
827 role_ids,
828 drop_ids: _,
829 privilege_revokes: _,
830 default_privilege_revokes: _,
831 }) => RbacRequirements {
832 role_membership: role_ids.into_iter().cloned().collect(),
833 ..Default::default()
834 },
835 Plan::ShowCreate(plan::ShowCreatePlan { id, row: _ }) => {
836 let container_id = match id {
837 ObjectId::Item(id) => Some(SystemObjectId::Object(
838 catalog.get_item(id).name().qualifiers.clone().into(),
839 )),
840 ObjectId::Schema((database_id, _schema_id)) => match database_id {
841 ResolvedDatabaseSpecifier::Ambient => None,
842 ResolvedDatabaseSpecifier::Id(id) => Some(SystemObjectId::Object(id.into())),
843 },
844 ObjectId::Cluster(_)
845 | ObjectId::ClusterReplica(_)
846 | ObjectId::Database(_)
847 | ObjectId::Role(_)
848 | ObjectId::NetworkPolicy(_) => None,
849 };
850 let privileges = match container_id {
851 Some(id) => vec![(id, AclMode::USAGE, role_id)],
852 None => Vec::new(),
853 };
854 RbacRequirements {
855 privileges,
856 item_usage: &EMPTY_ITEM_USAGE,
857 ..Default::default()
858 }
859 }
860 Plan::ShowColumns(plan::ShowColumnsPlan {
861 id,
862 select_plan,
863 new_resolved_ids: _,
864 }) => {
865 let mut privileges = vec![(
866 SystemObjectId::Object(catalog.get_item(id).name().qualifiers.clone().into()),
867 AclMode::USAGE,
868 role_id,
869 )];
870
871 for privilege in generate_rbac_requirements(
872 catalog,
873 &Plan::Select(select_plan.clone()),
874 target_conn_role,
875 target_cluster_id,
876 role_id,
877 )
878 .privileges
879 {
880 privileges.push(privilege);
881 }
882 RbacRequirements {
883 privileges,
884 ..Default::default()
885 }
886 }
887 Plan::Select(plan::SelectPlan {
888 source,
889 select: _,
890 when: _,
891 finishing: _,
892 copy_to: _,
893 }) => {
894 let items = source
895 .depends_on()
896 .into_iter()
897 .map(|gid| catalog.resolve_item_id(&gid));
898 let mut privileges = generate_read_privileges(catalog, items, role_id);
899 if let Some(privilege) = generate_cluster_usage_privileges(
900 source.as_const().is_some(),
901 target_cluster_id,
902 role_id,
903 ) {
904 privileges.push(privilege);
905 }
906 RbacRequirements {
907 privileges,
908 ..Default::default()
909 }
910 }
911 Plan::Subscribe(plan::SubscribePlan {
912 from,
913 with_snapshot: _,
914 when: _,
915 up_to: _,
916 copy_to: _,
917 emit_progress: _,
918 output: _,
919 }) => {
920 let items = from
921 .depends_on()
922 .into_iter()
923 .map(|gid| catalog.resolve_item_id(&gid));
924 let mut privileges = generate_read_privileges(catalog, items, role_id);
925 if let Some(cluster_id) = target_cluster_id {
926 privileges.push((
927 SystemObjectId::Object(cluster_id.into()),
928 AclMode::USAGE,
929 role_id,
930 ));
931 }
932 RbacRequirements {
933 privileges,
934 ..Default::default()
935 }
936 }
937 Plan::CopyFrom(plan::CopyFromPlan {
938 target_name: _,
939 target_id,
940 source: _,
941 columns: _,
942 source_desc: _,
943 mfp: _,
944 params: _,
945 filter: _,
946 }) => RbacRequirements {
947 privileges: vec![
948 (
949 SystemObjectId::Object(
950 catalog.get_item(target_id).name().qualifiers.clone().into(),
951 ),
952 AclMode::USAGE,
953 role_id,
954 ),
955 (
956 SystemObjectId::Object(target_id.into()),
957 AclMode::INSERT,
958 role_id,
959 ),
960 ],
961 ..Default::default()
962 },
963 Plan::CopyTo(plan::CopyToPlan {
964 select_plan,
965 desc: _,
966 to: _,
967 connection: _,
968 connection_id: _,
969 format: _,
970 max_file_size: _,
971 }) => {
972 let items = select_plan
973 .source
974 .depends_on()
975 .into_iter()
976 .map(|gid| catalog.resolve_item_id(&gid));
977 let mut privileges = generate_read_privileges(catalog, items, role_id);
978 if let Some(cluster_id) = target_cluster_id {
979 privileges.push((
980 SystemObjectId::Object(cluster_id.into()),
981 AclMode::USAGE,
982 role_id,
983 ));
984 }
985 RbacRequirements {
986 privileges,
987 ..Default::default()
988 }
989 }
990 Plan::ExplainPlan(plan::ExplainPlanPlan {
991 stage: _,
992 format: _,
993 config: _,
994 explainee,
995 })
996 | Plan::ExplainPushdown(plan::ExplainPushdownPlan { explainee }) => RbacRequirements {
997 privileges: match explainee {
998 Explainee::View(id)
999 | Explainee::MaterializedView(id)
1000 | Explainee::Index(id)
1001 | Explainee::ReplanView(id)
1002 | Explainee::ReplanMaterializedView(id)
1003 | Explainee::ReplanIndex(id) => {
1004 let item = catalog.get_item(id);
1005 let schema_id: ObjectId = item.name().qualifiers.clone().into();
1006 vec![(SystemObjectId::Object(schema_id), AclMode::USAGE, role_id)]
1007 }
1008 Explainee::Statement(stmt) => stmt
1009 .depends_on()
1010 .into_iter()
1011 .map(|id| {
1012 let item = catalog.get_item_by_global_id(&id);
1013 let schema_id: ObjectId = item.name().qualifiers.clone().into();
1014 (SystemObjectId::Object(schema_id), AclMode::USAGE, role_id)
1015 })
1016 .collect(),
1017 },
1018 item_usage: match explainee {
1019 Explainee::View(..)
1020 | Explainee::MaterializedView(..)
1021 | Explainee::Index(..)
1022 | Explainee::ReplanView(..)
1023 | Explainee::ReplanMaterializedView(..)
1024 | Explainee::ReplanIndex(..) => &EMPTY_ITEM_USAGE,
1025 Explainee::Statement(_) => &DEFAULT_ITEM_USAGE,
1026 },
1027 ..Default::default()
1028 },
1029 Plan::ExplainSinkSchema(plan::ExplainSinkSchemaPlan { sink_from, .. }) => {
1030 RbacRequirements {
1031 privileges: {
1032 let item = catalog.get_item_by_global_id(sink_from);
1033 let schema_id: ObjectId = item.name().qualifiers.clone().into();
1034 vec![(SystemObjectId::Object(schema_id), AclMode::USAGE, role_id)]
1035 },
1036 item_usage: &EMPTY_ITEM_USAGE,
1037 ..Default::default()
1038 }
1039 }
1040 Plan::ExplainTimestamp(plan::ExplainTimestampPlan {
1041 format: _,
1042 raw_plan,
1043 when: _,
1044 }) => RbacRequirements {
1045 privileges: raw_plan
1046 .depends_on()
1047 .into_iter()
1048 .map(|id| {
1049 let item = catalog.get_item_by_global_id(&id);
1050 let schema_id: ObjectId = item.name().qualifiers.clone().into();
1051 (SystemObjectId::Object(schema_id), AclMode::USAGE, role_id)
1052 })
1053 .collect(),
1054 ..Default::default()
1055 },
1056 Plan::Insert(plan::InsertPlan {
1057 id,
1058 values,
1059 returning,
1060 }) => {
1061 let schema_id: ObjectId = catalog.get_item(id).name().qualifiers.clone().into();
1062 let mut privileges = vec![
1063 (
1064 SystemObjectId::Object(schema_id.clone()),
1065 AclMode::USAGE,
1066 role_id,
1067 ),
1068 (SystemObjectId::Object(id.into()), AclMode::INSERT, role_id),
1069 ];
1070 let mut seen = BTreeSet::from([(schema_id, role_id)]);
1071
1072 if returning
1075 .iter()
1076 .any(|assignment| assignment.contains_column())
1077 {
1078 privileges.push((SystemObjectId::Object(id.into()), AclMode::SELECT, role_id));
1079 seen.insert((id.into(), role_id));
1080 }
1081
1082 let items = values
1083 .depends_on()
1084 .into_iter()
1085 .map(|gid| catalog.resolve_item_id(&gid));
1086 privileges.extend_from_slice(&generate_read_privileges_inner(
1087 catalog, items, role_id, &mut seen,
1088 ));
1089
1090 if let Some(privilege) = generate_cluster_usage_privileges(
1091 values.as_const().is_some(),
1092 target_cluster_id,
1093 role_id,
1094 ) {
1095 privileges.push(privilege);
1096 } else if !returning.is_empty() {
1097 if let Some(cluster_id) = target_cluster_id {
1100 privileges.push((
1101 SystemObjectId::Object(cluster_id.into()),
1102 AclMode::USAGE,
1103 role_id,
1104 ));
1105 }
1106 }
1107 RbacRequirements {
1108 privileges,
1109 ..Default::default()
1110 }
1111 }
1112 Plan::AlterCluster(plan::AlterClusterPlan {
1113 id,
1114 name: _,
1115 options: _,
1116 strategy: _,
1117 }) => RbacRequirements {
1118 ownership: vec![ObjectId::Cluster(*id)],
1119 item_usage: &CREATE_ITEM_USAGE,
1120 ..Default::default()
1121 },
1122 Plan::AlterSetCluster(plan::AlterSetClusterPlan { id, set_cluster }) => RbacRequirements {
1123 ownership: vec![ObjectId::Item(*id)],
1124 privileges: vec![(
1125 SystemObjectId::Object(set_cluster.into()),
1126 AclMode::CREATE,
1127 role_id,
1128 )],
1129 item_usage: &CREATE_ITEM_USAGE,
1130 ..Default::default()
1131 },
1132 Plan::AlterRetainHistory(plan::AlterRetainHistoryPlan {
1133 id,
1134 window: _,
1135 value: _,
1136 object_type: _,
1137 }) => RbacRequirements {
1138 ownership: vec![ObjectId::Item(*id)],
1139 item_usage: &CREATE_ITEM_USAGE,
1140 ..Default::default()
1141 },
1142 Plan::AlterSourceTimestampInterval(plan::AlterSourceTimestampIntervalPlan {
1143 id,
1144 value: _,
1145 interval: _,
1146 }) => RbacRequirements {
1147 ownership: vec![ObjectId::Item(*id)],
1148 item_usage: &CREATE_ITEM_USAGE,
1149 ..Default::default()
1150 },
1151 Plan::AlterConnection(plan::AlterConnectionPlan { id, action: _ }) => RbacRequirements {
1152 ownership: vec![ObjectId::Item(*id)],
1153 ..Default::default()
1154 },
1155 Plan::AlterSource(plan::AlterSourcePlan {
1156 item_id,
1157 ingestion_id: _,
1158 action: _,
1159 }) => RbacRequirements {
1160 ownership: vec![ObjectId::Item(*item_id)],
1161 item_usage: &CREATE_ITEM_USAGE,
1162 ..Default::default()
1163 },
1164 Plan::AlterSink(plan::AlterSinkPlan {
1165 item_id,
1166 global_id: _,
1167 sink,
1168 with_snapshot: _,
1169 in_cluster,
1170 set_options: _,
1171 reset_options: _,
1172 }) => {
1173 let items = iter::once(sink.from).map(|gid| catalog.resolve_item_id(&gid));
1174 let mut privileges = generate_read_privileges(catalog, items, role_id);
1175 privileges.push((
1176 SystemObjectId::Object(in_cluster.into()),
1177 AclMode::CREATE,
1178 role_id,
1179 ));
1180 RbacRequirements {
1181 ownership: vec![ObjectId::Item(*item_id)],
1182 privileges,
1183 item_usage: &CREATE_ITEM_USAGE,
1184 ..Default::default()
1185 }
1186 }
1187 Plan::AlterClusterRename(plan::AlterClusterRenamePlan {
1188 id,
1189 name: _,
1190 to_name: _,
1191 }) => RbacRequirements {
1192 ownership: vec![ObjectId::Cluster(*id)],
1193 ..Default::default()
1194 },
1195 Plan::AlterClusterSwap(plan::AlterClusterSwapPlan {
1196 id_a,
1197 id_b,
1198 name_a: _,
1199 name_b: _,
1200 name_temp: _,
1201 }) => RbacRequirements {
1202 ownership: vec![ObjectId::Cluster(*id_a), ObjectId::Cluster(*id_b)],
1203 ..Default::default()
1204 },
1205 Plan::AlterClusterReplicaRename(plan::AlterClusterReplicaRenamePlan {
1206 cluster_id,
1207 replica_id,
1208 name: _,
1209 to_name: _,
1210 }) => RbacRequirements {
1211 ownership: vec![ObjectId::ClusterReplica((*cluster_id, *replica_id))],
1212 ..Default::default()
1213 },
1214 Plan::AlterItemRename(plan::AlterItemRenamePlan {
1215 id,
1216 current_full_name: _,
1217 to_name: _,
1218 object_type: _,
1219 }) => RbacRequirements {
1220 ownership: vec![ObjectId::Item(*id)],
1221 ..Default::default()
1222 },
1223 Plan::AlterSchemaRename(plan::AlterSchemaRenamePlan {
1224 cur_schema_spec,
1225 new_schema_name: _,
1226 }) => {
1227 let privileges = match cur_schema_spec.0 {
1228 ResolvedDatabaseSpecifier::Id(db_id) => vec![(
1229 SystemObjectId::Object(ObjectId::Database(db_id)),
1230 AclMode::CREATE,
1231 role_id,
1232 )],
1233 ResolvedDatabaseSpecifier::Ambient => vec![],
1234 };
1235
1236 RbacRequirements {
1237 ownership: vec![ObjectId::Schema(*cur_schema_spec)],
1238 privileges,
1239 ..Default::default()
1240 }
1241 }
1242 Plan::AlterSchemaSwap(plan::AlterSchemaSwapPlan {
1243 schema_a_spec,
1244 schema_a_name: _,
1245 schema_b_spec,
1246 schema_b_name: _,
1247 name_temp: _,
1248 }) => {
1249 let mut privileges = vec![];
1250 if let ResolvedDatabaseSpecifier::Id(id_a) = schema_a_spec.0 {
1251 privileges.push((
1252 SystemObjectId::Object(ObjectId::Database(id_a)),
1253 AclMode::CREATE,
1254 role_id,
1255 ));
1256 }
1257 if let ResolvedDatabaseSpecifier::Id(id_b) = schema_b_spec.0 {
1258 privileges.push((
1259 SystemObjectId::Object(ObjectId::Database(id_b)),
1260 AclMode::CREATE,
1261 role_id,
1262 ));
1263 }
1264
1265 RbacRequirements {
1266 ownership: vec![
1267 ObjectId::Schema(*schema_a_spec),
1268 ObjectId::Schema(*schema_b_spec),
1269 ],
1270 privileges,
1271 ..Default::default()
1272 }
1273 }
1274 Plan::AlterSecret(plan::AlterSecretPlan { id, secret_as: _ }) => RbacRequirements {
1275 ownership: vec![ObjectId::Item(*id)],
1276 item_usage: &CREATE_ITEM_USAGE,
1277 ..Default::default()
1278 },
1279 Plan::AlterRole(plan::AlterRolePlan {
1280 id,
1281 name: _,
1282 option,
1283 }) => match option {
1284 plan::PlannedAlterRoleOption::Attributes(attributes)
1286 if attributes.superuser.is_some() =>
1287 {
1288 RbacRequirements {
1289 superuser_action: Some("alter superuser role".to_string()),
1290 ..Default::default()
1291 }
1292 }
1293 plan::PlannedAlterRoleOption::Attributes(plan::PlannedRoleAttributes {
1296 password,
1297 scram_iterations: _,
1300 nopassword: _,
1301 superuser: None,
1304 inherit: None,
1305 login: None,
1306 }) if password.is_some() && role_id == *id => RbacRequirements::default(),
1307 plan::PlannedAlterRoleOption::Attributes(attributes)
1309 if attributes.password.is_some() && role_id != *id =>
1310 {
1311 RbacRequirements {
1312 superuser_action: Some("alter password of role".to_string()),
1313 ..Default::default()
1314 }
1315 }
1316 plan::PlannedAlterRoleOption::Variable(var)
1321 if var.name().eq_ignore_ascii_case("restrict_to_user_objects") =>
1322 {
1323 RbacRequirements {
1324 superuser_action: Some("set restrict_to_user_objects".to_string()),
1325 ..Default::default()
1326 }
1327 }
1328 plan::PlannedAlterRoleOption::Variable(_) if role_id == *id => {
1330 RbacRequirements::default()
1331 }
1332 _ => RbacRequirements {
1334 privileges: vec![(SystemObjectId::System, AclMode::CREATE_ROLE, role_id)],
1335 item_usage: &CREATE_ITEM_USAGE,
1336 ..Default::default()
1337 },
1338 },
1339 Plan::AlterOwner(plan::AlterOwnerPlan {
1340 id,
1341 object_type: _,
1342 new_owner,
1343 }) => {
1344 let privileges = match id {
1345 ObjectId::ClusterReplica((cluster_id, _)) => {
1346 vec![(
1347 SystemObjectId::Object(cluster_id.into()),
1348 AclMode::CREATE,
1349 role_id,
1350 )]
1351 }
1352 ObjectId::Schema((database_spec, _)) => match database_spec {
1353 ResolvedDatabaseSpecifier::Ambient => Vec::new(),
1354 ResolvedDatabaseSpecifier::Id(database_id) => {
1355 vec![(
1356 SystemObjectId::Object(database_id.into()),
1357 AclMode::CREATE,
1358 role_id,
1359 )]
1360 }
1361 },
1362 ObjectId::Item(item_id) => {
1363 let item = catalog.get_item(item_id);
1364 vec![(
1365 SystemObjectId::Object(item.name().qualifiers.clone().into()),
1366 AclMode::CREATE,
1367 role_id,
1368 )]
1369 }
1370 ObjectId::Cluster(_)
1371 | ObjectId::Database(_)
1372 | ObjectId::Role(_)
1373 | ObjectId::NetworkPolicy(_) => Vec::new(),
1374 };
1375 RbacRequirements {
1376 role_membership: BTreeSet::from([*new_owner]),
1377 ownership: vec![id.clone()],
1378 privileges,
1379 ..Default::default()
1380 }
1381 }
1382 Plan::AlterTableAddColumn(plan::AlterTablePlan { relation_id, .. }) => RbacRequirements {
1383 ownership: vec![ObjectId::Item(*relation_id)],
1384 item_usage: &CREATE_ITEM_USAGE,
1385 ..Default::default()
1386 },
1387 Plan::AlterMaterializedViewApplyReplacement(
1388 plan::AlterMaterializedViewApplyReplacementPlan { id, replacement_id },
1389 ) => RbacRequirements {
1390 ownership: vec![ObjectId::Item(*id), ObjectId::Item(*replacement_id)],
1391 item_usage: &CREATE_ITEM_USAGE,
1392 ..Default::default()
1393 },
1394 Plan::AlterNetworkPolicy(plan::AlterNetworkPolicyPlan { id, .. }) => RbacRequirements {
1395 ownership: vec![ObjectId::NetworkPolicy(*id)],
1396 item_usage: &CREATE_ITEM_USAGE,
1397 ..Default::default()
1398 },
1399 Plan::ReadThenWrite(plan::ReadThenWritePlan {
1400 id,
1401 selection,
1402 finishing: _,
1403 assignments,
1404 kind,
1405 returning,
1406 }) => {
1407 let acl_mode = match kind {
1408 MutationKind::Insert => AclMode::INSERT,
1409 MutationKind::Update => AclMode::UPDATE,
1410 MutationKind::Delete => AclMode::DELETE,
1411 };
1412 let schema_id: ObjectId = catalog.get_item(id).name().qualifiers.clone().into();
1413 let mut privileges = vec![
1414 (
1415 SystemObjectId::Object(schema_id.clone()),
1416 AclMode::USAGE,
1417 role_id,
1418 ),
1419 (SystemObjectId::Object(id.into()), acl_mode, role_id),
1420 ];
1421 let mut seen = BTreeSet::from([(schema_id, role_id)]);
1422
1423 if assignments
1426 .values()
1427 .chain(returning.iter())
1428 .any(|assignment| assignment.contains_column())
1429 {
1430 privileges.push((SystemObjectId::Object(id.into()), AclMode::SELECT, role_id));
1431 seen.insert((id.into(), role_id));
1432 }
1433
1434 let items = selection
1441 .depends_on()
1442 .into_iter()
1443 .map(|gid| catalog.resolve_item_id(&gid));
1444 privileges.extend_from_slice(&generate_read_privileges_inner(
1445 catalog, items, role_id, &mut seen,
1446 ));
1447
1448 if let Some(privilege) = generate_cluster_usage_privileges(
1449 selection.as_const().is_some(),
1450 target_cluster_id,
1451 role_id,
1452 ) {
1453 privileges.push(privilege);
1454 }
1455 RbacRequirements {
1456 privileges,
1457 ..Default::default()
1458 }
1459 }
1460 Plan::GrantRole(plan::GrantRolePlan {
1461 role_ids: _,
1462 member_ids: _,
1463 grantor_id: _,
1464 })
1465 | Plan::RevokeRole(plan::RevokeRolePlan {
1466 role_ids: _,
1467 member_ids: _,
1468 grantor_id: _,
1469 }) => RbacRequirements {
1470 privileges: vec![(SystemObjectId::System, AclMode::CREATE_ROLE, role_id)],
1471 ..Default::default()
1472 },
1473 Plan::GrantPrivileges(plan::GrantPrivilegesPlan {
1474 update_privileges,
1475 grantees: _,
1476 })
1477 | Plan::RevokePrivileges(plan::RevokePrivilegesPlan {
1478 update_privileges,
1479 revokees: _,
1480 }) => {
1481 let mut privileges = Vec::with_capacity(update_privileges.len());
1482 for UpdatePrivilege { target_id, .. } in update_privileges {
1483 match target_id {
1484 SystemObjectId::Object(object_id) => match object_id {
1485 ObjectId::ClusterReplica((cluster_id, _)) => {
1486 privileges.push((
1487 SystemObjectId::Object(cluster_id.into()),
1488 AclMode::USAGE,
1489 role_id,
1490 ));
1491 }
1492 ObjectId::Schema((database_spec, _)) => match database_spec {
1493 ResolvedDatabaseSpecifier::Ambient => {}
1494 ResolvedDatabaseSpecifier::Id(database_id) => {
1495 privileges.push((
1496 SystemObjectId::Object(database_id.into()),
1497 AclMode::USAGE,
1498 role_id,
1499 ));
1500 }
1501 },
1502 ObjectId::Item(item_id) => {
1503 let item = catalog.get_item(item_id);
1504 privileges.push((
1505 SystemObjectId::Object(item.name().qualifiers.clone().into()),
1506 AclMode::USAGE,
1507 role_id,
1508 ))
1509 }
1510 ObjectId::Cluster(_)
1511 | ObjectId::Database(_)
1512 | ObjectId::Role(_)
1513 | ObjectId::NetworkPolicy(_) => {}
1514 },
1515 SystemObjectId::System => {}
1516 }
1517 }
1518 RbacRequirements {
1519 ownership: update_privileges
1520 .iter()
1521 .filter_map(|update_privilege| update_privilege.target_id.object_id())
1522 .cloned()
1523 .collect(),
1524 privileges,
1525 superuser_action: if update_privileges
1530 .iter()
1531 .any(|update_privilege| update_privilege.target_id.is_system())
1532 {
1533 Some("GRANT/REVOKE SYSTEM PRIVILEGES".to_string())
1534 } else {
1535 None
1536 },
1537 ..Default::default()
1538 }
1539 }
1540 Plan::AlterDefaultPrivileges(plan::AlterDefaultPrivilegesPlan {
1541 privilege_objects,
1542 privilege_acl_items: _,
1543 is_grant: _,
1544 }) => RbacRequirements {
1545 role_membership: privilege_objects
1546 .iter()
1547 .map(|privilege_object| privilege_object.role_id)
1548 .collect(),
1549 privileges: privilege_objects
1550 .into_iter()
1551 .filter_map(|privilege_object| {
1552 if let (Some(database_id), Some(_)) =
1553 (privilege_object.database_id, privilege_object.schema_id)
1554 {
1555 Some((
1556 SystemObjectId::Object(database_id.into()),
1557 AclMode::USAGE,
1558 role_id,
1559 ))
1560 } else {
1561 None
1562 }
1563 })
1564 .collect(),
1565 superuser_action: if privilege_objects
1571 .iter()
1572 .any(|privilege_object| privilege_object.role_id.is_public())
1573 {
1574 Some("ALTER DEFAULT PRIVILEGES FOR ALL ROLES".to_string())
1575 } else {
1576 None
1577 },
1578 ..Default::default()
1579 },
1580 Plan::ReassignOwned(plan::ReassignOwnedPlan {
1581 old_roles,
1582 new_role,
1583 reassign_ids: _,
1584 }) => RbacRequirements {
1585 role_membership: old_roles
1586 .into_iter()
1587 .cloned()
1588 .chain(iter::once(*new_role))
1589 .collect(),
1590 ..Default::default()
1591 },
1592 Plan::SideEffectingFunc(func) => {
1593 let role_membership = match func {
1594 SideEffectingFunc::PgCancelBackend {
1597 connection_id: None,
1598 } => BTreeSet::new(),
1599 SideEffectingFunc::PgCancelBackend {
1600 connection_id: Some(_),
1601 } => target_conn_role.map(|x| [x].into()).unwrap_or_default(),
1602 };
1603 RbacRequirements {
1604 role_membership,
1605 ..Default::default()
1606 }
1607 }
1608 Plan::ValidateConnection(plan::ValidateConnectionPlan { id, connection: _ }) => {
1609 let schema_id: ObjectId = catalog.get_item(id).name().qualifiers.clone().into();
1610 RbacRequirements {
1611 privileges: vec![
1612 (SystemObjectId::Object(schema_id), AclMode::USAGE, role_id),
1613 (SystemObjectId::Object(id.into()), AclMode::USAGE, role_id),
1614 ],
1615 ..Default::default()
1616 }
1617 }
1618 Plan::DiscardTemp
1619 | Plan::DiscardAll
1620 | Plan::EmptyQuery
1621 | Plan::ShowAllVariables
1622 | Plan::ShowVariable(plan::ShowVariablePlan { name: _ })
1623 | Plan::InspectShard(plan::InspectShardPlan { id: _ })
1624 | Plan::SetVariable(plan::SetVariablePlan {
1625 name: _,
1626 value: _,
1627 local: _,
1628 })
1629 | Plan::ResetVariable(plan::ResetVariablePlan { name: _ })
1630 | Plan::SetTransaction(plan::SetTransactionPlan { local: _, modes: _ })
1631 | Plan::StartTransaction(plan::StartTransactionPlan {
1632 access: _,
1633 isolation_level: _,
1634 })
1635 | Plan::CommitTransaction(plan::CommitTransactionPlan {
1636 transaction_type: _,
1637 })
1638 | Plan::AbortTransaction(plan::AbortTransactionPlan {
1639 transaction_type: _,
1640 })
1641 | Plan::AlterNoop(plan::AlterNoopPlan { object_type: _ })
1642 | Plan::AlterSystemSet(plan::AlterSystemSetPlan { name: _, value: _ })
1643 | Plan::AlterSystemReset(plan::AlterSystemResetPlan { name: _ })
1644 | Plan::AlterSystemResetAll(plan::AlterSystemResetAllPlan {})
1645 | Plan::Declare(plan::DeclarePlan {
1646 name: _,
1647 stmt: _,
1648 sql: _,
1649 params: _,
1650 })
1651 | Plan::Fetch(plan::FetchPlan {
1652 name: _,
1653 count: _,
1654 timeout: _,
1655 })
1656 | Plan::Close(plan::ClosePlan { name: _ })
1657 | Plan::Prepare(plan::PreparePlan {
1658 name: _,
1659 stmt: _,
1660 desc: _,
1661 sql: _,
1662 })
1663 | Plan::Execute(plan::ExecutePlan { name: _, params: _ })
1664 | Plan::Deallocate(plan::DeallocatePlan { name: _ })
1665 | Plan::Raise(plan::RaisePlan { severity: _ }) => Default::default(),
1666 }
1667}
1668
1669fn check_owner_roles(
1671 object_id: &ObjectId,
1672 role_ids: &BTreeSet<RoleId>,
1673 catalog: &impl SessionCatalog,
1674) -> bool {
1675 if let Some(owner_id) = catalog.get_owner_id(object_id) {
1676 role_ids.contains(&owner_id)
1677 } else {
1678 true
1679 }
1680}
1681
1682fn ownership_err(
1683 unheld_ownership: Vec<ObjectId>,
1684 catalog: &impl SessionCatalog,
1685) -> Result<(), UnauthorizedError> {
1686 if !unheld_ownership.is_empty() {
1687 let objects = unheld_ownership
1688 .into_iter()
1689 .map(|ownership| match ownership {
1690 ObjectId::Cluster(id) => (
1691 ObjectType::Cluster,
1692 catalog.get_cluster(id).name().to_string(),
1693 ),
1694 ObjectId::ClusterReplica((cluster_id, replica_id)) => {
1695 let cluster = catalog.get_cluster(cluster_id);
1696 let replica = catalog.get_cluster_replica(cluster_id, replica_id);
1697 let name = QualifiedReplica {
1700 cluster: Ident::new_unchecked(cluster.name()),
1701 replica: Ident::new_unchecked(replica.name()),
1702 };
1703 (ObjectType::ClusterReplica, name.to_string())
1704 }
1705 ObjectId::Database(id) => (
1706 ObjectType::Database,
1707 catalog.get_database(&id).name().to_string(),
1708 ),
1709 ObjectId::Schema((database_spec, schema_spec)) => {
1710 let schema = catalog.get_schema(&database_spec, &schema_spec);
1711 let name = catalog.resolve_full_schema_name(schema.name());
1712 (ObjectType::Schema, name.to_string())
1713 }
1714 ObjectId::Item(id) => {
1715 let item = catalog.get_item(&id);
1716 let name = catalog.resolve_full_name(item.name());
1717 (item.item_type().into(), name.to_string())
1718 }
1719 ObjectId::NetworkPolicy(id) => (
1720 ObjectType::NetworkPolicy,
1721 catalog.get_network_policy(&id).name().to_string(),
1722 ),
1723 ObjectId::Role(_) => unreachable!("roles have no owner"),
1724 })
1725 .collect();
1726 Err(UnauthorizedError::Ownership { objects })
1727 } else {
1728 Ok(())
1729 }
1730}
1731
1732fn generate_required_source_privileges(
1733 name: &QualifiedItemName,
1734 data_source: &DataSourceDesc,
1735 in_cluster: Option<ClusterId>,
1736 role_id: RoleId,
1737) -> Vec<(SystemObjectId, AclMode, RoleId)> {
1738 let mut privileges = vec![(
1739 SystemObjectId::Object(name.qualifiers.clone().into()),
1740 AclMode::CREATE,
1741 role_id,
1742 )];
1743 match (data_source, in_cluster) {
1744 (_, Some(id)) => {
1745 privileges.push((SystemObjectId::Object(id.into()), AclMode::CREATE, role_id))
1746 }
1747 (DataSourceDesc::Ingestion(_), None) => {
1748 privileges.push((SystemObjectId::System, AclMode::CREATE_CLUSTER, role_id))
1749 }
1750 (_, None) => {}
1755 }
1756 privileges
1757}
1758
1759fn generate_read_privileges(
1767 catalog: &impl SessionCatalog,
1768 ids: impl Iterator<Item = CatalogItemId>,
1769 role_id: RoleId,
1770) -> Vec<(SystemObjectId, AclMode, RoleId)> {
1771 generate_read_privileges_inner(catalog, ids, role_id, &mut BTreeSet::new())
1772}
1773
1774fn generate_read_privileges_inner(
1775 catalog: &impl SessionCatalog,
1776 ids: impl Iterator<Item = CatalogItemId>,
1777 role_id: RoleId,
1778 seen: &mut BTreeSet<(ObjectId, RoleId)>,
1779) -> Vec<(SystemObjectId, AclMode, RoleId)> {
1780 let mut privileges = Vec::new();
1781
1782 let mut queue: VecDeque<(CatalogItemId, RoleId)> = ids.map(|id| (id, role_id)).collect();
1785 while let Some((id, role_id)) = queue.pop_front() {
1786 if seen.insert((id.into(), role_id)) {
1787 let item = catalog.get_item(&id);
1788 let schema_id: ObjectId = item.name().qualifiers.clone().into();
1789 if seen.insert((schema_id.clone(), role_id)) {
1790 privileges.push((SystemObjectId::Object(schema_id), AclMode::USAGE, role_id))
1791 }
1792 match item.item_type() {
1793 CatalogItemType::View | CatalogItemType::MaterializedView => {
1794 privileges.push((SystemObjectId::Object(id.into()), AclMode::SELECT, role_id));
1795 let view_owner = item.owner_id();
1796 queue.extend(item.references().items().map(|id| (*id, view_owner)));
1797 }
1798 CatalogItemType::Table | CatalogItemType::Source => {
1799 privileges.push((SystemObjectId::Object(id.into()), AclMode::SELECT, role_id));
1800 }
1801 CatalogItemType::Type | CatalogItemType::Secret | CatalogItemType::Connection => {
1802 privileges.push((SystemObjectId::Object(id.into()), AclMode::USAGE, role_id));
1803 }
1804 CatalogItemType::Sink
1805 | CatalogItemType::MetricSink
1806 | CatalogItemType::Index
1807 | CatalogItemType::Func => {}
1808 }
1809 }
1810 }
1811
1812 privileges
1813}
1814
1815fn generate_usage_privileges(
1816 catalog: &impl SessionCatalog,
1817 ids: &ResolvedIds,
1818 role_id: RoleId,
1819 item_types: &BTreeSet<CatalogItemType>,
1820) -> BTreeSet<(SystemObjectId, AclMode, RoleId)> {
1821 ids.items()
1823 .filter_map(move |id| {
1824 let item = catalog.get_item(id);
1825 if item_types.contains(&item.item_type()) {
1826 let schema_id = item.name().qualifiers.clone().into();
1827 Some([
1828 (SystemObjectId::Object(schema_id), AclMode::USAGE, role_id),
1829 (SystemObjectId::Object(id.into()), AclMode::USAGE, role_id),
1830 ])
1831 } else {
1832 None
1833 }
1834 })
1835 .flatten()
1836 .collect()
1837}
1838
1839fn generate_cluster_usage_privileges(
1840 expr_is_const: bool,
1841 target_cluster_id: Option<ClusterId>,
1842 role_id: RoleId,
1843) -> Option<(SystemObjectId, AclMode, RoleId)> {
1844 if !expr_is_const {
1847 if let Some(cluster_id) = target_cluster_id {
1848 return Some((
1849 SystemObjectId::Object(cluster_id.into()),
1850 AclMode::USAGE,
1851 role_id,
1852 ));
1853 }
1854 }
1855
1856 None
1857}
1858
1859fn check_object_privileges(
1860 catalog: &impl SessionCatalog,
1861 privileges: Vec<(SystemObjectId, AclMode, RoleId)>,
1862 role_membership: BTreeSet<RoleId>,
1863 current_role_id: RoleId,
1864) -> Result<(), UnauthorizedError> {
1865 let mut role_memberships: BTreeMap<RoleId, BTreeSet<RoleId>> = BTreeMap::new();
1866 role_memberships.insert(current_role_id, role_membership);
1867 for (object_id, acl_mode, role_id) in privileges {
1868 if matches!(
1872 &object_id,
1873 SystemObjectId::Object(ObjectId::Schema((_, SchemaSpecifier::Temporary)))
1874 ) {
1875 continue;
1876 }
1877
1878 let role_membership = role_memberships
1879 .entry(role_id)
1880 .or_insert_with_key(|role_id| catalog.collect_role_membership(role_id));
1881 let object_privileges = catalog
1882 .get_privileges(&object_id)
1883 .expect("only object types with privileges will generate required privileges");
1884 let role_privileges = role_membership
1885 .iter()
1886 .flat_map(|role_id| object_privileges.get_acl_items_for_grantee(role_id))
1887 .map(|mz_acl_item| mz_acl_item.acl_mode)
1888 .fold(AclMode::empty(), |accum, acl_mode| accum.union(acl_mode));
1889 if !role_privileges.contains(acl_mode) {
1890 let role_name = catalog.get_role(&role_id).name().to_string();
1891 let privileges = acl_mode.to_error_string();
1892 return Err(UnauthorizedError::Privilege {
1893 object_description: ErrorMessageObjectDescription::from_sys_id(&object_id, catalog),
1894 role_name,
1895 privileges,
1896 });
1897 }
1898 }
1899
1900 Ok(())
1901}
1902
1903pub const fn all_object_privileges(object_type: SystemObjectType) -> AclMode {
1904 const TABLE_ACL_MODE: AclMode = AclMode::INSERT
1905 .union(AclMode::SELECT)
1906 .union(AclMode::UPDATE)
1907 .union(AclMode::DELETE);
1908 const USAGE_CREATE_ACL_MODE: AclMode = AclMode::USAGE.union(AclMode::CREATE);
1909 const ALL_SYSTEM_PRIVILEGES: AclMode = AclMode::CREATE_ROLE
1910 .union(AclMode::CREATE_DB)
1911 .union(AclMode::CREATE_CLUSTER)
1912 .union(AclMode::CREATE_NETWORK_POLICY);
1913
1914 const EMPTY_ACL_MODE: AclMode = AclMode::empty();
1915 match object_type {
1916 SystemObjectType::Object(ObjectType::Table) => TABLE_ACL_MODE,
1917 SystemObjectType::Object(ObjectType::View) => AclMode::SELECT,
1918 SystemObjectType::Object(ObjectType::MaterializedView) => AclMode::SELECT,
1919 SystemObjectType::Object(ObjectType::Source) => AclMode::SELECT,
1920 SystemObjectType::Object(ObjectType::Sink) => EMPTY_ACL_MODE,
1921 SystemObjectType::Object(ObjectType::MetricSink) => EMPTY_ACL_MODE,
1922 SystemObjectType::Object(ObjectType::Index) => EMPTY_ACL_MODE,
1923 SystemObjectType::Object(ObjectType::Type) => AclMode::USAGE,
1924 SystemObjectType::Object(ObjectType::Role) => EMPTY_ACL_MODE,
1925 SystemObjectType::Object(ObjectType::Cluster) => USAGE_CREATE_ACL_MODE,
1926 SystemObjectType::Object(ObjectType::ClusterReplica) => EMPTY_ACL_MODE,
1927 SystemObjectType::Object(ObjectType::Secret) => AclMode::USAGE,
1928 SystemObjectType::Object(ObjectType::NetworkPolicy) => AclMode::USAGE,
1929 SystemObjectType::Object(ObjectType::Connection) => AclMode::USAGE,
1930 SystemObjectType::Object(ObjectType::Database) => USAGE_CREATE_ACL_MODE,
1931 SystemObjectType::Object(ObjectType::Schema) => USAGE_CREATE_ACL_MODE,
1932 SystemObjectType::Object(ObjectType::Func) => EMPTY_ACL_MODE,
1933 SystemObjectType::System => ALL_SYSTEM_PRIVILEGES,
1934 }
1935}
1936
1937pub const fn owner_privilege(object_type: ObjectType, owner_id: RoleId) -> MzAclItem {
1938 MzAclItem {
1939 grantee: owner_id,
1940 grantor: owner_id,
1941 acl_mode: all_object_privileges(SystemObjectType::Object(object_type)),
1942 }
1943}
1944
1945const fn default_builtin_object_acl_mode(object_type: ObjectType) -> AclMode {
1946 match object_type {
1947 ObjectType::Table
1948 | ObjectType::View
1949 | ObjectType::MaterializedView
1950 | ObjectType::Source => AclMode::SELECT,
1951 ObjectType::Type | ObjectType::Schema => AclMode::USAGE,
1952 ObjectType::Sink
1953 | ObjectType::MetricSink
1954 | ObjectType::Index
1955 | ObjectType::Role
1956 | ObjectType::Cluster
1957 | ObjectType::ClusterReplica
1958 | ObjectType::Secret
1959 | ObjectType::Connection
1960 | ObjectType::Database
1961 | ObjectType::Func
1962 | ObjectType::NetworkPolicy => AclMode::empty(),
1963 }
1964}
1965
1966pub const fn support_builtin_object_privilege(object_type: ObjectType) -> MzAclItem {
1967 let acl_mode = default_builtin_object_acl_mode(object_type);
1968 MzAclItem {
1969 grantee: MZ_SUPPORT_ROLE_ID,
1970 grantor: MZ_SYSTEM_ROLE_ID,
1971 acl_mode,
1972 }
1973}
1974
1975pub const fn default_builtin_object_privilege(object_type: ObjectType) -> MzAclItem {
1976 let acl_mode = default_builtin_object_acl_mode(object_type);
1977 MzAclItem {
1978 grantee: RoleId::Public,
1979 grantor: MZ_SYSTEM_ROLE_ID,
1980 acl_mode,
1981 }
1982}