Skip to content

Commit 8ada592

Browse files
authored
Merge pull request systeminit#5245 from systeminit/brit/ensure-change-sets-belong-to-workspaces
fix(dal,sdf): when finding change sets, ensure it belongs to the relevant workspace
2 parents 48dc40c + 8e15c47 commit 8ada592

File tree

4 files changed

+128
-6
lines changed

4 files changed

+128
-6
lines changed

lib/dal/src/change_set.rs

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -555,16 +555,18 @@ impl ChangeSet {
555555
Ok(())
556556
}
557557

558+
/// Finds a [`ChangeSet`] across all workspaces, ignoring the provided [`WorkspacePk`] on the
559+
/// current [`DalContext`]
558560
#[instrument(
559-
name = "change_set.find",
561+
name = "change_set.find_across_workspaces",
560562
level = "debug",
561563
skip_all,
562564
fields(
563565
si.change_set.id = %change_set_id,
564566
si.workspace.id = Empty,
565567
),
566568
)]
567-
pub async fn find(
569+
pub async fn find_across_workspaces(
568570
ctx: &DalContext,
569571
change_set_id: ChangeSetId,
570572
) -> ChangeSetResult<Option<Self>> {
@@ -593,6 +595,52 @@ impl ChangeSet {
593595
}
594596
}
595597

598+
/// Find a change set within the [`WorkspacePk`] set for the current [`DalContext`]
599+
#[instrument(
600+
name = "change_set.find",
601+
level = "debug",
602+
skip_all,
603+
fields(
604+
si.change_set.id = %change_set_id,
605+
si.workspace.id = Empty,
606+
),
607+
)]
608+
pub async fn find(
609+
ctx: &DalContext,
610+
change_set_id: ChangeSetId,
611+
) -> ChangeSetResult<Option<Self>> {
612+
let span = current_span_for_instrument_at!("debug");
613+
let workspace_id = ctx.workspace_pk()?;
614+
let row = ctx
615+
.txns()
616+
.await?
617+
.pg()
618+
.query_opt(
619+
"SELECT * FROM change_set_pointers WHERE id = $1 AND workspace_id = $2",
620+
&[&change_set_id, &workspace_id],
621+
)
622+
.await?;
623+
624+
match row {
625+
Some(row) => {
626+
let change_set = Self::try_from(row)?;
627+
628+
if let Some(workspace_id) = change_set.workspace_id {
629+
span.record("si.workspace.id", workspace_id.to_string());
630+
}
631+
Ok(Some(change_set))
632+
}
633+
None => {
634+
// warn here so we can see if something is requesting change sets cross workspace
635+
warn!(
636+
si.workspace.id = %workspace_id,
637+
"Change Set Id: {change_set_id} not found for Workspace: {workspace_id}",
638+
);
639+
Ok(None)
640+
}
641+
}
642+
}
643+
596644
pub async fn list_active(ctx: &DalContext) -> ChangeSetResult<Vec<Self>> {
597645
let mut result = vec![];
598646
let rows = ctx

lib/dal/src/context.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -403,9 +403,11 @@ impl DalContext {
403403
Ok(workspace)
404404
}
405405

406-
/// Update the context to use the most recent snapshot pointed to by the current `ChangeSetId`.
406+
/// Update the context to use the most recent snapshot pointed to by the current [`ChangeSetId`].
407+
/// Note: This does not guarantee that the [`ChangeSetId`] is contained within the [`WorkspacePk`]
408+
/// for the current [`DalContext`]
407409
pub async fn update_snapshot_to_visibility(&mut self) -> TransactionsResult<()> {
408-
let change_set = ChangeSet::find(self, self.change_set_id())
410+
let change_set = ChangeSet::find_across_workspaces(self, self.change_set_id())
409411
.await
410412
.map_err(|err| TransactionsError::ChangeSet(err.to_string()))?
411413
.ok_or(TransactionsError::ChangeSetNotFound(self.change_set_id()))?;

lib/dal/src/workspace_snapshot/migrator.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ impl SnapshotGraphMigrator {
8787
info!("Migrating {} snapshot(s)", open_change_sets.len(),);
8888

8989
for change_set in open_change_sets {
90-
let mut change_set = ChangeSet::find(ctx, change_set.id)
90+
let mut change_set = ChangeSet::find_across_workspaces(ctx, change_set.id)
9191
.await?
9292
.ok_or(ChangeSetError::ChangeSetNotFound(change_set.id))?;
9393
if change_set.workspace_id.is_none() || change_set.status == ChangeSetStatus::Failed {

lib/dal/tests/integration_test/change_set.rs

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use dal::{
33
context::TransactionsErrorDiscriminants, DalContext, DalContextBuilder, HistoryActor,
44
RequestContext, Workspace, WorkspacePk,
55
};
6-
use dal::{ChangeSet, ChangeSetStatus, Component};
6+
use dal::{AccessBuilder, ChangeSet, ChangeSetStatus, Component};
77
use dal_test::helpers::{
88
create_component_for_default_schema_name_in_default_view, create_user, ChangeSetTestHelpers,
99
};
@@ -255,6 +255,78 @@ async fn build_from_request_context_limits_to_change_sets_of_current_workspace(
255255
.is_err_and(|e| TransactionsErrorDiscriminants::BadWorkspaceAndChangeSet == e.into()));
256256
}
257257

258+
#[test]
259+
async fn cannot_find_change_set_across_workspaces(
260+
ctx: &mut DalContext,
261+
ctx_builder: DalContextBuilder,
262+
) {
263+
let user_1 = create_user(ctx).await.expect("Unable to create user");
264+
let user_2 = create_user(ctx).await.expect("Unable to create user");
265+
let user_1_workspace =
266+
Workspace::new_from_builtin(ctx, WorkspacePk::generate(), "user_1 workspace", "token")
267+
.await
268+
.expect("Unable to create workspace");
269+
let user_2_workspace =
270+
Workspace::new_from_builtin(ctx, WorkspacePk::generate(), "user_2 workspace", "token")
271+
.await
272+
.expect("Unable to create workspace");
273+
user_1
274+
.associate_workspace(ctx, *user_1_workspace.pk())
275+
.await
276+
.expect("Unable to associate user with workspace");
277+
user_2
278+
.associate_workspace(ctx, *user_2_workspace.pk())
279+
.await
280+
.expect("Unable to associate user with workspace");
281+
ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(ctx)
282+
.await
283+
.expect("Unable to set up test data");
284+
285+
let request_context = RequestContext {
286+
tenancy: dal::Tenancy::new(*user_2_workspace.pk()),
287+
visibility: dal::Visibility {
288+
change_set_id: user_2_workspace.default_change_set_id(),
289+
},
290+
history_actor: HistoryActor::User(user_2.pk()),
291+
request_ulid: None,
292+
};
293+
294+
let mut user_2_dal_ctx = ctx_builder
295+
.build(request_context)
296+
.await
297+
.expect("built dal ctx for user 2");
298+
299+
//create a new change set for user 2
300+
let user_2_change_set = ChangeSet::fork_head(&user_2_dal_ctx, "user 2")
301+
.await
302+
.expect("could not create change set");
303+
ChangeSetTestHelpers::commit_and_update_snapshot_to_visibility(&mut user_2_dal_ctx)
304+
.await
305+
.expect("Unable to set up test data");
306+
307+
let user_1_tenancy = dal::Tenancy::new(*user_1_workspace.pk());
308+
let access_builder = AccessBuilder::new(user_1_tenancy, HistoryActor::User(user_1.pk()), None);
309+
310+
let user_1_dal_context = ctx_builder
311+
.build_head(access_builder)
312+
.await
313+
.expect("could not build dal context");
314+
315+
//first, let's ensure we can't find it when using the user 1 dal ctx
316+
let user_2_change_set_unfound = ChangeSet::find(&user_1_dal_context, user_2_change_set.id)
317+
.await
318+
.expect("could not find change set");
319+
assert!(user_2_change_set_unfound.is_none());
320+
321+
// But if we search for the change set across all workspaces, we find it
322+
let user_2_change_set_found_harshly =
323+
ChangeSet::find_across_workspaces(&user_1_dal_context, user_2_change_set.id)
324+
.await
325+
.expect("could not find change set");
326+
327+
assert!(user_2_change_set_found_harshly.is_some());
328+
}
329+
258330
#[test]
259331
async fn build_from_request_context_allows_change_set_from_workspace_with_access(
260332
ctx: &mut DalContext,

0 commit comments

Comments
 (0)