Skip to content

Feature/212 implement authorization on delta sharing apis #213

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@
import io.whitefox.api.deltasharing.ClientCapabilitiesMapper;
import io.whitefox.api.deltasharing.DeltaMappers;
import io.whitefox.api.deltasharing.encoders.DeltaPageTokenEncoder;
import io.whitefox.api.deltasharing.model.v1.generated.ListSchemasResponse;
import io.whitefox.api.deltasharing.model.v1.generated.ListShareResponse;
import io.whitefox.api.deltasharing.model.v1.generated.ListTablesResponse;
import io.whitefox.api.deltasharing.model.v1.generated.QueryRequest;
import io.whitefox.api.deltasharing.model.v1.generated.*;
import io.whitefox.api.deltasharing.serializers.TableMetadataSerializer;
import io.whitefox.api.deltasharing.serializers.TableQueryResponseSerializer;
import io.whitefox.api.deltasharing.server.v1.generated.DeltaApiApi;
Expand Down Expand Up @@ -51,8 +48,10 @@ public DeltaSharesApiImpl(
@Override
public Response getShare(String share) {
return wrapExceptions(
() ->
optionalToNotFound(shareService.getShare(share), s -> Response.ok(s).build()),
() -> optionalToNotFound(shareService.getShare(share), s -> {
var resultShare = new Share().name(s.name()).id(s.id());
return Response.ok(resultShare).build();
}),
exceptionToResponse);
}

Expand Down Expand Up @@ -85,9 +84,15 @@ public Response getTableMetadata(
clientCapabilitiesMapper.parseDeltaSharingCapabilities(deltaSharingCapabilities);
return optionalToNotFound(
deltaSharesService.getTableMetadata(
share, schema, table, startingTimestamp, clientCapabilities),
share,
schema,
table,
startingTimestamp,
clientCapabilities,
getRequestPrincipal()),
m -> optionalToNotFound(
deltaSharesService.getTableVersion(share, schema, table, startingTimestamp),
deltaSharesService.getTableVersion(
share, schema, table, startingTimestamp, getRequestPrincipal()),
v -> Response.ok(
tableResponseSerializer.serialize(
DeltaMappers.toTableResponseMetadata(m)),
Expand All @@ -104,12 +109,12 @@ public Response getTableMetadata(
@Override
public Response getTableVersion(
String share, String schema, String table, String startingTimestampStr) {

return wrapExceptions(
() -> {
var startingTimestamp = parseTimestamp(startingTimestampStr);
return optionalToNotFound(
deltaSharesService.getTableVersion(share, schema, table, startingTimestamp),
deltaSharesService.getTableVersion(
share, schema, table, startingTimestamp, getRequestPrincipal()),
t -> Response.ok().header(DELTA_TABLE_VERSION_HEADER, t).build());
},
exceptionToResponse);
Expand All @@ -120,7 +125,10 @@ public Response listALLTables(String share, Integer maxResults, String pageToken
return wrapExceptions(
() -> optionalToNotFound(
deltaSharesService.listTablesOfShare(
share, parseToken(pageToken), Optional.ofNullable(maxResults)),
share,
parseToken(pageToken),
Optional.ofNullable(maxResults),
getRequestPrincipal()),
c -> Response.ok(c.getToken()
.map(t -> new ListTablesResponse()
.items(mapList(c.getContent(), DeltaMappers::table2api))
Expand All @@ -136,7 +144,11 @@ public Response listSchemas(String share, Integer maxResults, String pageToken)
return wrapExceptions(
() -> optionalToNotFound(
deltaSharesService
.listSchemas(share, parseToken(pageToken), Optional.ofNullable(maxResults))
.listSchemas(
share,
parseToken(pageToken),
Optional.ofNullable(maxResults),
getRequestPrincipal())
.map(ct -> ct.getToken()
.map(t -> new ListSchemasResponse()
.nextPageToken(tokenEncoder.encodePageToken(t))
Expand All @@ -151,8 +163,8 @@ public Response listSchemas(String share, Integer maxResults, String pageToken)
public Response listShares(Integer maxResults, String pageToken) {
return wrapExceptions(
() -> {
var c =
deltaSharesService.listShares(parseToken(pageToken), Optional.ofNullable(maxResults));
var c = deltaSharesService.listShares(
parseToken(pageToken), Optional.ofNullable(maxResults), getRequestPrincipal());
var response =
new ListShareResponse().items(mapList(c.getContent(), DeltaMappers::share2api));
return Response.ok(c.getToken()
Expand All @@ -168,7 +180,11 @@ public Response listTables(String share, String schema, Integer maxResults, Stri
return wrapExceptions(
() -> optionalToNotFound(
deltaSharesService.listTables(
share, schema, parseToken(pageToken), Optional.ofNullable(maxResults)),
share,
schema,
parseToken(pageToken),
Optional.ofNullable(maxResults),
getRequestPrincipal()),
c -> Response.ok(c.getToken()
.map(t -> new ListTablesResponse()
.items(mapList(c.getContent(), DeltaMappers::table2api))
Expand Down Expand Up @@ -201,7 +217,8 @@ public Response queryTable(
schema,
table,
DeltaMappers.api2ReadTableRequest(queryRequest),
clientCapabilitiesMapper.parseDeltaSharingCapabilities(deltaSharingCapabilities));
clientCapabilitiesMapper.parseDeltaSharingCapabilities(deltaSharingCapabilities),
getRequestPrincipal());
var serializedReadResult =
tableQueryResponseSerializer.serialize(DeltaMappers.readTableResult2api(readResult));
return Response.ok(serializedReadResult, ndjsonMediaType)
Expand Down
13 changes: 13 additions & 0 deletions server/app/src/main/java/io/whitefox/api/server/ApiUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import io.whitefox.core.Principal;
import io.whitefox.core.services.exceptions.AlreadyExists;
import io.whitefox.core.services.exceptions.NotFound;
import jakarta.ws.rs.ForbiddenException;
import jakarta.ws.rs.core.Response;
import java.sql.Timestamp;
import java.time.OffsetDateTime;
Expand Down Expand Up @@ -45,6 +46,12 @@ public interface ApiUtils extends DeltaHeaders {
.errorCode("BAD REQUEST - timestamp provided is not formatted correctly")
.message(ExceptionUtil.generateStackTrace(t)))
.build();
} else if (t instanceof ForbiddenException) {
return Response.status(Response.Status.FORBIDDEN)
.entity(new CommonErrorResponse()
.errorCode("FORBIDDEN ACCESS")
.message(ExceptionUtil.generateStackTrace(t)))
.build();
} else {
return Response.status(Response.Status.BAD_GATEWAY)
.entity(new CommonErrorResponse()
Expand All @@ -68,6 +75,12 @@ default Response notFoundResponse() {
.build();
}

default Response forbiddenResponse() {
return Response.status(Response.Status.FORBIDDEN)
.entity(new CommonErrorResponse().errorCode("2").message("UNAUTHORIZED ACCESS"))
.build();
}

default <T> Response optionalToNotFound(Optional<T> opt, Function<T, Response> fn) {
return opt.map(fn).orElse(notFoundResponse());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,39 @@ public static InternalTable s3IcebergTable1(
public static final InternalTable deltaTableWithHistory1 = deltaTable("delta-table-with-history");

public static StorageManager createStorageManager() {
return new InMemoryStorageManager(List.of(new io.whitefox.core.Share(
"name",
"key",
Map.of(
"default",
new io.whitefox.core.Schema(
return new InMemoryStorageManager(List.of(
new io.whitefox.core.Share(
"name",
"key",
Map.of(
"default",
List.of(
new SharedTable("table1", "default", "name", deltaTable1),
new SharedTable(
"table-with-history", "default", "name", deltaTableWithHistory1),
new SharedTable("icebergtable1", "default", "name", icebergtable1),
new SharedTable("icebergtable2", "default", "name", icebergtable2)),
"name")),
testPrincipal,
0L)));
new io.whitefox.core.Schema(
"default",
List.of(
new SharedTable("table1", "default", "name", deltaTable1),
new SharedTable(
"table-with-history", "default", "name", deltaTableWithHistory1),
new SharedTable("icebergtable1", "default", "name", icebergtable1),
new SharedTable("icebergtable2", "default", "name", icebergtable2)),
"name")),
testPrincipal,
0L),
new io.whitefox.core.Share(
"noauthShare",
"key",
Map.of(
"default",
new io.whitefox.core.Schema(
"default",
List.of(
new SharedTable("table1", "default", "name", deltaTable1),
new SharedTable(
"table-with-history", "default", "name", deltaTableWithHistory1),
new SharedTable("icebergtable1", "default", "name", icebergtable1),
new SharedTable("icebergtable2", "default", "name", icebergtable2)),
"name")),
new Principal("Mr. White"),
0L)));
}

public static final ParquetMetadata deltaTable1Metadata = ParquetMetadata.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ public void updateStorageManagerWithS3Tables() {
"s3share",
s3IcebergTable1(s3TestConfig, awsGlueTestConfig))),
"s3share")),
new Principal("Mr fox"),
new Principal("Mr. Fox"),
0L));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ public void listNotFoundSchemas() {
.body("message", is("NOT FOUND"));
}

@Test
public void listSchemasNoAuth() {
given()
.when()
.filter(deltaFilter)
.get("delta-api/v1/shares/{share}/schemas", "noauthShare")
.then()
.statusCode(403);
}

@Test
public void listSchemas() {
given()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ void createShare() {
.statusCode(201)
.body("name", is("share1"))
.body("comment", is(nullValue()))
.body("recipients", is(hasSize(0)))
.body("recipients", is(hasSize(1)))
.body("schemas", is(hasSize(0)))
.body("createdAt", is(0))
.body("createdBy", is("Mr. Fox"))
Expand Down Expand Up @@ -89,7 +89,7 @@ void addRecipientsToShare() {
.statusCode(200)
.body("name", is("share1"))
.body("comment", is(nullValue()))
.body("recipients", is(hasSize(3)))
.body("recipients", is(hasSize(4)))
.body("schemas", is(hasSize(0)))
.body("createdAt", is(0))
.body("createdBy", is("Mr. Fox"))
Expand All @@ -105,7 +105,7 @@ void addSameRecipientTwice() {
.statusCode(200)
.body("name", is("share1"))
.body("comment", is(nullValue()))
.body("recipients", is(hasSize(3)))
.body("recipients", is(hasSize(4)))
.body("schemas", is(hasSize(0)))
.body("createdAt", is(0))
.body("createdBy", is("Mr. Fox"))
Expand All @@ -121,7 +121,7 @@ void addAnotherRecipient() {
.statusCode(200)
.body("name", is("share1"))
.body("comment", is(nullValue()))
.body("recipients", is(hasSize(4)))
.body("recipients", is(hasSize(5)))
.body("schemas", is(hasSize(0)))
.body("createdAt", is(0))
.body("createdBy", is("Mr. Fox"))
Expand All @@ -143,7 +143,7 @@ public void createSchema() {
.statusCode(201)
.body("name", is("share1"))
.body("comment", is(nullValue()))
.body("recipients", is(hasSize(4)))
.body("recipients", is(hasSize(5)))
.body("schemas", is(hasSize(1)))
.body("schemas[0]", is("schema1"))
.body("createdAt", is(0))
Expand Down Expand Up @@ -185,7 +185,7 @@ public void addTableToSchema() {
.statusCode(201)
.body("name", is("share1"))
.body("comment", is(nullValue()))
.body("recipients", is(hasSize(4)))
.body("recipients", is(hasSize(5)))
.body("schemas", is(hasSize(1)))
.body("schemas[0]", is("schema1"))
.body("createdAt", is(0))
Expand All @@ -200,7 +200,7 @@ ValidatableResponse createEmptyShare(String name) {
.when()
.filter(whitefoxFilter)
.body(
new CreateShareInput().name(name).recipients(List.of()).schemas(List.of()),
new CreateShareInput().name(name).recipients(List.of("Mr. Fox")).schemas(List.of()),
new Jackson2Mapper((cls, charset) -> objectMapper))
.header(new Header("Content-Type", "application/json"))
.post("/whitefox-api/v1/shares")
Expand Down
3 changes: 2 additions & 1 deletion server/app/src/test/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
quarkus.http.test-port=8080
quarkus.http.test-port=8080
quarkus.test.arg-line=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005
4 changes: 2 additions & 2 deletions server/core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ dependencies {
implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}"))
// QUARKUS
compileOnly("jakarta.enterprise:jakarta.enterprise.cdi-api")
compileOnly("jakarta.ws.rs:jakarta.ws.rs-api")
implementation("jakarta.ws.rs:jakarta.ws.rs-api")
compileOnly("org.eclipse.microprofile.config:microprofile-config-api")

implementation("org.glassfish.jersey.core:jersey-common:3.1.2")

testFixturesImplementation("jakarta.inject:jakarta.inject-api")
testFixturesImplementation("org.eclipse.microprofile.config:microprofile-config-api")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.whitefox.core;

import jakarta.enterprise.context.ApplicationScoped;
import lombok.Data;

public interface WhitefoxAuthorization {

Boolean authorize(Share share, Principal principal);

@Data
@ApplicationScoped
class WhitefoxSimpleAuthorization implements WhitefoxAuthorization {

@Override
public Boolean authorize(Share share, Principal principal) {
return share.recipients().contains(principal) || share.owner().equals(principal);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,34 +13,49 @@
public interface DeltaSharesService {

Optional<Long> getTableVersion(
String share, String schema, String table, Optional<Timestamp> startingTimestamp);
String share,
String schema,
String table,
Optional<Timestamp> startingTimestamp,
Principal principal);

ContentAndToken<List<Share>> listShares(
Optional<ContentAndToken.Token> nextPageToken, Optional<Integer> maxResults);
Optional<ContentAndToken.Token> nextPageToken,
Optional<Integer> maxResults,
Principal currentPrincipal);

Optional<Metadata> getTableMetadata(
String share,
String schema,
String table,
Optional<Timestamp> startingTimestamp,
ClientCapabilities clientCapabilities);
ClientCapabilities clientCapabilities,
Principal currentPrincipal);

Optional<ContentAndToken<List<Schema>>> listSchemas(
String share, Optional<ContentAndToken.Token> nextPageToken, Optional<Integer> maxResults);
String share,
Optional<ContentAndToken.Token> nextPageToken,
Optional<Integer> maxResults,
Principal currentPrincipal);

Optional<ContentAndToken<List<SharedTable>>> listTables(
String share,
String schema,
Optional<ContentAndToken.Token> nextPageToken,
Optional<Integer> maxResults);
Optional<Integer> maxResults,
Principal currentPrincipal);

Optional<ContentAndToken<List<SharedTable>>> listTablesOfShare(
String share, Optional<ContentAndToken.Token> token, Optional<Integer> maxResults);
String share,
Optional<ContentAndToken.Token> token,
Optional<Integer> maxResults,
Principal currentPrincipal);

ReadTableResult queryTable(
String share,
String schema,
String table,
ReadTableRequest queryRequest,
ClientCapabilities clientCapabilities);
ClientCapabilities clientCapabilities,
Principal currentPrincipal);
}
Loading