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