Skip to content

Commit

Permalink
fixed share serde on getShare call
Browse files Browse the repository at this point in the history
fixed recipients and id of share

basic authorization based on the request principal

auth into separate class

forbidden exception handling + owner of share authorized

formatting
  • Loading branch information
duhizjame authored and agilelab-tmnd1991 committed Apr 5, 2024
1 parent 689fce5 commit dbe6492
Show file tree
Hide file tree
Showing 14 changed files with 410 additions and 184 deletions.
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

0 comments on commit dbe6492

Please sign in to comment.