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 }) => {
1141 let items = iter::once(sink.from).map(|gid| catalog.resolve_item_id(&gid));
1142 let mut privileges = generate_read_privileges(catalog, items, role_id);
1143 privileges.push((
1144 SystemObjectId::Object(in_cluster.into()),
1145 AclMode::CREATE,
1146 role_id,
1147 ));
1148 RbacRequirements {
1149 ownership: vec![ObjectId::Item(*item_id)],
1150 privileges,
1151 item_usage: &CREATE_ITEM_USAGE,
1152 ..Default::default()
1153 }
1154 }
1155 Plan::AlterClusterRename(plan::AlterClusterRenamePlan {
1156 id,
1157 name: _,
1158 to_name: _,
1159 }) => RbacRequirements {
1160 ownership: vec![ObjectId::Cluster(*id)],
1161 ..Default::default()
1162 },
1163 Plan::AlterClusterSwap(plan::AlterClusterSwapPlan {
1164 id_a,
1165 id_b,
1166 name_a: _,
1167 name_b: _,
1168 name_temp: _,
1169 }) => RbacRequirements {
1170 ownership: vec![ObjectId::Cluster(*id_a), ObjectId::Cluster(*id_b)],
1171 ..Default::default()
1172 },
1173 Plan::AlterClusterReplicaRename(plan::AlterClusterReplicaRenamePlan {
1174 cluster_id,
1175 replica_id,
1176 name: _,
1177 to_name: _,
1178 }) => RbacRequirements {
1179 ownership: vec![ObjectId::ClusterReplica((*cluster_id, *replica_id))],
1180 ..Default::default()
1181 },
1182 Plan::AlterItemRename(plan::AlterItemRenamePlan {
1183 id,
1184 current_full_name: _,
1185 to_name: _,
1186 object_type: _,
1187 }) => RbacRequirements {
1188 ownership: vec![ObjectId::Item(*id)],
1189 ..Default::default()
1190 },
1191 Plan::AlterSchemaRename(plan::AlterSchemaRenamePlan {
1192 cur_schema_spec,
1193 new_schema_name: _,
1194 }) => {
1195 let privileges = match cur_schema_spec.0 {
1196 ResolvedDatabaseSpecifier::Id(db_id) => vec![(
1197 SystemObjectId::Object(ObjectId::Database(db_id)),
1198 AclMode::CREATE,
1199 role_id,
1200 )],
1201 ResolvedDatabaseSpecifier::Ambient => vec![],
1202 };
1203
1204 RbacRequirements {
1205 ownership: vec![ObjectId::Schema(*cur_schema_spec)],
1206 privileges,
1207 ..Default::default()
1208 }
1209 }
1210 Plan::AlterSchemaSwap(plan::AlterSchemaSwapPlan {
1211 schema_a_spec,
1212 schema_a_name: _,
1213 schema_b_spec,
1214 schema_b_name: _,
1215 name_temp: _,
1216 }) => {
1217 let mut privileges = vec![];
1218 if let ResolvedDatabaseSpecifier::Id(id_a) = schema_a_spec.0 {
1219 privileges.push((
1220 SystemObjectId::Object(ObjectId::Database(id_a)),
1221 AclMode::CREATE,
1222 role_id,
1223 ));
1224 }
1225 if let ResolvedDatabaseSpecifier::Id(id_b) = schema_b_spec.0 {
1226 privileges.push((
1227 SystemObjectId::Object(ObjectId::Database(id_b)),
1228 AclMode::CREATE,
1229 role_id,
1230 ));
1231 }
1232
1233 RbacRequirements {
1234 ownership: vec![
1235 ObjectId::Schema(*schema_a_spec),
1236 ObjectId::Schema(*schema_b_spec),
1237 ],
1238 privileges,
1239 ..Default::default()
1240 }
1241 }
1242 Plan::AlterSecret(plan::AlterSecretPlan { id, secret_as: _ }) => RbacRequirements {
1243 ownership: vec![ObjectId::Item(*id)],
1244 item_usage: &CREATE_ITEM_USAGE,
1245 ..Default::default()
1246 },
1247 Plan::AlterRole(plan::AlterRolePlan {
1248 id,
1249 name: _,
1250 option,
1251 }) => match option {
1252 plan::PlannedAlterRoleOption::Attributes(attributes)
1254 if attributes.superuser.is_some() =>
1255 {
1256 RbacRequirements {
1257 superuser_action: Some("alter superuser role".to_string()),
1258 ..Default::default()
1259 }
1260 }
1261 plan::PlannedAlterRoleOption::Attributes(plan::PlannedRoleAttributes {
1264 password,
1265 scram_iterations: _,
1268 nopassword: _,
1269 superuser: None,
1272 inherit: None,
1273 login: None,
1274 }) if password.is_some() && role_id == *id => RbacRequirements::default(),
1275 plan::PlannedAlterRoleOption::Attributes(attributes)
1277 if attributes.password.is_some() && role_id != *id =>
1278 {
1279 RbacRequirements {
1280 superuser_action: Some("alter password of role".to_string()),
1281 ..Default::default()
1282 }
1283 }
1284 plan::PlannedAlterRoleOption::Variable(var)
1289 if var.name().eq_ignore_ascii_case("restrict_to_user_objects") =>
1290 {
1291 RbacRequirements {
1292 superuser_action: Some("set restrict_to_user_objects".to_string()),
1293 ..Default::default()
1294 }
1295 }
1296 plan::PlannedAlterRoleOption::Variable(_) if role_id == *id => {
1298 RbacRequirements::default()
1299 }
1300 _ => RbacRequirements {
1302 privileges: vec![(SystemObjectId::System, AclMode::CREATE_ROLE, role_id)],
1303 item_usage: &CREATE_ITEM_USAGE,
1304 ..Default::default()
1305 },
1306 },
1307 Plan::AlterOwner(plan::AlterOwnerPlan {
1308 id,
1309 object_type: _,
1310 new_owner,
1311 }) => {
1312 let privileges = match id {
1313 ObjectId::ClusterReplica((cluster_id, _)) => {
1314 vec![(
1315 SystemObjectId::Object(cluster_id.into()),
1316 AclMode::CREATE,
1317 role_id,
1318 )]
1319 }
1320 ObjectId::Schema((database_spec, _)) => match database_spec {
1321 ResolvedDatabaseSpecifier::Ambient => Vec::new(),
1322 ResolvedDatabaseSpecifier::Id(database_id) => {
1323 vec![(
1324 SystemObjectId::Object(database_id.into()),
1325 AclMode::CREATE,
1326 role_id,
1327 )]
1328 }
1329 },
1330 ObjectId::Item(item_id) => {
1331 let item = catalog.get_item(item_id);
1332 vec![(
1333 SystemObjectId::Object(item.name().qualifiers.clone().into()),
1334 AclMode::CREATE,
1335 role_id,
1336 )]
1337 }
1338 ObjectId::Cluster(_)
1339 | ObjectId::Database(_)
1340 | ObjectId::Role(_)
1341 | ObjectId::NetworkPolicy(_) => Vec::new(),
1342 };
1343 RbacRequirements {
1344 role_membership: BTreeSet::from([*new_owner]),
1345 ownership: vec![id.clone()],
1346 privileges,
1347 ..Default::default()
1348 }
1349 }
1350 Plan::AlterTableAddColumn(plan::AlterTablePlan { relation_id, .. }) => RbacRequirements {
1351 ownership: vec![ObjectId::Item(*relation_id)],
1352 item_usage: &CREATE_ITEM_USAGE,
1353 ..Default::default()
1354 },
1355 Plan::AlterMaterializedViewApplyReplacement(
1356 plan::AlterMaterializedViewApplyReplacementPlan { id, replacement_id },
1357 ) => RbacRequirements {
1358 ownership: vec![ObjectId::Item(*id), ObjectId::Item(*replacement_id)],
1359 item_usage: &CREATE_ITEM_USAGE,
1360 ..Default::default()
1361 },
1362 Plan::AlterNetworkPolicy(plan::AlterNetworkPolicyPlan { id, .. }) => RbacRequirements {
1363 ownership: vec![ObjectId::NetworkPolicy(*id)],
1364 item_usage: &CREATE_ITEM_USAGE,
1365 ..Default::default()
1366 },
1367 Plan::ReadThenWrite(plan::ReadThenWritePlan {
1368 id,
1369 selection,
1370 finishing: _,
1371 assignments,
1372 kind,
1373 returning,
1374 }) => {
1375 let acl_mode = match kind {
1376 MutationKind::Insert => AclMode::INSERT,
1377 MutationKind::Update => AclMode::UPDATE,
1378 MutationKind::Delete => AclMode::DELETE,
1379 };
1380 let schema_id: ObjectId = catalog.get_item(id).name().qualifiers.clone().into();
1381 let mut privileges = vec![
1382 (
1383 SystemObjectId::Object(schema_id.clone()),
1384 AclMode::USAGE,
1385 role_id,
1386 ),
1387 (SystemObjectId::Object(id.into()), acl_mode, role_id),
1388 ];
1389 let mut seen = BTreeSet::from([(schema_id, role_id)]);
1390
1391 if assignments
1394 .values()
1395 .chain(returning.iter())
1396 .any(|assignment| assignment.contains_column())
1397 {
1398 privileges.push((SystemObjectId::Object(id.into()), AclMode::SELECT, role_id));
1399 seen.insert((id.into(), role_id));
1400 }
1401
1402 let items = selection
1409 .depends_on()
1410 .into_iter()
1411 .map(|gid| catalog.resolve_item_id(&gid));
1412 privileges.extend_from_slice(&generate_read_privileges_inner(
1413 catalog, items, role_id, &mut seen,
1414 ));
1415
1416 if let Some(privilege) = generate_cluster_usage_privileges(
1417 selection.as_const().is_some(),
1418 target_cluster_id,
1419 role_id,
1420 ) {
1421 privileges.push(privilege);
1422 }
1423 RbacRequirements {
1424 privileges,
1425 ..Default::default()
1426 }
1427 }
1428 Plan::GrantRole(plan::GrantRolePlan {
1429 role_ids: _,
1430 member_ids: _,
1431 grantor_id: _,
1432 })
1433 | Plan::RevokeRole(plan::RevokeRolePlan {
1434 role_ids: _,
1435 member_ids: _,
1436 grantor_id: _,
1437 }) => RbacRequirements {
1438 privileges: vec![(SystemObjectId::System, AclMode::CREATE_ROLE, role_id)],
1439 ..Default::default()
1440 },
1441 Plan::GrantPrivileges(plan::GrantPrivilegesPlan {
1442 update_privileges,
1443 grantees: _,
1444 })
1445 | Plan::RevokePrivileges(plan::RevokePrivilegesPlan {
1446 update_privileges,
1447 revokees: _,
1448 }) => {
1449 let mut privileges = Vec::with_capacity(update_privileges.len());
1450 for UpdatePrivilege { target_id, .. } in update_privileges {
1451 match target_id {
1452 SystemObjectId::Object(object_id) => match object_id {
1453 ObjectId::ClusterReplica((cluster_id, _)) => {
1454 privileges.push((
1455 SystemObjectId::Object(cluster_id.into()),
1456 AclMode::USAGE,
1457 role_id,
1458 ));
1459 }
1460 ObjectId::Schema((database_spec, _)) => match database_spec {
1461 ResolvedDatabaseSpecifier::Ambient => {}
1462 ResolvedDatabaseSpecifier::Id(database_id) => {
1463 privileges.push((
1464 SystemObjectId::Object(database_id.into()),
1465 AclMode::USAGE,
1466 role_id,
1467 ));
1468 }
1469 },
1470 ObjectId::Item(item_id) => {
1471 let item = catalog.get_item(item_id);
1472 privileges.push((
1473 SystemObjectId::Object(item.name().qualifiers.clone().into()),
1474 AclMode::USAGE,
1475 role_id,
1476 ))
1477 }
1478 ObjectId::Cluster(_)
1479 | ObjectId::Database(_)
1480 | ObjectId::Role(_)
1481 | ObjectId::NetworkPolicy(_) => {}
1482 },
1483 SystemObjectId::System => {}
1484 }
1485 }
1486 RbacRequirements {
1487 ownership: update_privileges
1488 .iter()
1489 .filter_map(|update_privilege| update_privilege.target_id.object_id())
1490 .cloned()
1491 .collect(),
1492 privileges,
1493 superuser_action: if update_privileges
1498 .iter()
1499 .any(|update_privilege| update_privilege.target_id.is_system())
1500 {
1501 Some("GRANT/REVOKE SYSTEM PRIVILEGES".to_string())
1502 } else {
1503 None
1504 },
1505 ..Default::default()
1506 }
1507 }
1508 Plan::AlterDefaultPrivileges(plan::AlterDefaultPrivilegesPlan {
1509 privilege_objects,
1510 privilege_acl_items: _,
1511 is_grant: _,
1512 }) => RbacRequirements {
1513 role_membership: privilege_objects
1514 .iter()
1515 .map(|privilege_object| privilege_object.role_id)
1516 .collect(),
1517 privileges: privilege_objects
1518 .into_iter()
1519 .filter_map(|privilege_object| {
1520 if let (Some(database_id), Some(_)) =
1521 (privilege_object.database_id, privilege_object.schema_id)
1522 {
1523 Some((
1524 SystemObjectId::Object(database_id.into()),
1525 AclMode::USAGE,
1526 role_id,
1527 ))
1528 } else {
1529 None
1530 }
1531 })
1532 .collect(),
1533 superuser_action: if privilege_objects
1539 .iter()
1540 .any(|privilege_object| privilege_object.role_id.is_public())
1541 {
1542 Some("ALTER DEFAULT PRIVILEGES FOR ALL ROLES".to_string())
1543 } else {
1544 None
1545 },
1546 ..Default::default()
1547 },
1548 Plan::ReassignOwned(plan::ReassignOwnedPlan {
1549 old_roles,
1550 new_role,
1551 reassign_ids: _,
1552 }) => RbacRequirements {
1553 role_membership: old_roles
1554 .into_iter()
1555 .cloned()
1556 .chain(iter::once(*new_role))
1557 .collect(),
1558 ..Default::default()
1559 },
1560 Plan::SideEffectingFunc(func) => {
1561 let role_membership = match func {
1562 SideEffectingFunc::PgCancelBackend {
1565 connection_id: None,
1566 } => BTreeSet::new(),
1567 SideEffectingFunc::PgCancelBackend {
1568 connection_id: Some(_),
1569 } => target_conn_role.map(|x| [x].into()).unwrap_or_default(),
1570 };
1571 RbacRequirements {
1572 role_membership,
1573 ..Default::default()
1574 }
1575 }
1576 Plan::ValidateConnection(plan::ValidateConnectionPlan { id, connection: _ }) => {
1577 let schema_id: ObjectId = catalog.get_item(id).name().qualifiers.clone().into();
1578 RbacRequirements {
1579 privileges: vec![
1580 (SystemObjectId::Object(schema_id), AclMode::USAGE, role_id),
1581 (SystemObjectId::Object(id.into()), AclMode::USAGE, role_id),
1582 ],
1583 ..Default::default()
1584 }
1585 }
1586 Plan::DiscardTemp
1587 | Plan::DiscardAll
1588 | Plan::EmptyQuery
1589 | Plan::ShowAllVariables
1590 | Plan::ShowVariable(plan::ShowVariablePlan { name: _ })
1591 | Plan::InspectShard(plan::InspectShardPlan { id: _ })
1592 | Plan::SetVariable(plan::SetVariablePlan {
1593 name: _,
1594 value: _,
1595 local: _,
1596 })
1597 | Plan::ResetVariable(plan::ResetVariablePlan { name: _ })
1598 | Plan::SetTransaction(plan::SetTransactionPlan { local: _, modes: _ })
1599 | Plan::StartTransaction(plan::StartTransactionPlan {
1600 access: _,
1601 isolation_level: _,
1602 })
1603 | Plan::CommitTransaction(plan::CommitTransactionPlan {
1604 transaction_type: _,
1605 })
1606 | Plan::AbortTransaction(plan::AbortTransactionPlan {
1607 transaction_type: _,
1608 })
1609 | Plan::AlterNoop(plan::AlterNoopPlan { object_type: _ })
1610 | Plan::AlterSystemSet(plan::AlterSystemSetPlan { name: _, value: _ })
1611 | Plan::AlterSystemReset(plan::AlterSystemResetPlan { name: _ })
1612 | Plan::AlterSystemResetAll(plan::AlterSystemResetAllPlan {})
1613 | Plan::Declare(plan::DeclarePlan {
1614 name: _,
1615 stmt: _,
1616 sql: _,
1617 params: _,
1618 })
1619 | Plan::Fetch(plan::FetchPlan {
1620 name: _,
1621 count: _,
1622 timeout: _,
1623 })
1624 | Plan::Close(plan::ClosePlan { name: _ })
1625 | Plan::Prepare(plan::PreparePlan {
1626 name: _,
1627 stmt: _,
1628 desc: _,
1629 sql: _,
1630 })
1631 | Plan::Execute(plan::ExecutePlan { name: _, params: _ })
1632 | Plan::Deallocate(plan::DeallocatePlan { name: _ })
1633 | Plan::Raise(plan::RaisePlan { severity: _ }) => Default::default(),
1634 }
1635}
1636
1637fn check_owner_roles(
1639 object_id: &ObjectId,
1640 role_ids: &BTreeSet<RoleId>,
1641 catalog: &impl SessionCatalog,
1642) -> bool {
1643 if let Some(owner_id) = catalog.get_owner_id(object_id) {
1644 role_ids.contains(&owner_id)
1645 } else {
1646 true
1647 }
1648}
1649
1650fn ownership_err(
1651 unheld_ownership: Vec<ObjectId>,
1652 catalog: &impl SessionCatalog,
1653) -> Result<(), UnauthorizedError> {
1654 if !unheld_ownership.is_empty() {
1655 let objects = unheld_ownership
1656 .into_iter()
1657 .map(|ownership| match ownership {
1658 ObjectId::Cluster(id) => (
1659 ObjectType::Cluster,
1660 catalog.get_cluster(id).name().to_string(),
1661 ),
1662 ObjectId::ClusterReplica((cluster_id, replica_id)) => {
1663 let cluster = catalog.get_cluster(cluster_id);
1664 let replica = catalog.get_cluster_replica(cluster_id, replica_id);
1665 let name = QualifiedReplica {
1668 cluster: Ident::new_unchecked(cluster.name()),
1669 replica: Ident::new_unchecked(replica.name()),
1670 };
1671 (ObjectType::ClusterReplica, name.to_string())
1672 }
1673 ObjectId::Database(id) => (
1674 ObjectType::Database,
1675 catalog.get_database(&id).name().to_string(),
1676 ),
1677 ObjectId::Schema((database_spec, schema_spec)) => {
1678 let schema = catalog.get_schema(&database_spec, &schema_spec);
1679 let name = catalog.resolve_full_schema_name(schema.name());
1680 (ObjectType::Schema, name.to_string())
1681 }
1682 ObjectId::Item(id) => {
1683 let item = catalog.get_item(&id);
1684 let name = catalog.resolve_full_name(item.name());
1685 (item.item_type().into(), name.to_string())
1686 }
1687 ObjectId::NetworkPolicy(id) => (
1688 ObjectType::NetworkPolicy,
1689 catalog.get_network_policy(&id).name().to_string(),
1690 ),
1691 ObjectId::Role(_) => unreachable!("roles have no owner"),
1692 })
1693 .collect();
1694 Err(UnauthorizedError::Ownership { objects })
1695 } else {
1696 Ok(())
1697 }
1698}
1699
1700fn generate_required_source_privileges(
1701 name: &QualifiedItemName,
1702 data_source: &DataSourceDesc,
1703 in_cluster: Option<ClusterId>,
1704 role_id: RoleId,
1705) -> Vec<(SystemObjectId, AclMode, RoleId)> {
1706 let mut privileges = vec![(
1707 SystemObjectId::Object(name.qualifiers.clone().into()),
1708 AclMode::CREATE,
1709 role_id,
1710 )];
1711 match (data_source, in_cluster) {
1712 (_, Some(id)) => {
1713 privileges.push((SystemObjectId::Object(id.into()), AclMode::CREATE, role_id))
1714 }
1715 (DataSourceDesc::Ingestion(_), None) => {
1716 privileges.push((SystemObjectId::System, AclMode::CREATE_CLUSTER, role_id))
1717 }
1718 (_, None) => {}
1723 }
1724 privileges
1725}
1726
1727fn generate_read_privileges(
1735 catalog: &impl SessionCatalog,
1736 ids: impl Iterator<Item = CatalogItemId>,
1737 role_id: RoleId,
1738) -> Vec<(SystemObjectId, AclMode, RoleId)> {
1739 generate_read_privileges_inner(catalog, ids, role_id, &mut BTreeSet::new())
1740}
1741
1742fn generate_read_privileges_inner(
1743 catalog: &impl SessionCatalog,
1744 ids: impl Iterator<Item = CatalogItemId>,
1745 role_id: RoleId,
1746 seen: &mut BTreeSet<(ObjectId, RoleId)>,
1747) -> Vec<(SystemObjectId, AclMode, RoleId)> {
1748 let mut privileges = Vec::new();
1749
1750 let mut queue: VecDeque<(CatalogItemId, RoleId)> = ids.map(|id| (id, role_id)).collect();
1753 while let Some((id, role_id)) = queue.pop_front() {
1754 if seen.insert((id.into(), role_id)) {
1755 let item = catalog.get_item(&id);
1756 let schema_id: ObjectId = item.name().qualifiers.clone().into();
1757 if seen.insert((schema_id.clone(), role_id)) {
1758 privileges.push((SystemObjectId::Object(schema_id), AclMode::USAGE, role_id))
1759 }
1760 match item.item_type() {
1761 CatalogItemType::View | CatalogItemType::MaterializedView => {
1762 privileges.push((SystemObjectId::Object(id.into()), AclMode::SELECT, role_id));
1763 let view_owner = item.owner_id();
1764 queue.extend(item.references().items().map(|id| (*id, view_owner)));
1765 }
1766 CatalogItemType::Table | CatalogItemType::Source => {
1767 privileges.push((SystemObjectId::Object(id.into()), AclMode::SELECT, role_id));
1768 }
1769 CatalogItemType::Type | CatalogItemType::Secret | CatalogItemType::Connection => {
1770 privileges.push((SystemObjectId::Object(id.into()), AclMode::USAGE, role_id));
1771 }
1772 CatalogItemType::Sink | CatalogItemType::Index | CatalogItemType::Func => {}
1773 }
1774 }
1775 }
1776
1777 privileges
1778}
1779
1780fn generate_usage_privileges(
1781 catalog: &impl SessionCatalog,
1782 ids: &ResolvedIds,
1783 role_id: RoleId,
1784 item_types: &BTreeSet<CatalogItemType>,
1785) -> BTreeSet<(SystemObjectId, AclMode, RoleId)> {
1786 ids.items()
1788 .filter_map(move |id| {
1789 let item = catalog.get_item(id);
1790 if item_types.contains(&item.item_type()) {
1791 let schema_id = item.name().qualifiers.clone().into();
1792 Some([
1793 (SystemObjectId::Object(schema_id), AclMode::USAGE, role_id),
1794 (SystemObjectId::Object(id.into()), AclMode::USAGE, role_id),
1795 ])
1796 } else {
1797 None
1798 }
1799 })
1800 .flatten()
1801 .collect()
1802}
1803
1804fn generate_cluster_usage_privileges(
1805 expr_is_const: bool,
1806 target_cluster_id: Option<ClusterId>,
1807 role_id: RoleId,
1808) -> Option<(SystemObjectId, AclMode, RoleId)> {
1809 if !expr_is_const {
1812 if let Some(cluster_id) = target_cluster_id {
1813 return Some((
1814 SystemObjectId::Object(cluster_id.into()),
1815 AclMode::USAGE,
1816 role_id,
1817 ));
1818 }
1819 }
1820
1821 None
1822}
1823
1824fn check_object_privileges(
1825 catalog: &impl SessionCatalog,
1826 privileges: Vec<(SystemObjectId, AclMode, RoleId)>,
1827 role_membership: BTreeSet<RoleId>,
1828 current_role_id: RoleId,
1829) -> Result<(), UnauthorizedError> {
1830 let mut role_memberships: BTreeMap<RoleId, BTreeSet<RoleId>> = BTreeMap::new();
1831 role_memberships.insert(current_role_id, role_membership);
1832 for (object_id, acl_mode, role_id) in privileges {
1833 if matches!(
1837 &object_id,
1838 SystemObjectId::Object(ObjectId::Schema((_, SchemaSpecifier::Temporary)))
1839 ) {
1840 continue;
1841 }
1842
1843 let role_membership = role_memberships
1844 .entry(role_id)
1845 .or_insert_with_key(|role_id| catalog.collect_role_membership(role_id));
1846 let object_privileges = catalog
1847 .get_privileges(&object_id)
1848 .expect("only object types with privileges will generate required privileges");
1849 let role_privileges = role_membership
1850 .iter()
1851 .flat_map(|role_id| object_privileges.get_acl_items_for_grantee(role_id))
1852 .map(|mz_acl_item| mz_acl_item.acl_mode)
1853 .fold(AclMode::empty(), |accum, acl_mode| accum.union(acl_mode));
1854 if !role_privileges.contains(acl_mode) {
1855 let role_name = catalog.get_role(&role_id).name().to_string();
1856 let privileges = acl_mode.to_error_string();
1857 return Err(UnauthorizedError::Privilege {
1858 object_description: ErrorMessageObjectDescription::from_sys_id(&object_id, catalog),
1859 role_name,
1860 privileges,
1861 });
1862 }
1863 }
1864
1865 Ok(())
1866}
1867
1868pub const fn all_object_privileges(object_type: SystemObjectType) -> AclMode {
1869 const TABLE_ACL_MODE: AclMode = AclMode::INSERT
1870 .union(AclMode::SELECT)
1871 .union(AclMode::UPDATE)
1872 .union(AclMode::DELETE);
1873 const USAGE_CREATE_ACL_MODE: AclMode = AclMode::USAGE.union(AclMode::CREATE);
1874 const ALL_SYSTEM_PRIVILEGES: AclMode = AclMode::CREATE_ROLE
1875 .union(AclMode::CREATE_DB)
1876 .union(AclMode::CREATE_CLUSTER)
1877 .union(AclMode::CREATE_NETWORK_POLICY);
1878
1879 const EMPTY_ACL_MODE: AclMode = AclMode::empty();
1880 match object_type {
1881 SystemObjectType::Object(ObjectType::Table) => TABLE_ACL_MODE,
1882 SystemObjectType::Object(ObjectType::View) => AclMode::SELECT,
1883 SystemObjectType::Object(ObjectType::MaterializedView) => AclMode::SELECT,
1884 SystemObjectType::Object(ObjectType::Source) => AclMode::SELECT,
1885 SystemObjectType::Object(ObjectType::Sink) => EMPTY_ACL_MODE,
1886 SystemObjectType::Object(ObjectType::Index) => EMPTY_ACL_MODE,
1887 SystemObjectType::Object(ObjectType::Type) => AclMode::USAGE,
1888 SystemObjectType::Object(ObjectType::Role) => EMPTY_ACL_MODE,
1889 SystemObjectType::Object(ObjectType::Cluster) => USAGE_CREATE_ACL_MODE,
1890 SystemObjectType::Object(ObjectType::ClusterReplica) => EMPTY_ACL_MODE,
1891 SystemObjectType::Object(ObjectType::Secret) => AclMode::USAGE,
1892 SystemObjectType::Object(ObjectType::NetworkPolicy) => AclMode::USAGE,
1893 SystemObjectType::Object(ObjectType::Connection) => AclMode::USAGE,
1894 SystemObjectType::Object(ObjectType::Database) => USAGE_CREATE_ACL_MODE,
1895 SystemObjectType::Object(ObjectType::Schema) => USAGE_CREATE_ACL_MODE,
1896 SystemObjectType::Object(ObjectType::Func) => EMPTY_ACL_MODE,
1897 SystemObjectType::System => ALL_SYSTEM_PRIVILEGES,
1898 }
1899}
1900
1901pub const fn owner_privilege(object_type: ObjectType, owner_id: RoleId) -> MzAclItem {
1902 MzAclItem {
1903 grantee: owner_id,
1904 grantor: owner_id,
1905 acl_mode: all_object_privileges(SystemObjectType::Object(object_type)),
1906 }
1907}
1908
1909const fn default_builtin_object_acl_mode(object_type: ObjectType) -> AclMode {
1910 match object_type {
1911 ObjectType::Table
1912 | ObjectType::View
1913 | ObjectType::MaterializedView
1914 | ObjectType::Source => AclMode::SELECT,
1915 ObjectType::Type | ObjectType::Schema => AclMode::USAGE,
1916 ObjectType::Sink
1917 | ObjectType::Index
1918 | ObjectType::Role
1919 | ObjectType::Cluster
1920 | ObjectType::ClusterReplica
1921 | ObjectType::Secret
1922 | ObjectType::Connection
1923 | ObjectType::Database
1924 | ObjectType::Func
1925 | ObjectType::NetworkPolicy => AclMode::empty(),
1926 }
1927}
1928
1929pub const fn support_builtin_object_privilege(object_type: ObjectType) -> MzAclItem {
1930 let acl_mode = default_builtin_object_acl_mode(object_type);
1931 MzAclItem {
1932 grantee: MZ_SUPPORT_ROLE_ID,
1933 grantor: MZ_SYSTEM_ROLE_ID,
1934 acl_mode,
1935 }
1936}
1937
1938pub const fn default_builtin_object_privilege(object_type: ObjectType) -> MzAclItem {
1939 let acl_mode = default_builtin_object_acl_mode(object_type);
1940 MzAclItem {
1941 grantee: RoleId::Public,
1942 grantor: MZ_SYSTEM_ROLE_ID,
1943 acl_mode,
1944 }
1945}