Skip to content

Commit a38da0c

Browse files
shyjsarahclaude
andcommitted
[core] Add ViewNoPermissionException and handle ForbiddenException for view operations in RESTCatalog
Align view permission handling with table in RESTCatalog by catching ForbiddenException and throwing ViewNoPermissionException for all view operations (get/create/drop/rename/alter/list). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 72600f9 commit a38da0c

File tree

5 files changed

+94
-0
lines changed

5 files changed

+94
-0
lines changed

paimon-core/src/main/java/org/apache/paimon/catalog/Catalog.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1433,6 +1433,33 @@ public String getTableId() {
14331433
}
14341434
}
14351435

1436+
/** Exception for trying to operate on the view that doesn't have permission. */
1437+
class ViewNoPermissionException extends RuntimeException {
1438+
1439+
private static final String MSG = "View %s has no permission. Cause by %s.";
1440+
1441+
private final Identifier identifier;
1442+
1443+
public ViewNoPermissionException(Identifier identifier, Throwable cause) {
1444+
super(
1445+
String.format(
1446+
MSG,
1447+
identifier.getFullName(),
1448+
cause != null && cause.getMessage() != null ? cause.getMessage() : ""),
1449+
cause);
1450+
this.identifier = identifier;
1451+
}
1452+
1453+
@VisibleForTesting
1454+
public ViewNoPermissionException(Identifier identifier) {
1455+
this(identifier, null);
1456+
}
1457+
1458+
public Identifier identifier() {
1459+
return identifier;
1460+
}
1461+
}
1462+
14361463
/** Exception for trying to alter a column that already exists. */
14371464
class ColumnAlreadyExistException extends Exception {
14381465

paimon-core/src/main/java/org/apache/paimon/rest/RESTCatalog.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,8 @@ public View getView(Identifier identifier) throws ViewNotExistException {
900900
return toView(identifier.getDatabaseName(), response);
901901
} catch (NoSuchResourceException e) {
902902
throw new ViewNotExistException(identifier);
903+
} catch (ForbiddenException e) {
904+
throw new ViewNoPermissionException(identifier, e);
903905
}
904906
}
905907

@@ -912,6 +914,8 @@ public void dropView(Identifier identifier, boolean ignoreIfNotExists)
912914
if (!ignoreIfNotExists) {
913915
throw new ViewNotExistException(identifier);
914916
}
917+
} catch (ForbiddenException e) {
918+
throw new ViewNoPermissionException(identifier, e);
915919
}
916920
}
917921

@@ -935,6 +939,8 @@ public void createView(Identifier identifier, View view, boolean ignoreIfExists)
935939
}
936940
} catch (BadRequestException e) {
937941
throw new IllegalArgumentException(e.getMessage());
942+
} catch (ForbiddenException e) {
943+
throw new ViewNoPermissionException(identifier, e);
938944
}
939945
}
940946

@@ -946,6 +952,8 @@ public List<String> listViews(String databaseName) throws DatabaseNotExistExcept
946952
: api.listViews(databaseName);
947953
} catch (NoSuchResourceException e) {
948954
throw new DatabaseNotExistException(databaseName);
955+
} catch (ForbiddenException e) {
956+
throw new DatabaseNoPermissionException(databaseName, e);
949957
}
950958
}
951959

@@ -960,6 +968,8 @@ public PagedList<String> listViewsPaged(
960968
return api.listViewsPaged(databaseName, maxResults, pageToken, viewNamePattern);
961969
} catch (NoSuchResourceException e) {
962970
throw new DatabaseNotExistException(databaseName);
971+
} catch (ForbiddenException e) {
972+
throw new DatabaseNoPermissionException(databaseName, e);
963973
}
964974
}
965975

@@ -980,6 +990,8 @@ public PagedList<View> listViewDetailsPaged(
980990
views.getNextPageToken());
981991
} catch (NoSuchResourceException e) {
982992
throw new DatabaseNotExistException(db);
993+
} catch (ForbiddenException e) {
994+
throw new DatabaseNoPermissionException(db, e);
983995
}
984996
}
985997

@@ -1021,6 +1033,8 @@ public void renameView(Identifier fromView, Identifier toView, boolean ignoreIfN
10211033
throw new ViewAlreadyExistException(toView);
10221034
} catch (BadRequestException e) {
10231035
throw new IllegalArgumentException(e.getMessage());
1036+
} catch (ForbiddenException e) {
1037+
throw new ViewNoPermissionException(fromView, e);
10241038
}
10251039
}
10261040

@@ -1041,6 +1055,8 @@ public void alterView(
10411055
}
10421056
} catch (BadRequestException e) {
10431057
throw new IllegalArgumentException(e.getMessage());
1058+
} catch (ForbiddenException e) {
1059+
throw new ViewNoPermissionException(identifier, e);
10441060
}
10451061
}
10461062

paimon-core/src/test/java/org/apache/paimon/rest/MockRESTCatalogTest.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,11 @@ protected void revokeTablePermission(Identifier identifier) {
347347
restCatalogServer.addNoPermissionTable(identifier);
348348
}
349349

350+
@Override
351+
protected void revokeViewPermission(Identifier identifier) {
352+
restCatalogServer.addNoPermissionView(identifier);
353+
}
354+
350355
@Override
351356
protected void authTableColumns(Identifier identifier, List<String> columns) {
352357
restCatalogServer.addTableColumnAuth(identifier, columns);

paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogServer.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ public class RESTCatalogServer {
195195
private final Map<String, TableSnapshot> tableWithSnapshotId2SnapshotStore = new HashMap<>();
196196
private final List<String> noPermissionDatabases = new ArrayList<>();
197197
private final List<String> noPermissionTables = new ArrayList<>();
198+
private final List<String> noPermissionViews = new ArrayList<>();
198199
private final Map<String, Function> functionStore = new HashMap<>();
199200
private final Map<String, List<String>> columnAuthHandler = new HashMap<>();
200201
private final Map<String, List<Predicate>> rowFilterAuthHandler = new HashMap<>();
@@ -276,6 +277,10 @@ public void addNoPermissionTable(Identifier identifier) {
276277
noPermissionTables.add(identifier.getFullName());
277278
}
278279

280+
public void addNoPermissionView(Identifier identifier) {
281+
noPermissionViews.add(identifier.getFullName());
282+
}
283+
279284
public void addTableColumnAuth(Identifier identifier, List<String> select) {
280285
columnAuthHandler.put(identifier.getFullName(), select);
281286
}
@@ -631,6 +636,14 @@ && isTableByIdRequest(request.getPath())) {
631636
e.getMessage(),
632637
403);
633638
return mockResponse(response, 403);
639+
} catch (Catalog.ViewNoPermissionException e) {
640+
response =
641+
new ErrorResponse(
642+
ErrorResponse.RESOURCE_TYPE_VIEW,
643+
e.identifier().getTableName(),
644+
e.getMessage(),
645+
403);
646+
return mockResponse(response, 403);
634647
} catch (Catalog.DatabaseAlreadyExistException e) {
635648
response =
636649
new ErrorResponse(
@@ -2327,6 +2340,9 @@ private List<Identifier> listViews(Map<String, String> parameters) {
23272340
private MockResponse viewHandle(String method, Identifier identifier, String requestData)
23282341
throws Exception {
23292342
RESTResponse response;
2343+
if (noPermissionViews.contains(identifier.getFullName())) {
2344+
throw new Catalog.ViewNoPermissionException(identifier);
2345+
}
23302346
if (viewStore.containsKey(identifier.getFullName())) {
23312347
switch (method) {
23322348
case "GET":

paimon-core/src/test/java/org/apache/paimon/rest/RESTCatalogTest.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,34 @@ void testDatabaseApiWhenNoPermission() {
278278
false));
279279
}
280280

281+
@Test
282+
void testApiWhenViewNoPermission() throws Exception {
283+
Identifier identifier = Identifier.create("test_view_db", "no_permission_view");
284+
catalog.createDatabase(identifier.getDatabaseName(), false);
285+
View view = createView(identifier);
286+
catalog.createView(identifier, view, false);
287+
revokeViewPermission(identifier);
288+
assertThrows(Catalog.ViewNoPermissionException.class, () -> catalog.getView(identifier));
289+
assertThrows(
290+
Catalog.ViewNoPermissionException.class, () -> catalog.dropView(identifier, false));
291+
assertThrows(
292+
Catalog.ViewNoPermissionException.class,
293+
() ->
294+
catalog.renameView(
295+
identifier,
296+
Identifier.create("test_view_db", "no_permission_view2"),
297+
false));
298+
assertThrows(
299+
Catalog.ViewNoPermissionException.class,
300+
() ->
301+
catalog.alterView(
302+
identifier,
303+
ImmutableList.of(
304+
ViewChange.addDialect(
305+
"flink_1", "SELECT * FROM FLINK_TABLE_1")),
306+
false));
307+
}
308+
281309
@Test
282310
void testApiWhenDatabaseNoExistAndNotIgnore() {
283311
String database = "test_no_exist_db";
@@ -3932,6 +3960,8 @@ protected abstract Catalog newRestCatalogWithDataToken(Map<String, String> extra
39323960

39333961
protected abstract void revokeTablePermission(Identifier identifier);
39343962

3963+
protected abstract void revokeViewPermission(Identifier identifier);
3964+
39353965
protected abstract void authTableColumns(Identifier identifier, List<String> columns);
39363966

39373967
protected abstract void revokeDatabasePermission(String database);

0 commit comments

Comments
 (0)