-
Notifications
You must be signed in to change notification settings - Fork 52
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
[openhouse] Create GET /accesss endpoint for fetching DataAccessCredentials for a given table #243
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.linkedin.openhouse.cluster.storage.auth; | ||
|
||
import com.fasterxml.jackson.annotation.JsonProperty; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import java.util.Map; | ||
import lombok.Builder; | ||
import lombok.Value; | ||
|
||
@Value | ||
@Builder | ||
public class DataAccessCredential { | ||
|
||
@Schema( | ||
description = "Map with the access credentials", | ||
example = "{'token':'header.payload.signature', 'path':'/my/table'}") | ||
@JsonProperty(access = JsonProperty.Access.READ_ONLY) | ||
private Map<String, String> credential; | ||
|
||
@Schema(description = "Expiration date of token in millis since epoch", example = "0") | ||
@JsonProperty(access = JsonProperty.Access.READ_ONLY) | ||
private long expirationMillisSinceEpoch; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,23 @@ | ||
package com.linkedin.openhouse.tables.api.handler.impl; | ||
|
||
import com.linkedin.openhouse.cluster.configs.ClusterProperties; | ||
import com.linkedin.openhouse.cluster.storage.auth.DataAccessCredential; | ||
import com.linkedin.openhouse.common.api.spec.ApiResponse; | ||
import com.linkedin.openhouse.common.exception.UnsupportedClientOperationException; | ||
import com.linkedin.openhouse.tables.api.handler.TablesApiHandler; | ||
import com.linkedin.openhouse.tables.api.spec.v0.request.CreateUpdateTableRequestBody; | ||
import com.linkedin.openhouse.tables.api.spec.v0.request.UpdateAclPoliciesRequestBody; | ||
import com.linkedin.openhouse.tables.api.spec.v0.response.GetAclPoliciesResponseBody; | ||
import com.linkedin.openhouse.tables.api.spec.v0.response.GetAllTablesResponseBody; | ||
import com.linkedin.openhouse.tables.api.spec.v0.response.GetDataAccessCredentialResponseBody; | ||
import com.linkedin.openhouse.tables.api.spec.v0.response.GetTableResponseBody; | ||
import com.linkedin.openhouse.tables.api.validator.TablesApiValidator; | ||
import com.linkedin.openhouse.tables.dto.mapper.TablesMapper; | ||
import com.linkedin.openhouse.tables.model.TableDto; | ||
import com.linkedin.openhouse.tables.services.TablesService; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.stream.Collectors; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.data.util.Pair; | ||
|
@@ -24,6 +30,14 @@ | |
@Component | ||
public class OpenHouseTablesApiHandler implements TablesApiHandler { | ||
|
||
// Default DataAccessCredential that is returned when no credential is generated for a given | ||
// table. | ||
private static final DataAccessCredential UNSUPPORTED_DATA_ACCESS_CREDENTIAL = | ||
DataAccessCredential.builder() | ||
.credential(new HashMap<>()) | ||
.expirationMillisSinceEpoch(-1) | ||
.build(); | ||
|
||
@Autowired private TablesApiValidator tablesApiValidator; | ||
|
||
@Autowired private TablesService tableService; | ||
|
@@ -140,4 +154,27 @@ public ApiResponse<GetAclPoliciesResponseBody> getAclPoliciesForUserPrincipal( | |
.build()) | ||
.build(); | ||
} | ||
|
||
@Override | ||
public ApiResponse<GetDataAccessCredentialResponseBody> getDataAccessCredential( | ||
String databaseId, String tableId, Map<String, String> params) { | ||
Optional<DataAccessCredential> dataAccessCredential = | ||
tableService.getDataAccessCredential(databaseId, tableId, params); | ||
|
||
if (!dataAccessCredential.isPresent()) { | ||
throw new UnsupportedClientOperationException( | ||
UnsupportedClientOperationException.Operation.DATA_ACCESS_CREDENTIAL_UNSUPPORTED, | ||
"Unable to get a DataAccessCredential for the given table."); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add the table id and database id in the response message? |
||
} | ||
|
||
return ApiResponse.<GetDataAccessCredentialResponseBody>builder() | ||
.httpStatus(HttpStatus.OK) | ||
.responseBody( | ||
GetDataAccessCredentialResponseBody.builder() | ||
.credential(dataAccessCredential.get().getCredential()) | ||
.expirationMillisSinceEpoch( | ||
dataAccessCredential.get().getExpirationMillisSinceEpoch()) | ||
.build()) | ||
.build(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package com.linkedin.openhouse.tables.api.spec.v0.response; | ||
|
||
import com.fasterxml.jackson.annotation.JsonProperty; | ||
import com.google.gson.Gson; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import java.util.Map; | ||
import lombok.Builder; | ||
import lombok.Value; | ||
|
||
@Builder(toBuilder = true) | ||
@Value | ||
public class GetDataAccessCredentialResponseBody { | ||
|
||
@Schema( | ||
description = "Map with the access credentials", | ||
example = "{'token':'header.payload.signature', 'path':'/my/table'}") | ||
@JsonProperty(access = JsonProperty.Access.READ_ONLY) | ||
private Map<String, String> credential; | ||
|
||
@Schema(description = "Expiration date of token in millis since epoch", example = "0") | ||
@JsonProperty(access = JsonProperty.Access.READ_ONLY) | ||
private long expirationMillisSinceEpoch; | ||
|
||
public String toJson() { | ||
return new Gson().toJson(this); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,13 +7,16 @@ | |
import com.linkedin.openhouse.tables.api.spec.v0.request.UpdateAclPoliciesRequestBody; | ||
import com.linkedin.openhouse.tables.api.spec.v0.response.GetAclPoliciesResponseBody; | ||
import com.linkedin.openhouse.tables.api.spec.v0.response.GetAllTablesResponseBody; | ||
import com.linkedin.openhouse.tables.api.spec.v0.response.GetDataAccessCredentialResponseBody; | ||
import com.linkedin.openhouse.tables.api.spec.v0.response.GetTableResponseBody; | ||
import com.linkedin.openhouse.tables.authorization.Privileges; | ||
import io.swagger.v3.oas.annotations.Operation; | ||
import io.swagger.v3.oas.annotations.Parameter; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
import io.swagger.v3.oas.annotations.responses.ApiResponses; | ||
import java.util.Collections; | ||
import java.util.Map; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.security.access.annotation.Secured; | ||
|
@@ -291,4 +294,39 @@ public ResponseEntity<GetAclPoliciesResponseBody> getAclPoliciesForUserPrincipal | |
return new ResponseEntity<>( | ||
apiResponse.getResponseBody(), apiResponse.getHttpHeaders(), apiResponse.getHttpStatus()); | ||
} | ||
|
||
@Operation( | ||
summary = "Get temporary credentials to access data in a given table", | ||
description = | ||
"Returns the temporary credentials which have data access to the Table resource identified by by " | ||
+ "databaseId and tableId. The expiration time of the temporary credentials is returned " | ||
+ "in millis since epoch.", | ||
tags = {"Table"}) | ||
@ApiResponses( | ||
value = { | ||
@ApiResponse(responseCode = "200", description = "Access GET: OK"), | ||
@ApiResponse(responseCode = "400", description = "Access GET: ACCESS_UNSUPPORTED"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. HTTP status 400 should be associated with bad request such invalid input is provided. Can we consider some other HTTP code for unsupported? |
||
}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please as table not found as well. |
||
@GetMapping( | ||
value = { | ||
"/v0/databases/{databaseId}/tables/{tableId}/access", | ||
"/v1/databases/{databaseId}/tables/{tableId}/access" | ||
}, | ||
produces = {"application/json"}) | ||
public ResponseEntity<GetDataAccessCredentialResponseBody> getDataAccessCredential( | ||
@Parameter(description = "Database ID", required = true) @PathVariable String databaseId, | ||
@Parameter(description = "Table ID", required = true) @PathVariable String tableId, | ||
@Parameter(description = "Other Params", required = false) @PathVariable | ||
Map<String, String> params) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the params here used for passing STS ID token? |
||
|
||
if (params == null) { | ||
params = Collections.emptyMap(); | ||
} | ||
|
||
com.linkedin.openhouse.common.api.spec.ApiResponse<GetDataAccessCredentialResponseBody> | ||
apiResponse = tablesApiHandler.getDataAccessCredential(databaseId, tableId, params); | ||
|
||
return new ResponseEntity<>( | ||
apiResponse.getResponseBody(), apiResponse.getHttpHeaders(), apiResponse.getHttpStatus()); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,9 @@ | ||
package com.linkedin.openhouse.tables.services; | ||
|
||
import com.linkedin.openhouse.cluster.storage.Storage; | ||
import com.linkedin.openhouse.cluster.storage.StorageManager; | ||
import com.linkedin.openhouse.cluster.storage.StorageType; | ||
import com.linkedin.openhouse.cluster.storage.auth.DataAccessCredential; | ||
import com.linkedin.openhouse.common.api.spec.TableUri; | ||
import com.linkedin.openhouse.common.exception.AlreadyExistsException; | ||
import com.linkedin.openhouse.common.exception.EntityConcurrentModificationException; | ||
|
@@ -18,6 +22,7 @@ | |
import com.linkedin.openhouse.tables.repository.OpenHouseInternalRepository; | ||
import com.linkedin.openhouse.tables.utils.AuthorizationUtils; | ||
import com.linkedin.openhouse.tables.utils.TableUUIDGenerator; | ||
import java.net.URI; | ||
import java.util.List; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
|
@@ -41,6 +46,9 @@ public class TablesServiceImpl implements TablesService { | |
@Autowired AuthorizationHandler authorizationHandler; | ||
|
||
@Autowired TableUUIDGenerator tableUUIDGenerator; | ||
|
||
@Autowired StorageManager storageManager; | ||
|
||
/** | ||
* Lookup a table by databaseId and tableId in OpenHouse's Internal Catalog. | ||
* | ||
|
@@ -211,6 +219,18 @@ public List<AclPolicy> getAclPolicies( | |
return authorizationHandler.listAclPolicies(tableDto, userPrincipal); | ||
} | ||
|
||
@Override | ||
public Optional<DataAccessCredential> getDataAccessCredential( | ||
String databaseId, String tableId, Map<String, String> params) { | ||
TableDto tableDto = getTableOrThrow(databaseId, tableId); | ||
StorageType.Type tableStorageType = | ||
new StorageType().fromString(URI.create(tableDto.getTableLocation()).getScheme()); | ||
|
||
Storage storage = storageManager.getStorage(tableStorageType); | ||
|
||
return storage.getDataAccessCredentialForTableLocation(tableDto.getTableLocation(), params); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the storage layer going to interact with STS service to return the STS Access token? |
||
} | ||
|
||
/** Whether sharing has been enabled for the table denoted by tableDto. */ | ||
private boolean isTableSharingEnabled(TableDto tableDto) { | ||
return (tableDto.getPolicies() != null && tableDto.getPolicies().isSharingEnabled()); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What are the possible key value pairs here? The example shows
token
andpath
. Do we expect any additional details?