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 }) => RbacRequirements {
536 privileges: vec![(SystemObjectId::System, AclMode::CREATE_CLUSTER, role_id)],
537 item_usage: &CREATE_ITEM_USAGE,
538 ..Default::default()
539 },
540 Plan::CreateClusterReplica(plan::CreateClusterReplicaPlan {
541 cluster_id,
542 name: _,
543 config: _,
544 }) => RbacRequirements {
545 ownership: vec![ObjectId::Cluster(*cluster_id)],
546 item_usage: &CREATE_ITEM_USAGE,
547 ..Default::default()
548 },
549 Plan::CreateSource(plan::CreateSourcePlan {
550 name,
551 source,
552 if_not_exists: _,
553 timeline: _,
554 in_cluster,
555 }) => RbacRequirements {
556 privileges: generate_required_source_privileges(
557 name,
558 &source.data_source,
559 *in_cluster,
560 role_id,
561 ),
562 item_usage: &CREATE_ITEM_USAGE,
563 ..Default::default()
564 },
565 Plan::CreateSources(plans) => RbacRequirements {
566 privileges: plans
567 .iter()
568 .flat_map(
569 |plan::CreateSourcePlanBundle {
570 item_id: _,
571 global_id: _,
572 plan:
573 plan::CreateSourcePlan {
574 name,
575 source,
576 if_not_exists: _,
577 timeline: _,
578 in_cluster,
579 },
580 resolved_ids: _,
581 available_source_references: _,
582 }| {
583 generate_required_source_privileges(
584 name,
585 &source.data_source,
586 *in_cluster,
587 role_id,
588 )
589 .into_iter()
590 },
591 )
592 .collect(),
593 item_usage: &CREATE_ITEM_USAGE,
594 ..Default::default()
595 },
596 Plan::CreateSecret(plan::CreateSecretPlan {
597 name,
598 secret: _,
599 if_not_exists: _,
600 }) => RbacRequirements {
601 privileges: vec![(
602 SystemObjectId::Object(name.qualifiers.clone().into()),
603 AclMode::CREATE,
604 role_id,
605 )],
606 item_usage: &CREATE_ITEM_USAGE,
607 ..Default::default()
608 },
609 Plan::CreateSink(plan::CreateSinkPlan {
610 name,
611 sink,
612 with_snapshot: _,
613 if_not_exists: _,
614 in_cluster,
615 }) => {
616 let mut privileges = vec![(
617 SystemObjectId::Object(name.qualifiers.clone().into()),
618 AclMode::CREATE,
619 role_id,
620 )];
621 let items = iter::once(sink.from).map(|gid| catalog.resolve_item_id(&gid));
622 privileges.extend_from_slice(&generate_read_privileges(catalog, items, role_id));
623 privileges.push((
624 SystemObjectId::Object(in_cluster.into()),
625 AclMode::CREATE,
626 role_id,
627 ));
628 RbacRequirements {
629 privileges,
630 item_usage: &CREATE_ITEM_USAGE,
631 ..Default::default()
632 }
633 }
634 Plan::CreateTable(plan::CreateTablePlan {
635 name,
636 table: _,
637 if_not_exists: _,
638 }) => RbacRequirements {
639 privileges: vec![(
640 SystemObjectId::Object(name.qualifiers.clone().into()),
641 AclMode::CREATE,
642 role_id,
643 )],
644 item_usage: &CREATE_ITEM_USAGE,
645 ..Default::default()
646 },
647 Plan::CreateView(plan::CreateViewPlan {
648 name,
649 view: _,
650 replace,
651 drop_ids: _,
652 if_not_exists: _,
653 ambiguous_columns: _,
654 }) => RbacRequirements {
655 ownership: replace
656 .map(|id| vec![ObjectId::Item(id)])
657 .unwrap_or_default(),
658 privileges: vec![(
659 SystemObjectId::Object(name.qualifiers.clone().into()),
660 AclMode::CREATE,
661 role_id,
662 )],
663 item_usage: &CREATE_ITEM_USAGE,
664 ..Default::default()
665 },
666 Plan::CreateMaterializedView(plan::CreateMaterializedViewPlan {
667 name,
668 materialized_view,
669 replace,
670 drop_ids: _,
671 if_not_exists: _,
672 ambiguous_columns: _,
673 }) => RbacRequirements {
674 ownership: replace
679 .iter()
680 .chain(materialized_view.replacement_target.iter())
681 .map(|id| ObjectId::Item(*id))
682 .collect(),
683 privileges: vec![
684 (
685 SystemObjectId::Object(name.qualifiers.clone().into()),
686 AclMode::CREATE,
687 role_id,
688 ),
689 (
690 SystemObjectId::Object(materialized_view.cluster_id.into()),
691 AclMode::CREATE,
692 role_id,
693 ),
694 ],
695 item_usage: &CREATE_ITEM_USAGE,
696 ..Default::default()
697 },
698 Plan::CreateIndex(plan::CreateIndexPlan {
699 name,
700 index,
701 if_not_exists: _,
702 }) => {
703 let index_on_item = catalog.resolve_item_id(&index.on);
704 RbacRequirements {
705 ownership: vec![ObjectId::Item(index_on_item)],
706 privileges: vec![
707 (
708 SystemObjectId::Object(name.qualifiers.clone().into()),
709 AclMode::CREATE,
710 role_id,
711 ),
712 (
713 SystemObjectId::Object(index.cluster_id.into()),
714 AclMode::CREATE,
715 role_id,
716 ),
717 ],
718 item_usage: &CREATE_ITEM_USAGE,
719 ..Default::default()
720 }
721 }
722 Plan::CreateType(plan::CreateTypePlan { name, typ: _ }) => RbacRequirements {
723 privileges: vec![(
724 SystemObjectId::Object(name.qualifiers.clone().into()),
725 AclMode::CREATE,
726 role_id,
727 )],
728 item_usage: &CREATE_ITEM_USAGE,
729 ..Default::default()
730 },
731 Plan::Comment(plan::CommentPlan {
732 object_id,
733 sub_component: _,
734 comment: _,
735 }) => {
736 let (ownership, privileges) = match object_id {
737 CommentObjectId::Role(_) => (
740 Vec::new(),
741 vec![(SystemObjectId::System, AclMode::CREATE_ROLE, role_id)],
742 ),
743 _ => (vec![ObjectId::from(*object_id)], Vec::new()),
744 };
745 RbacRequirements {
746 ownership,
747 privileges,
748 ..Default::default()
749 }
750 }
751 Plan::DropObjects(plan::DropObjectsPlan {
752 referenced_ids,
753 drop_ids: _,
754 object_type,
755 }) => {
756 let privileges = if object_type == &ObjectType::Role {
757 vec![(SystemObjectId::System, AclMode::CREATE_ROLE, role_id)]
758 } else {
759 referenced_ids
760 .iter()
761 .filter_map(|id| match id {
762 ObjectId::ClusterReplica((cluster_id, _)) => Some((
763 SystemObjectId::Object(cluster_id.into()),
764 AclMode::USAGE,
765 role_id,
766 )),
767 ObjectId::Schema((database_spec, _)) => match database_spec {
768 ResolvedDatabaseSpecifier::Ambient => None,
769 ResolvedDatabaseSpecifier::Id(database_id) => Some((
770 SystemObjectId::Object(database_id.into()),
771 AclMode::USAGE,
772 role_id,
773 )),
774 },
775 ObjectId::Item(item_id) => {
776 let item = catalog.get_item(item_id);
777 Some((
778 SystemObjectId::Object(item.name().qualifiers.clone().into()),
779 AclMode::USAGE,
780 role_id,
781 ))
782 }
783 ObjectId::Cluster(_)
784 | ObjectId::Database(_)
785 | ObjectId::Role(_)
786 | ObjectId::NetworkPolicy(_) => None,
787 })
788 .collect()
789 };
790 RbacRequirements {
791 ownership: referenced_ids.clone(),
793 privileges,
794 ..Default::default()
795 }
796 }
797 Plan::DropOwned(plan::DropOwnedPlan {
798 role_ids,
799 drop_ids: _,
800 privilege_revokes: _,
801 default_privilege_revokes: _,
802 }) => RbacRequirements {
803 role_membership: role_ids.into_iter().cloned().collect(),
804 ..Default::default()
805 },
806 Plan::ShowCreate(plan::ShowCreatePlan { id, row: _ }) => {
807 let container_id = match id {
808 ObjectId::Item(id) => Some(SystemObjectId::Object(
809 catalog.get_item(id).name().qualifiers.clone().into(),
810 )),
811 ObjectId::Schema((database_id, _schema_id)) => match database_id {
812 ResolvedDatabaseSpecifier::Ambient => None,
813 ResolvedDatabaseSpecifier::Id(id) => Some(SystemObjectId::Object(id.into())),
814 },
815 ObjectId::Cluster(_)
816 | ObjectId::ClusterReplica(_)
817 | ObjectId::Database(_)
818 | ObjectId::Role(_)
819 | ObjectId::NetworkPolicy(_) => None,
820 };
821 let privileges = match container_id {
822 Some(id) => vec![(id, AclMode::USAGE, role_id)],
823 None => Vec::new(),
824 };
825 RbacRequirements {
826 privileges,
827 item_usage: &EMPTY_ITEM_USAGE,
828 ..Default::default()
829 }
830 }
831 Plan::ShowColumns(plan::ShowColumnsPlan {
832 id,
833 select_plan,
834 new_resolved_ids: _,
835 }) => {
836 let mut privileges = vec![(
837 SystemObjectId::Object(catalog.get_item(id).name().qualifiers.clone().into()),
838 AclMode::USAGE,
839 role_id,
840 )];
841
842 for privilege in generate_rbac_requirements(
843 catalog,
844 &Plan::Select(select_plan.clone()),
845 target_conn_role,
846 target_cluster_id,
847 role_id,
848 )
849 .privileges
850 {
851 privileges.push(privilege);
852 }
853 RbacRequirements {
854 privileges,
855 ..Default::default()
856 }
857 }
858 Plan::Select(plan::SelectPlan {
859 source,
860 select: _,
861 when: _,
862 finishing: _,
863 copy_to: _,
864 }) => {
865 let items = source
866 .depends_on()
867 .into_iter()
868 .map(|gid| catalog.resolve_item_id(&gid));
869 let mut privileges = generate_read_privileges(catalog, items, role_id);
870 if let Some(privilege) = generate_cluster_usage_privileges(
871 source.as_const().is_some(),
872 target_cluster_id,
873 role_id,
874 ) {
875 privileges.push(privilege);
876 }
877 RbacRequirements {
878 privileges,
879 ..Default::default()
880 }
881 }
882 Plan::Subscribe(plan::SubscribePlan {
883 from,
884 with_snapshot: _,
885 when: _,
886 up_to: _,
887 copy_to: _,
888 emit_progress: _,
889 output: _,
890 }) => {
891 let items = from
892 .depends_on()
893 .into_iter()
894 .map(|gid| catalog.resolve_item_id(&gid));
895 let mut privileges = generate_read_privileges(catalog, items, role_id);
896 if let Some(cluster_id) = target_cluster_id {
897 privileges.push((
898 SystemObjectId::Object(cluster_id.into()),
899 AclMode::USAGE,
900 role_id,
901 ));
902 }
903 RbacRequirements {
904 privileges,
905 ..Default::default()
906 }
907 }
908 Plan::CopyFrom(plan::CopyFromPlan {
909 target_name: _,
910 target_id,
911 source: _,
912 columns: _,
913 source_desc: _,
914 mfp: _,
915 params: _,
916 filter: _,
917 }) => RbacRequirements {
918 privileges: vec![
919 (
920 SystemObjectId::Object(
921 catalog.get_item(target_id).name().qualifiers.clone().into(),
922 ),
923 AclMode::USAGE,
924 role_id,
925 ),
926 (
927 SystemObjectId::Object(target_id.into()),
928 AclMode::INSERT,
929 role_id,
930 ),
931 ],
932 ..Default::default()
933 },
934 Plan::CopyTo(plan::CopyToPlan {
935 select_plan,
936 desc: _,
937 to: _,
938 connection: _,
939 connection_id: _,
940 format: _,
941 max_file_size: _,
942 }) => {
943 let items = select_plan
944 .source
945 .depends_on()
946 .into_iter()
947 .map(|gid| catalog.resolve_item_id(&gid));
948 let mut privileges = generate_read_privileges(catalog, items, role_id);
949 if let Some(cluster_id) = target_cluster_id {
950 privileges.push((
951 SystemObjectId::Object(cluster_id.into()),
952 AclMode::USAGE,
953 role_id,
954 ));
955 }
956 RbacRequirements {
957 privileges,
958 ..Default::default()
959 }
960 }
961 Plan::ExplainPlan(plan::ExplainPlanPlan {
962 stage: _,
963 format: _,
964 config: _,
965 explainee,
966 })
967 | Plan::ExplainPushdown(plan::ExplainPushdownPlan { explainee }) => RbacRequirements {
968 privileges: match explainee {
969 Explainee::View(id)
970 | Explainee::MaterializedView(id)
971 | Explainee::Index(id)
972 | Explainee::ReplanView(id)
973 | Explainee::ReplanMaterializedView(id)
974 | Explainee::ReplanIndex(id) => {
975 let item = catalog.get_item(id);
976 let schema_id: ObjectId = item.name().qualifiers.clone().into();
977 vec![(SystemObjectId::Object(schema_id), AclMode::USAGE, role_id)]
978 }
979 Explainee::Statement(stmt) => stmt
980 .depends_on()
981 .into_iter()
982 .map(|id| {
983 let item = catalog.get_item_by_global_id(&id);
984 let schema_id: ObjectId = item.name().qualifiers.clone().into();
985 (SystemObjectId::Object(schema_id), AclMode::USAGE, role_id)
986 })
987 .collect(),
988 },
989 item_usage: match explainee {
990 Explainee::View(..)
991 | Explainee::MaterializedView(..)
992 | Explainee::Index(..)
993 | Explainee::ReplanView(..)
994 | Explainee::ReplanMaterializedView(..)
995 | Explainee::ReplanIndex(..) => &EMPTY_ITEM_USAGE,
996 Explainee::Statement(_) => &DEFAULT_ITEM_USAGE,
997 },
998 ..Default::default()
999 },
1000 Plan::ExplainSinkSchema(plan::ExplainSinkSchemaPlan { sink_from, .. }) => {
1001 RbacRequirements {
1002 privileges: {
1003 let item = catalog.get_item_by_global_id(sink_from);
1004 let schema_id: ObjectId = item.name().qualifiers.clone().into();
1005 vec![(SystemObjectId::Object(schema_id), AclMode::USAGE, role_id)]
1006 },
1007 item_usage: &EMPTY_ITEM_USAGE,
1008 ..Default::default()
1009 }
1010 }
1011 Plan::ExplainTimestamp(plan::ExplainTimestampPlan {
1012 format: _,
1013 raw_plan,
1014 when: _,
1015 }) => RbacRequirements {
1016 privileges: raw_plan
1017 .depends_on()
1018 .into_iter()
1019 .map(|id| {
1020 let item = catalog.get_item_by_global_id(&id);
1021 let schema_id: ObjectId = item.name().qualifiers.clone().into();
1022 (SystemObjectId::Object(schema_id), AclMode::USAGE, role_id)
1023 })
1024 .collect(),
1025 ..Default::default()
1026 },
1027 Plan::Insert(plan::InsertPlan {
1028 id,
1029 values,
1030 returning,
1031 }) => {
1032 let schema_id: ObjectId = catalog.get_item(id).name().qualifiers.clone().into();
1033 let mut privileges = vec![
1034 (
1035 SystemObjectId::Object(schema_id.clone()),
1036 AclMode::USAGE,
1037 role_id,
1038 ),
1039 (SystemObjectId::Object(id.into()), AclMode::INSERT, role_id),
1040 ];
1041 let mut seen = BTreeSet::from([(schema_id, role_id)]);
1042
1043 if returning
1046 .iter()
1047 .any(|assignment| assignment.contains_column())
1048 {
1049 privileges.push((SystemObjectId::Object(id.into()), AclMode::SELECT, role_id));
1050 seen.insert((id.into(), role_id));
1051 }
1052
1053 let items = values
1054 .depends_on()
1055 .into_iter()
1056 .map(|gid| catalog.resolve_item_id(&gid));
1057 privileges.extend_from_slice(&generate_read_privileges_inner(
1058 catalog, items, role_id, &mut seen,
1059 ));
1060
1061 if let Some(privilege) = generate_cluster_usage_privileges(
1062 values.as_const().is_some(),
1063 target_cluster_id,
1064 role_id,
1065 ) {
1066 privileges.push(privilege);
1067 } else if !returning.is_empty() {
1068 if let Some(cluster_id) = target_cluster_id {
1071 privileges.push((
1072 SystemObjectId::Object(cluster_id.into()),
1073 AclMode::USAGE,
1074 role_id,
1075 ));
1076 }
1077 }
1078 RbacRequirements {
1079 privileges,
1080 ..Default::default()
1081 }
1082 }
1083 Plan::AlterCluster(plan::AlterClusterPlan {
1084 id,
1085 name: _,
1086 options: _,
1087 strategy: _,
1088 }) => RbacRequirements {
1089 ownership: vec![ObjectId::Cluster(*id)],
1090 item_usage: &CREATE_ITEM_USAGE,
1091 ..Default::default()
1092 },
1093 Plan::AlterSetCluster(plan::AlterSetClusterPlan { id, set_cluster }) => RbacRequirements {
1094 ownership: vec![ObjectId::Item(*id)],
1095 privileges: vec![(
1096 SystemObjectId::Object(set_cluster.into()),
1097 AclMode::CREATE,
1098 role_id,
1099 )],
1100 item_usage: &CREATE_ITEM_USAGE,
1101 ..Default::default()
1102 },
1103 Plan::AlterRetainHistory(plan::AlterRetainHistoryPlan {
1104 id,
1105 window: _,
1106 value: _,
1107 object_type: _,
1108 }) => RbacRequirements {
1109 ownership: vec![ObjectId::Item(*id)],
1110 item_usage: &CREATE_ITEM_USAGE,
1111 ..Default::default()
1112 },
1113 Plan::AlterSourceTimestampInterval(plan::AlterSourceTimestampIntervalPlan {
1114 id,
1115 value: _,
1116 interval: _,
1117 }) => RbacRequirements {
1118 ownership: vec![ObjectId::Item(*id)],
1119 item_usage: &CREATE_ITEM_USAGE,
1120 ..Default::default()
1121 },
1122 Plan::AlterConnection(plan::AlterConnectionPlan { id, action: _ }) => RbacRequirements {
1123 ownership: vec![ObjectId::Item(*id)],
1124 ..Default::default()
1125 },
1126 Plan::AlterSource(plan::AlterSourcePlan {
1127 item_id,
1128 ingestion_id: _,
1129 action: _,
1130 }) => RbacRequirements {
1131 ownership: vec![ObjectId::Item(*item_id)],
1132 item_usage: &CREATE_ITEM_USAGE,
1133 ..Default::default()
1134 },
1135 Plan::AlterSink(plan::AlterSinkPlan {
1136 item_id,
1137 global_id: _,
1138 sink,
1139 with_snapshot: _,
1140 in_cluster,
1141 set_options: _,
1142 reset_options: _,
1143 }) => {
1144 let items = iter::once(sink.from).map(|gid| catalog.resolve_item_id(&gid));
1145 let mut privileges = generate_read_privileges(catalog, items, role_id);
1146 privileges.push((
1147 SystemObjectId::Object(in_cluster.into()),
1148 AclMode::CREATE,
1149 role_id,
1150 ));
1151 RbacRequirements {
1152 ownership: vec![ObjectId::Item(*item_id)],
1153 privileges,
1154 item_usage: &CREATE_ITEM_USAGE,
1155 ..Default::default()
1156 }
1157 }
1158 Plan::AlterClusterRename(plan::AlterClusterRenamePlan {
1159 id,
1160 name: _,
1161 to_name: _,
1162 }) => RbacRequirements {
1163 ownership: vec![ObjectId::Cluster(*id)],
1164 ..Default::default()
1165 },
1166 Plan::AlterClusterSwap(plan::AlterClusterSwapPlan {
1167 id_a,
1168 id_b,
1169 name_a: _,
1170 name_b: _,
1171 name_temp: _,
1172 }) => RbacRequirements {
1173 ownership: vec![ObjectId::Cluster(*id_a), ObjectId::Cluster(*id_b)],
1174 ..Default::default()
1175 },
1176 Plan::AlterClusterReplicaRename(plan::AlterClusterReplicaRenamePlan {
1177 cluster_id,
1178 replica_id,
1179 name: _,
1180 to_name: _,
1181 }) => RbacRequirements {
1182 ownership: vec![ObjectId::ClusterReplica((*cluster_id, *replica_id))],
1183 ..Default::default()
1184 },
1185 Plan::AlterItemRename(plan::AlterItemRenamePlan {
1186 id,
1187 current_full_name: _,
1188 to_name: _,
1189 object_type: _,
1190 }) => RbacRequirements {
1191 ownership: vec![ObjectId::Item(*id)],
1192 ..Default::default()
1193 },
1194 Plan::AlterSchemaRename(plan::AlterSchemaRenamePlan {
1195 cur_schema_spec,
1196 new_schema_name: _,
1197 }) => {
1198 let privileges = match cur_schema_spec.0 {
1199 ResolvedDatabaseSpecifier::Id(db_id) => vec![(
1200 SystemObjectId::Object(ObjectId::Database(db_id)),
1201 AclMode::CREATE,
1202 role_id,
1203 )],
1204 ResolvedDatabaseSpecifier::Ambient => vec![],
1205 };
1206
1207 RbacRequirements {
1208 ownership: vec![ObjectId::Schema(*cur_schema_spec)],
1209 privileges,
1210 ..Default::default()
1211 }
1212 }
1213 Plan::AlterSchemaSwap(plan::AlterSchemaSwapPlan {
1214 schema_a_spec,
1215 schema_a_name: _,
1216 schema_b_spec,
1217 schema_b_name: _,
1218 name_temp: _,
1219 }) => {
1220 let mut privileges = vec![];
1221 if let ResolvedDatabaseSpecifier::Id(id_a) = schema_a_spec.0 {
1222 privileges.push((
1223 SystemObjectId::Object(ObjectId::Database(id_a)),
1224 AclMode::CREATE,
1225 role_id,
1226 ));
1227 }
1228 if let ResolvedDatabaseSpecifier::Id(id_b) = schema_b_spec.0 {
1229 privileges.push((
1230 SystemObjectId::Object(ObjectId::Database(id_b)),
1231 AclMode::CREATE,
1232 role_id,
1233 ));
1234 }
1235
1236 RbacRequirements {
1237 ownership: vec![
1238 ObjectId::Schema(*schema_a_spec),
1239 ObjectId::Schema(*schema_b_spec),
1240 ],
1241 privileges,
1242 ..Default::default()
1243 }
1244 }
1245 Plan::AlterSecret(plan::AlterSecretPlan { id, secret_as: _ }) => RbacRequirements {
1246 ownership: vec![ObjectId::Item(*id)],
1247 item_usage: &CREATE_ITEM_USAGE,
1248 ..Default::default()
1249 },
1250 Plan::AlterRole(plan::AlterRolePlan {
1251 id,
1252 name: _,
1253 option,
1254 }) => match option {
1255 plan::PlannedAlterRoleOption::Attributes(attributes)
1257 if attributes.superuser.is_some() =>
1258 {
1259 RbacRequirements {
1260 superuser_action: Some("alter superuser role".to_string()),
1261 ..Default::default()
1262 }
1263 }
1264 plan::PlannedAlterRoleOption::Attributes(plan::PlannedRoleAttributes {
1267 password,
1268 scram_iterations: _,
1271 nopassword: _,
1272 superuser: None,
1275 inherit: None,
1276 login: None,
1277 }) if password.is_some() && role_id == *id => RbacRequirements::default(),
1278 plan::PlannedAlterRoleOption::Attributes(attributes)
1280 if attributes.password.is_some() && role_id != *id =>
1281 {
1282 RbacRequirements {
1283 superuser_action: Some("alter password of role".to_string()),
1284 ..Default::default()
1285 }
1286 }
1287 plan::PlannedAlterRoleOption::Variable(var)
1292 if var.name().eq_ignore_ascii_case("restrict_to_user_objects") =>
1293 {
1294 RbacRequirements {
1295 superuser_action: Some("set restrict_to_user_objects".to_string()),
1296 ..Default::default()
1297 }
1298 }
1299 plan::PlannedAlterRoleOption::Variable(_) if role_id == *id => {
1301 RbacRequirements::default()
1302 }
1303 _ => RbacRequirements {
1305 privileges: vec![(SystemObjectId::System, AclMode::CREATE_ROLE, role_id)],
1306 item_usage: &CREATE_ITEM_USAGE,
1307 ..Default::default()
1308 },
1309 },
1310 Plan::AlterOwner(plan::AlterOwnerPlan {
1311 id,
1312 object_type: _,
1313 new_owner,
1314 }) => {
1315 let privileges = match id {
1316 ObjectId::ClusterReplica((cluster_id, _)) => {
1317 vec![(
1318 SystemObjectId::Object(cluster_id.into()),
1319 AclMode::CREATE,
1320 role_id,
1321 )]
1322 }
1323 ObjectId::Schema((database_spec, _)) => match database_spec {
1324 ResolvedDatabaseSpecifier::Ambient => Vec::new(),
1325 ResolvedDatabaseSpecifier::Id(database_id) => {
1326 vec![(
1327 SystemObjectId::Object(database_id.into()),
1328 AclMode::CREATE,
1329 role_id,
1330 )]
1331 }
1332 },
1333 ObjectId::Item(item_id) => {
1334 let item = catalog.get_item(item_id);
1335 vec![(
1336 SystemObjectId::Object(item.name().qualifiers.clone().into()),
1337 AclMode::CREATE,
1338 role_id,
1339 )]
1340 }
1341 ObjectId::Cluster(_)
1342 | ObjectId::Database(_)
1343 | ObjectId::Role(_)
1344 | ObjectId::NetworkPolicy(_) => Vec::new(),
1345 };
1346 RbacRequirements {
1347 role_membership: BTreeSet::from([*new_owner]),
1348 ownership: vec![id.clone()],
1349 privileges,
1350 ..Default::default()
1351 }
1352 }
1353 Plan::AlterTableAddColumn(plan::AlterTablePlan { relation_id, .. }) => RbacRequirements {
1354 ownership: vec![ObjectId::Item(*relation_id)],
1355 item_usage: &CREATE_ITEM_USAGE,
1356 ..Default::default()
1357 },
1358 Plan::AlterMaterializedViewApplyReplacement(
1359 plan::AlterMaterializedViewApplyReplacementPlan { id, replacement_id },
1360 ) => RbacRequirements {
1361 ownership: vec![ObjectId::Item(*id), ObjectId::Item(*replacement_id)],
1362 item_usage: &CREATE_ITEM_USAGE,
1363 ..Default::default()
1364 },
1365 Plan::AlterNetworkPolicy(plan::AlterNetworkPolicyPlan { id, .. }) => RbacRequirements {
1366 ownership: vec![ObjectId::NetworkPolicy(*id)],
1367 item_usage: &CREATE_ITEM_USAGE,
1368 ..Default::default()
1369 },
1370 Plan::ReadThenWrite(plan::ReadThenWritePlan {
1371 id,
1372 selection,
1373 finishing: _,
1374 assignments,
1375 kind,
1376 returning,
1377 }) => {
1378 let acl_mode = match kind {
1379 MutationKind::Insert => AclMode::INSERT,
1380 MutationKind::Update => AclMode::UPDATE,
1381 MutationKind::Delete => AclMode::DELETE,
1382 };
1383 let schema_id: ObjectId = catalog.get_item(id).name().qualifiers.clone().into();
1384 let mut privileges = vec![
1385 (
1386 SystemObjectId::Object(schema_id.clone()),
1387 AclMode::USAGE,
1388 role_id,
1389 ),
1390 (SystemObjectId::Object(id.into()), acl_mode, role_id),
1391 ];
1392 let mut seen = BTreeSet::from([(schema_id, role_id)]);
1393
1394 if assignments
1397 .values()
1398 .chain(returning.iter())
1399 .any(|assignment| assignment.contains_column())
1400 {
1401 privileges.push((SystemObjectId::Object(id.into()), AclMode::SELECT, role_id));
1402 seen.insert((id.into(), role_id));
1403 }
1404
1405 let items = selection
1412 .depends_on()
1413 .into_iter()
1414 .map(|gid| catalog.resolve_item_id(&gid));
1415 privileges.extend_from_slice(&generate_read_privileges_inner(
1416 catalog, items, role_id, &mut seen,
1417 ));
1418
1419 if let Some(privilege) = generate_cluster_usage_privileges(
1420 selection.as_const().is_some(),
1421 target_cluster_id,
1422 role_id,
1423 ) {
1424 privileges.push(privilege);
1425 }
1426 RbacRequirements {
1427 privileges,
1428 ..Default::default()
1429 }
1430 }
1431 Plan::GrantRole(plan::GrantRolePlan {
1432 role_ids: _,
1433 member_ids: _,
1434 grantor_id: _,
1435 })
1436 | Plan::RevokeRole(plan::RevokeRolePlan {
1437 role_ids: _,
1438 member_ids: _,
1439 grantor_id: _,
1440 }) => RbacRequirements {
1441 privileges: vec![(SystemObjectId::System, AclMode::CREATE_ROLE, role_id)],
1442 ..Default::default()
1443 },
1444 Plan::GrantPrivileges(plan::GrantPrivilegesPlan {
1445 update_privileges,
1446 grantees: _,
1447 })
1448 | Plan::RevokePrivileges(plan::RevokePrivilegesPlan {
1449 update_privileges,
1450 revokees: _,
1451 }) => {
1452 let mut privileges = Vec::with_capacity(update_privileges.len());
1453 for UpdatePrivilege { target_id, .. } in update_privileges {
1454 match target_id {
1455 SystemObjectId::Object(object_id) => match object_id {
1456 ObjectId::ClusterReplica((cluster_id, _)) => {
1457 privileges.push((
1458 SystemObjectId::Object(cluster_id.into()),
1459 AclMode::USAGE,
1460 role_id,
1461 ));
1462 }
1463 ObjectId::Schema((database_spec, _)) => match database_spec {
1464 ResolvedDatabaseSpecifier::Ambient => {}
1465 ResolvedDatabaseSpecifier::Id(database_id) => {
1466 privileges.push((
1467 SystemObjectId::Object(database_id.into()),
1468 AclMode::USAGE,
1469 role_id,
1470 ));
1471 }
1472 },
1473 ObjectId::Item(item_id) => {
1474 let item = catalog.get_item(item_id);
1475 privileges.push((
1476 SystemObjectId::Object(item.name().qualifiers.clone().into()),
1477 AclMode::USAGE,
1478 role_id,
1479 ))
1480 }
1481 ObjectId::Cluster(_)
1482 | ObjectId::Database(_)
1483 | ObjectId::Role(_)
1484 | ObjectId::NetworkPolicy(_) => {}
1485 },
1486 SystemObjectId::System => {}
1487 }
1488 }
1489 RbacRequirements {
1490 ownership: update_privileges
1491 .iter()
1492 .filter_map(|update_privilege| update_privilege.target_id.object_id())
1493 .cloned()
1494 .collect(),
1495 privileges,
1496 superuser_action: if update_privileges
1501 .iter()
1502 .any(|update_privilege| update_privilege.target_id.is_system())
1503 {
1504 Some("GRANT/REVOKE SYSTEM PRIVILEGES".to_string())
1505 } else {
1506 None
1507 },
1508 ..Default::default()
1509 }
1510 }
1511 Plan::AlterDefaultPrivileges(plan::AlterDefaultPrivilegesPlan {
1512 privilege_objects,
1513 privilege_acl_items: _,
1514 is_grant: _,
1515 }) => RbacRequirements {
1516 role_membership: privilege_objects
1517 .iter()
1518 .map(|privilege_object| privilege_object.role_id)
1519 .collect(),
1520 privileges: privilege_objects
1521 .into_iter()
1522 .filter_map(|privilege_object| {
1523 if let (Some(database_id), Some(_)) =
1524 (privilege_object.database_id, privilege_object.schema_id)
1525 {
1526 Some((
1527 SystemObjectId::Object(database_id.into()),
1528 AclMode::USAGE,
1529 role_id,
1530 ))
1531 } else {
1532 None
1533 }
1534 })
1535 .collect(),
1536 superuser_action: if privilege_objects
1542 .iter()
1543 .any(|privilege_object| privilege_object.role_id.is_public())
1544 {
1545 Some("ALTER DEFAULT PRIVILEGES FOR ALL ROLES".to_string())
1546 } else {
1547 None
1548 },
1549 ..Default::default()
1550 },
1551 Plan::ReassignOwned(plan::ReassignOwnedPlan {
1552 old_roles,
1553 new_role,
1554 reassign_ids: _,
1555 }) => RbacRequirements {
1556 role_membership: old_roles
1557 .into_iter()
1558 .cloned()
1559 .chain(iter::once(*new_role))
1560 .collect(),
1561 ..Default::default()
1562 },
1563 Plan::SideEffectingFunc(func) => {
1564 let role_membership = match func {
1565 SideEffectingFunc::PgCancelBackend {
1568 connection_id: None,
1569 } => BTreeSet::new(),
1570 SideEffectingFunc::PgCancelBackend {
1571 connection_id: Some(_),
1572 } => target_conn_role.map(|x| [x].into()).unwrap_or_default(),
1573 };
1574 RbacRequirements {
1575 role_membership,
1576 ..Default::default()
1577 }
1578 }
1579 Plan::ValidateConnection(plan::ValidateConnectionPlan { id, connection: _ }) => {
1580 let schema_id: ObjectId = catalog.get_item(id).name().qualifiers.clone().into();
1581 RbacRequirements {
1582 privileges: vec![
1583 (SystemObjectId::Object(schema_id), AclMode::USAGE, role_id),
1584 (SystemObjectId::Object(id.into()), AclMode::USAGE, role_id),
1585 ],
1586 ..Default::default()
1587 }
1588 }
1589 Plan::DiscardTemp
1590 | Plan::DiscardAll
1591 | Plan::EmptyQuery
1592 | Plan::ShowAllVariables
1593 | Plan::ShowVariable(plan::ShowVariablePlan { name: _ })
1594 | Plan::InspectShard(plan::InspectShardPlan { id: _ })
1595 | Plan::SetVariable(plan::SetVariablePlan {
1596 name: _,
1597 value: _,
1598 local: _,
1599 })
1600 | Plan::ResetVariable(plan::ResetVariablePlan { name: _ })
1601 | Plan::SetTransaction(plan::SetTransactionPlan { local: _, modes: _ })
1602 | Plan::StartTransaction(plan::StartTransactionPlan {
1603 access: _,
1604 isolation_level: _,
1605 })
1606 | Plan::CommitTransaction(plan::CommitTransactionPlan {
1607 transaction_type: _,
1608 })
1609 | Plan::AbortTransaction(plan::AbortTransactionPlan {
1610 transaction_type: _,
1611 })
1612 | Plan::AlterNoop(plan::AlterNoopPlan { object_type: _ })
1613 | Plan::AlterSystemSet(plan::AlterSystemSetPlan { name: _, value: _ })
1614 | Plan::AlterSystemReset(plan::AlterSystemResetPlan { name: _ })
1615 | Plan::AlterSystemResetAll(plan::AlterSystemResetAllPlan {})
1616 | Plan::Declare(plan::DeclarePlan {
1617 name: _,
1618 stmt: _,
1619 sql: _,
1620 params: _,
1621 })
1622 | Plan::Fetch(plan::FetchPlan {
1623 name: _,
1624 count: _,
1625 timeout: _,
1626 })
1627 | Plan::Close(plan::ClosePlan { name: _ })
1628 | Plan::Prepare(plan::PreparePlan {
1629 name: _,
1630 stmt: _,
1631 desc: _,
1632 sql: _,
1633 })
1634 | Plan::Execute(plan::ExecutePlan { name: _, params: _ })
1635 | Plan::Deallocate(plan::DeallocatePlan { name: _ })
1636 | Plan::Raise(plan::RaisePlan { severity: _ }) => Default::default(),
1637 }
1638}
1639
1640fn check_owner_roles(
1642 object_id: &ObjectId,
1643 role_ids: &BTreeSet<RoleId>,
1644 catalog: &impl SessionCatalog,
1645) -> bool {
1646 if let Some(owner_id) = catalog.get_owner_id(object_id) {
1647 role_ids.contains(&owner_id)
1648 } else {
1649 true
1650 }
1651}
1652
1653fn ownership_err(
1654 unheld_ownership: Vec<ObjectId>,
1655 catalog: &impl SessionCatalog,
1656) -> Result<(), UnauthorizedError> {
1657 if !unheld_ownership.is_empty() {
1658 let objects = unheld_ownership
1659 .into_iter()
1660 .map(|ownership| match ownership {
1661 ObjectId::Cluster(id) => (
1662 ObjectType::Cluster,
1663 catalog.get_cluster(id).name().to_string(),
1664 ),
1665 ObjectId::ClusterReplica((cluster_id, replica_id)) => {
1666 let cluster = catalog.get_cluster(cluster_id);
1667 let replica = catalog.get_cluster_replica(cluster_id, replica_id);
1668 let name = QualifiedReplica {
1671 cluster: Ident::new_unchecked(cluster.name()),
1672 replica: Ident::new_unchecked(replica.name()),
1673 };
1674 (ObjectType::ClusterReplica, name.to_string())
1675 }
1676 ObjectId::Database(id) => (
1677 ObjectType::Database,
1678 catalog.get_database(&id).name().to_string(),
1679 ),
1680 ObjectId::Schema((database_spec, schema_spec)) => {
1681 let schema = catalog.get_schema(&database_spec, &schema_spec);
1682 let name = catalog.resolve_full_schema_name(schema.name());
1683 (ObjectType::Schema, name.to_string())
1684 }
1685 ObjectId::Item(id) => {
1686 let item = catalog.get_item(&id);
1687 let name = catalog.resolve_full_name(item.name());
1688 (item.item_type().into(), name.to_string())
1689 }
1690 ObjectId::NetworkPolicy(id) => (
1691 ObjectType::NetworkPolicy,
1692 catalog.get_network_policy(&id).name().to_string(),
1693 ),
1694 ObjectId::Role(_) => unreachable!("roles have no owner"),
1695 })
1696 .collect();
1697 Err(UnauthorizedError::Ownership { objects })
1698 } else {
1699 Ok(())
1700 }
1701}
1702
1703fn generate_required_source_privileges(
1704 name: &QualifiedItemName,
1705 data_source: &DataSourceDesc,
1706 in_cluster: Option<ClusterId>,
1707 role_id: RoleId,
1708) -> Vec<(SystemObjectId, AclMode, RoleId)> {
1709 let mut privileges = vec![(
1710 SystemObjectId::Object(name.qualifiers.clone().into()),
1711 AclMode::CREATE,
1712 role_id,
1713 )];
1714 match (data_source, in_cluster) {
1715 (_, Some(id)) => {
1716 privileges.push((SystemObjectId::Object(id.into()), AclMode::CREATE, role_id))
1717 }
1718 (DataSourceDesc::Ingestion(_), None) => {
1719 privileges.push((SystemObjectId::System, AclMode::CREATE_CLUSTER, role_id))
1720 }
1721 (_, None) => {}
1726 }
1727 privileges
1728}
1729
1730fn generate_read_privileges(
1738 catalog: &impl SessionCatalog,
1739 ids: impl Iterator<Item = CatalogItemId>,
1740 role_id: RoleId,
1741) -> Vec<(SystemObjectId, AclMode, RoleId)> {
1742 generate_read_privileges_inner(catalog, ids, role_id, &mut BTreeSet::new())
1743}
1744
1745fn generate_read_privileges_inner(
1746 catalog: &impl SessionCatalog,
1747 ids: impl Iterator<Item = CatalogItemId>,
1748 role_id: RoleId,
1749 seen: &mut BTreeSet<(ObjectId, RoleId)>,
1750) -> Vec<(SystemObjectId, AclMode, RoleId)> {
1751 let mut privileges = Vec::new();
1752
1753 let mut queue: VecDeque<(CatalogItemId, RoleId)> = ids.map(|id| (id, role_id)).collect();
1756 while let Some((id, role_id)) = queue.pop_front() {
1757 if seen.insert((id.into(), role_id)) {
1758 let item = catalog.get_item(&id);
1759 let schema_id: ObjectId = item.name().qualifiers.clone().into();
1760 if seen.insert((schema_id.clone(), role_id)) {
1761 privileges.push((SystemObjectId::Object(schema_id), AclMode::USAGE, role_id))
1762 }
1763 match item.item_type() {
1764 CatalogItemType::View | CatalogItemType::MaterializedView => {
1765 privileges.push((SystemObjectId::Object(id.into()), AclMode::SELECT, role_id));
1766 let view_owner = item.owner_id();
1767 queue.extend(item.references().items().map(|id| (*id, view_owner)));
1768 }
1769 CatalogItemType::Table | CatalogItemType::Source => {
1770 privileges.push((SystemObjectId::Object(id.into()), AclMode::SELECT, role_id));
1771 }
1772 CatalogItemType::Type | CatalogItemType::Secret | CatalogItemType::Connection => {
1773 privileges.push((SystemObjectId::Object(id.into()), AclMode::USAGE, role_id));
1774 }
1775 CatalogItemType::Sink | CatalogItemType::Index | CatalogItemType::Func => {}
1776 }
1777 }
1778 }
1779
1780 privileges
1781}
1782
1783fn generate_usage_privileges(
1784 catalog: &impl SessionCatalog,
1785 ids: &ResolvedIds,
1786 role_id: RoleId,
1787 item_types: &BTreeSet<CatalogItemType>,
1788) -> BTreeSet<(SystemObjectId, AclMode, RoleId)> {
1789 ids.items()
1791 .filter_map(move |id| {
1792 let item = catalog.get_item(id);
1793 if item_types.contains(&item.item_type()) {
1794 let schema_id = item.name().qualifiers.clone().into();
1795 Some([
1796 (SystemObjectId::Object(schema_id), AclMode::USAGE, role_id),
1797 (SystemObjectId::Object(id.into()), AclMode::USAGE, role_id),
1798 ])
1799 } else {
1800 None
1801 }
1802 })
1803 .flatten()
1804 .collect()
1805}
1806
1807fn generate_cluster_usage_privileges(
1808 expr_is_const: bool,
1809 target_cluster_id: Option<ClusterId>,
1810 role_id: RoleId,
1811) -> Option<(SystemObjectId, AclMode, RoleId)> {
1812 if !expr_is_const {
1815 if let Some(cluster_id) = target_cluster_id {
1816 return Some((
1817 SystemObjectId::Object(cluster_id.into()),
1818 AclMode::USAGE,
1819 role_id,
1820 ));
1821 }
1822 }
1823
1824 None
1825}
1826
1827fn check_object_privileges(
1828 catalog: &impl SessionCatalog,
1829 privileges: Vec<(SystemObjectId, AclMode, RoleId)>,
1830 role_membership: BTreeSet<RoleId>,
1831 current_role_id: RoleId,
1832) -> Result<(), UnauthorizedError> {
1833 let mut role_memberships: BTreeMap<RoleId, BTreeSet<RoleId>> = BTreeMap::new();
1834 role_memberships.insert(current_role_id, role_membership);
1835 for (object_id, acl_mode, role_id) in privileges {
1836 if matches!(
1840 &object_id,
1841 SystemObjectId::Object(ObjectId::Schema((_, SchemaSpecifier::Temporary)))
1842 ) {
1843 continue;
1844 }
1845
1846 let role_membership = role_memberships
1847 .entry(role_id)
1848 .or_insert_with_key(|role_id| catalog.collect_role_membership(role_id));
1849 let object_privileges = catalog
1850 .get_privileges(&object_id)
1851 .expect("only object types with privileges will generate required privileges");
1852 let role_privileges = role_membership
1853 .iter()
1854 .flat_map(|role_id| object_privileges.get_acl_items_for_grantee(role_id))
1855 .map(|mz_acl_item| mz_acl_item.acl_mode)
1856 .fold(AclMode::empty(), |accum, acl_mode| accum.union(acl_mode));
1857 if !role_privileges.contains(acl_mode) {
1858 let role_name = catalog.get_role(&role_id).name().to_string();
1859 let privileges = acl_mode.to_error_string();
1860 return Err(UnauthorizedError::Privilege {
1861 object_description: ErrorMessageObjectDescription::from_sys_id(&object_id, catalog),
1862 role_name,
1863 privileges,
1864 });
1865 }
1866 }
1867
1868 Ok(())
1869}
1870
1871pub const fn all_object_privileges(object_type: SystemObjectType) -> AclMode {
1872 const TABLE_ACL_MODE: AclMode = AclMode::INSERT
1873 .union(AclMode::SELECT)
1874 .union(AclMode::UPDATE)
1875 .union(AclMode::DELETE);
1876 const USAGE_CREATE_ACL_MODE: AclMode = AclMode::USAGE.union(AclMode::CREATE);
1877 const ALL_SYSTEM_PRIVILEGES: AclMode = AclMode::CREATE_ROLE
1878 .union(AclMode::CREATE_DB)
1879 .union(AclMode::CREATE_CLUSTER)
1880 .union(AclMode::CREATE_NETWORK_POLICY);
1881
1882 const EMPTY_ACL_MODE: AclMode = AclMode::empty();
1883 match object_type {
1884 SystemObjectType::Object(ObjectType::Table) => TABLE_ACL_MODE,
1885 SystemObjectType::Object(ObjectType::View) => AclMode::SELECT,
1886 SystemObjectType::Object(ObjectType::MaterializedView) => AclMode::SELECT,
1887 SystemObjectType::Object(ObjectType::Source) => AclMode::SELECT,
1888 SystemObjectType::Object(ObjectType::Sink) => EMPTY_ACL_MODE,
1889 SystemObjectType::Object(ObjectType::Index) => EMPTY_ACL_MODE,
1890 SystemObjectType::Object(ObjectType::Type) => AclMode::USAGE,
1891 SystemObjectType::Object(ObjectType::Role) => EMPTY_ACL_MODE,
1892 SystemObjectType::Object(ObjectType::Cluster) => USAGE_CREATE_ACL_MODE,
1893 SystemObjectType::Object(ObjectType::ClusterReplica) => EMPTY_ACL_MODE,
1894 SystemObjectType::Object(ObjectType::Secret) => AclMode::USAGE,
1895 SystemObjectType::Object(ObjectType::NetworkPolicy) => AclMode::USAGE,
1896 SystemObjectType::Object(ObjectType::Connection) => AclMode::USAGE,
1897 SystemObjectType::Object(ObjectType::Database) => USAGE_CREATE_ACL_MODE,
1898 SystemObjectType::Object(ObjectType::Schema) => USAGE_CREATE_ACL_MODE,
1899 SystemObjectType::Object(ObjectType::Func) => EMPTY_ACL_MODE,
1900 SystemObjectType::System => ALL_SYSTEM_PRIVILEGES,
1901 }
1902}
1903
1904pub const fn owner_privilege(object_type: ObjectType, owner_id: RoleId) -> MzAclItem {
1905 MzAclItem {
1906 grantee: owner_id,
1907 grantor: owner_id,
1908 acl_mode: all_object_privileges(SystemObjectType::Object(object_type)),
1909 }
1910}
1911
1912const fn default_builtin_object_acl_mode(object_type: ObjectType) -> AclMode {
1913 match object_type {
1914 ObjectType::Table
1915 | ObjectType::View
1916 | ObjectType::MaterializedView
1917 | ObjectType::Source => AclMode::SELECT,
1918 ObjectType::Type | ObjectType::Schema => AclMode::USAGE,
1919 ObjectType::Sink
1920 | ObjectType::Index
1921 | ObjectType::Role
1922 | ObjectType::Cluster
1923 | ObjectType::ClusterReplica
1924 | ObjectType::Secret
1925 | ObjectType::Connection
1926 | ObjectType::Database
1927 | ObjectType::Func
1928 | ObjectType::NetworkPolicy => AclMode::empty(),
1929 }
1930}
1931
1932pub const fn support_builtin_object_privilege(object_type: ObjectType) -> MzAclItem {
1933 let acl_mode = default_builtin_object_acl_mode(object_type);
1934 MzAclItem {
1935 grantee: MZ_SUPPORT_ROLE_ID,
1936 grantor: MZ_SYSTEM_ROLE_ID,
1937 acl_mode,
1938 }
1939}
1940
1941pub const fn default_builtin_object_privilege(object_type: ObjectType) -> MzAclItem {
1942 let acl_mode = default_builtin_object_acl_mode(object_type);
1943 MzAclItem {
1944 grantee: RoleId::Public,
1945 grantor: MZ_SYSTEM_ROLE_ID,
1946 acl_mode,
1947 }
1948}