Skip to content

Commit 3612c05

Browse files
feat(model): add unified system entity data access control
Introduce @SystemEntity/@System annotations, dataHubSystemState entity, and three enforcement layers (AuthUtil API gate, DAO read filter, write validator) with a single configuration toggle for operators. Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 30742e2 commit 3612c05

89 files changed

Lines changed: 3328 additions & 211 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/knowledge/DocumentChangeHistoryResolver.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public CompletableFuture<List<DocumentChange>> get(DataFetchingEnvironment envir
7474
// Get timeline from TimelineService
7575
List<ChangeTransaction> transactions =
7676
_timelineService.getTimeline(
77+
context.getOperationContext(),
7778
documentUrn,
7879
categories,
7980
startTime,

datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/timeline/GetSchemaBlameResolver.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,14 @@ public CompletableFuture<GetSchemaBlameResult> get(final DataFetchingEnvironment
6161
Collections.singleton(ChangeCategory.TECHNICAL_SCHEMA);
6262
final List<ChangeTransaction> changeTransactionList =
6363
_timelineService.getTimeline(
64-
datasetUrn, changeCategorySet, startTime, endTime, null, null, false);
64+
context.getOperationContext(),
65+
datasetUrn,
66+
changeCategorySet,
67+
startTime,
68+
endTime,
69+
null,
70+
null,
71+
false);
6572
return SchemaBlameMapper.map(changeTransactionList, version);
6673
} catch (Exception e) {
6774
log.error("Failed to list schema blame data", e);

datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/timeline/GetSchemaVersionListResolver.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,14 @@ public CompletableFuture<GetSchemaVersionListResult> get(
6060
changeCategorySet.add(ChangeCategory.TECHNICAL_SCHEMA);
6161
List<ChangeTransaction> changeTransactionList =
6262
_timelineService.getTimeline(
63-
datasetUrn, changeCategorySet, startTime, endTime, null, null, false);
63+
context.getOperationContext(),
64+
datasetUrn,
65+
changeCategorySet,
66+
startTime,
67+
endTime,
68+
null,
69+
null,
70+
false);
6471
return SchemaVersionListMapper.map(changeTransactionList);
6572
} catch (Exception e) {
6673
log.error("Failed to list schema version data", e);

datahub-graphql-core/src/main/java/com/linkedin/datahub/graphql/resolvers/timeline/GetTimelineResolver.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ public CompletableFuture<GetTimelineResult> get(final DataFetchingEnvironment en
6666
: Arrays.stream(ChangeCategory.values()).collect(Collectors.toSet());
6767
final List<ChangeTransaction> changeTransactionList =
6868
_timelineService.getTimeline(
69+
context.getOperationContext(),
6970
entityUrn,
7071
changeCategorySet,
7172
TimelineService.DEFAULT_MAX_CHANGE_TRANSACTIONS,

datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/knowledge/DocumentChangeHistoryResolverTest.java

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ public void testGetChangeHistorySuccess() throws Exception {
8080
transactions.add(transaction);
8181

8282
when(mockTimelineService.getTimeline(
83+
any(),
8384
eq(TEST_DOCUMENT_URN),
8485
any(Set.class),
8586
anyLong(),
@@ -126,7 +127,14 @@ public void testGetChangeHistoryWithContentModification() throws Exception {
126127
transactions.add(transaction);
127128

128129
when(mockTimelineService.getTimeline(
129-
any(Urn.class), any(Set.class), anyLong(), anyLong(), isNull(), isNull(), eq(false)))
130+
any(),
131+
any(Urn.class),
132+
any(Set.class),
133+
anyLong(),
134+
anyLong(),
135+
isNull(),
136+
isNull(),
137+
eq(false)))
130138
.thenReturn(transactions);
131139

132140
List<DocumentChange> result = resolver.get(mockEnv).get();
@@ -166,7 +174,14 @@ public void testGetChangeHistoryWithParentChange() throws Exception {
166174
transactions.add(transaction);
167175

168176
when(mockTimelineService.getTimeline(
169-
any(Urn.class), any(Set.class), anyLong(), anyLong(), isNull(), isNull(), eq(false)))
177+
any(),
178+
any(Urn.class),
179+
any(Set.class),
180+
anyLong(),
181+
anyLong(),
182+
isNull(),
183+
isNull(),
184+
eq(false)))
170185
.thenReturn(transactions);
171186

172187
List<DocumentChange> result = resolver.get(mockEnv).get();
@@ -201,7 +216,14 @@ public void testGetChangeHistoryWithStateChange() throws Exception {
201216
transactions.add(transaction);
202217

203218
when(mockTimelineService.getTimeline(
204-
any(Urn.class), any(Set.class), anyLong(), anyLong(), isNull(), isNull(), eq(false)))
219+
any(),
220+
any(Urn.class),
221+
any(Set.class),
222+
anyLong(),
223+
anyLong(),
224+
isNull(),
225+
isNull(),
226+
eq(false)))
205227
.thenReturn(transactions);
206228

207229
List<DocumentChange> result = resolver.get(mockEnv).get();
@@ -221,6 +243,7 @@ public void testGetChangeHistoryWithCustomTimeRange() throws Exception {
221243
when(mockEnv.getArgument("limit")).thenReturn(100);
222244

223245
when(mockTimelineService.getTimeline(
246+
any(),
224247
eq(TEST_DOCUMENT_URN),
225248
any(Set.class),
226249
eq(startTime),
@@ -235,6 +258,7 @@ public void testGetChangeHistoryWithCustomTimeRange() throws Exception {
235258
assertNotNull(result);
236259
verify(mockTimelineService, times(1))
237260
.getTimeline(
261+
any(),
238262
eq(TEST_DOCUMENT_URN),
239263
any(Set.class),
240264
eq(startTime),
@@ -280,7 +304,14 @@ public void testGetChangeHistoryMultipleChanges() throws Exception {
280304
transactions.add(transaction2);
281305

282306
when(mockTimelineService.getTimeline(
283-
any(Urn.class), any(Set.class), anyLong(), anyLong(), isNull(), isNull(), eq(false)))
307+
any(),
308+
any(Urn.class),
309+
any(Set.class),
310+
anyLong(),
311+
anyLong(),
312+
isNull(),
313+
isNull(),
314+
eq(false)))
284315
.thenReturn(transactions);
285316

286317
List<DocumentChange> result = resolver.get(mockEnv).get();
@@ -294,7 +325,14 @@ public void testGetChangeHistoryMultipleChanges() throws Exception {
294325
@Test(expectedExceptions = Exception.class)
295326
public void testGetChangeHistoryServiceThrowsException() throws Exception {
296327
when(mockTimelineService.getTimeline(
297-
any(Urn.class), any(Set.class), anyLong(), anyLong(), isNull(), isNull(), eq(false)))
328+
any(),
329+
any(Urn.class),
330+
any(Set.class),
331+
anyLong(),
332+
anyLong(),
333+
isNull(),
334+
isNull(),
335+
eq(false)))
298336
.thenThrow(new RuntimeException("Service error"));
299337

300338
// Should throw an exception when service fails
@@ -304,7 +342,14 @@ public void testGetChangeHistoryServiceThrowsException() throws Exception {
304342
@Test
305343
public void testGetChangeHistoryEmptyResult() throws Exception {
306344
when(mockTimelineService.getTimeline(
307-
any(Urn.class), any(Set.class), anyLong(), anyLong(), isNull(), isNull(), eq(false)))
345+
any(),
346+
any(Urn.class),
347+
any(Set.class),
348+
anyLong(),
349+
anyLong(),
350+
isNull(),
351+
isNull(),
352+
eq(false)))
308353
.thenReturn(new ArrayList<>());
309354

310355
List<DocumentChange> result = resolver.get(mockEnv).get();
@@ -341,7 +386,14 @@ public void testGetChangeHistoryWithRelatedAssetChange() throws Exception {
341386
transactions.add(transaction);
342387

343388
when(mockTimelineService.getTimeline(
344-
any(Urn.class), any(Set.class), anyLong(), anyLong(), isNull(), isNull(), eq(false)))
389+
any(),
390+
any(Urn.class),
391+
any(Set.class),
392+
anyLong(),
393+
anyLong(),
394+
isNull(),
395+
isNull(),
396+
eq(false)))
345397
.thenReturn(transactions);
346398

347399
List<DocumentChange> result = resolver.get(mockEnv).get();
@@ -394,7 +446,14 @@ public void testGetChangeHistoryRespectsLimit() throws Exception {
394446

395447
when(mockEnv.getArgument("limit")).thenReturn(10);
396448
when(mockTimelineService.getTimeline(
397-
any(Urn.class), any(Set.class), anyLong(), anyLong(), isNull(), isNull(), eq(false)))
449+
any(),
450+
any(Urn.class),
451+
any(Set.class),
452+
anyLong(),
453+
anyLong(),
454+
isNull(),
455+
isNull(),
456+
eq(false)))
398457
.thenReturn(transactions);
399458

400459
List<DocumentChange> result = resolver.get(mockEnv).get();

datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/timeline/GetSchemaBlameResolverTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public void testGetUnauthorizedThrowsAndDoesNotQueryDb() throws Exception {
3939
public void testGetAuthorizedInvokesTimelineService() throws Exception {
4040
TimelineService mockTimelineService = mock(TimelineService.class);
4141
when(mockTimelineService.getTimeline(
42-
any(), any(), anyLong(), anyLong(), any(), any(), anyBoolean()))
42+
any(), any(), any(), anyLong(), anyLong(), any(), any(), anyBoolean()))
4343
.thenReturn(Collections.emptyList());
4444

4545
GetSchemaBlameResolver resolver = new GetSchemaBlameResolver(mockTimelineService);
@@ -54,6 +54,6 @@ public void testGetAuthorizedInvokesTimelineService() throws Exception {
5454

5555
resolver.get(mockEnv).get();
5656
verify(mockTimelineService, times(1))
57-
.getTimeline(any(), any(), anyLong(), anyLong(), any(), any(), anyBoolean());
57+
.getTimeline(any(), any(), any(), anyLong(), anyLong(), any(), any(), anyBoolean());
5858
}
5959
}

datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/timeline/GetSchemaVersionListResolverTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public void testGetUnauthorizedThrowsAndDoesNotQueryDb() throws Exception {
3939
public void testGetAuthorizedInvokesTimelineService() throws Exception {
4040
TimelineService mockTimelineService = mock(TimelineService.class);
4141
when(mockTimelineService.getTimeline(
42-
any(), any(), anyLong(), anyLong(), any(), any(), anyBoolean()))
42+
any(), any(), any(), anyLong(), anyLong(), any(), any(), anyBoolean()))
4343
.thenReturn(Collections.emptyList());
4444

4545
GetSchemaVersionListResolver resolver = new GetSchemaVersionListResolver(mockTimelineService);
@@ -54,6 +54,6 @@ public void testGetAuthorizedInvokesTimelineService() throws Exception {
5454

5555
resolver.get(mockEnv).get();
5656
verify(mockTimelineService, times(1))
57-
.getTimeline(any(), any(), anyLong(), anyLong(), any(), any(), anyBoolean());
57+
.getTimeline(any(), any(), any(), anyLong(), anyLong(), any(), any(), anyBoolean());
5858
}
5959
}

datahub-graphql-core/src/test/java/com/linkedin/datahub/graphql/resolvers/timeline/GetTimelineResolverTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public void testGetUnauthorizedThrowsAndDoesNotQueryDb() {
6363
@Test
6464
public void testGetAuthorizedReturnsResult() throws Exception {
6565
TimelineService mockTimelineService = mock(TimelineService.class);
66-
when(mockTimelineService.getTimeline(any(), any(), anyInt(), anyBoolean()))
66+
when(mockTimelineService.getTimeline(any(), any(), any(), anyInt(), anyBoolean()))
6767
.thenReturn(List.of());
6868

6969
GetTimelineResolver resolver = new GetTimelineResolver(mockTimelineService);
@@ -77,6 +77,6 @@ public void testGetAuthorizedReturnsResult() throws Exception {
7777
when(mockEnv.getArgument("input")).thenReturn(input);
7878

7979
assertNotNull(resolver.get(mockEnv).get());
80-
verify(mockTimelineService, times(1)).getTimeline(any(), any(), anyInt(), anyBoolean());
80+
verify(mockTimelineService, times(1)).getTimeline(any(), any(), any(), anyInt(), anyBoolean());
8181
}
8282
}

docs-website/sidebars.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1393,6 +1393,7 @@ module.exports = {
13931393
],
13941394
},
13951395
"docs/modeling/extending-the-metadata-model",
1396+
"docs/modeling/system-data",
13961397
"docs/advanced/api-tracing",
13971398
"docs/advanced/micrometer-best-practices",
13981399
"datahub-web-react/src/app/analytics/README",

docs/modeling/extending-the-metadata-model.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,3 +803,8 @@ replaces it with its own- this has a different boostScore and a different fieldN
803803
As a result, you can issue a query specifically for tags on Schema Fields via `fieldTags:<tag_name>` or tags directly
804804
applied to an entity via `tags:<tag_name>`. Since both have `queryByDefault` set to true, you can also search for
805805
entities with either of these properties just by searching for the tag name.
806+
807+
## System metadata
808+
809+
For internal GMS-owned entities that must be hidden from users and writable only by the system actor, see
810+
[System Entity Data Access](./system-data.md).

0 commit comments

Comments
 (0)