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