Skip to content

Commit

Permalink
Adding filtering the reports based on tenants.
Browse files Browse the repository at this point in the history
When user=null or tenant=null, tenant is considered ""
The APIs match for tenant info from user context
  • Loading branch information
akbhatta committed Nov 24, 2020
1 parent 0bbab87 commit e05cb36
Show file tree
Hide file tree
Showing 12 changed files with 93 additions and 21 deletions.
2 changes: 1 addition & 1 deletion reports-scheduler/src/main/config/reports-scheduler.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ opendistro.reports:
# adminAccess values:
## Standard -> Admin user access follows standard user
## AllReports -> Admin user with "all_access" role can see all reports of all users.
filterBy: "NoFilter"
filterBy: "NoFilter" # Applied when tenant != __user__
# filterBy values:
## NoFilter -> everyone see each other's reports
## User -> reports are visible to only themselves
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ internal object ReportDefinitionActions {
val reportDefinitionDetails = ReportDefinitionDetails("ignore",
currentTime,
currentTime,
UserAccessManager.getUserTenant(user),
UserAccessManager.getAllAccessInfo(user),
request.reportDefinition
)
Expand All @@ -74,13 +75,14 @@ internal object ReportDefinitionActions {
val currentReportDefinitionDetails = ReportDefinitionsIndex.getReportDefinition(request.reportDefinitionId)
currentReportDefinitionDetails
?: throw ElasticsearchStatusException("Report Definition ${request.reportDefinitionId} not found", RestStatus.NOT_FOUND)
if (!UserAccessManager.doesUserHasAccess(user, currentReportDefinitionDetails.access)) {
if (!UserAccessManager.doesUserHasAccess(user, currentReportDefinitionDetails.tenant, currentReportDefinitionDetails.access)) {
throw ElasticsearchStatusException("Permission denied for Report Definition ${request.reportDefinitionId}", RestStatus.FORBIDDEN)
}
val currentTime = Instant.now()
val reportDefinitionDetails = ReportDefinitionDetails(request.reportDefinitionId,
currentTime,
currentReportDefinitionDetails.createdTime,
UserAccessManager.getUserTenant(user),
currentReportDefinitionDetails.access,
request.reportDefinition
)
Expand All @@ -101,7 +103,7 @@ internal object ReportDefinitionActions {
val reportDefinitionDetails = ReportDefinitionsIndex.getReportDefinition(request.reportDefinitionId)
reportDefinitionDetails
?: throw ElasticsearchStatusException("Report Definition ${request.reportDefinitionId} not found", RestStatus.NOT_FOUND)
if (!UserAccessManager.doesUserHasAccess(user, reportDefinitionDetails.access)) {
if (!UserAccessManager.doesUserHasAccess(user, reportDefinitionDetails.tenant, reportDefinitionDetails.access)) {
throw ElasticsearchStatusException("Permission denied for Report Definition ${request.reportDefinitionId}", RestStatus.FORBIDDEN)
}
return GetReportDefinitionResponse(reportDefinitionDetails, UserAccessManager.hasAllInfoAccess(user))
Expand All @@ -118,7 +120,7 @@ internal object ReportDefinitionActions {
val reportDefinitionDetails = ReportDefinitionsIndex.getReportDefinition(request.reportDefinitionId)
reportDefinitionDetails
?: throw ElasticsearchStatusException("Report Definition ${request.reportDefinitionId} not found", RestStatus.NOT_FOUND)
if (!UserAccessManager.doesUserHasAccess(user, reportDefinitionDetails.access)) {
if (!UserAccessManager.doesUserHasAccess(user, reportDefinitionDetails.tenant, reportDefinitionDetails.access)) {
throw ElasticsearchStatusException("Permission denied for Report Definition ${request.reportDefinitionId}", RestStatus.FORBIDDEN)
}
if (!ReportDefinitionsIndex.deleteReportDefinition(request.reportDefinitionId)) {
Expand All @@ -135,7 +137,8 @@ internal object ReportDefinitionActions {
fun getAll(request: GetAllReportDefinitionsRequest, user: User?): GetAllReportDefinitionsResponse {
log.info("$LOG_PREFIX:ReportDefinition-getAll fromIndex:${request.fromIndex} maxItems:${request.maxItems}")
UserAccessManager.validateUser(user)
val reportDefinitionsList = ReportDefinitionsIndex.getAllReportDefinitions(UserAccessManager.getSearchAccessInfo(user),
val reportDefinitionsList = ReportDefinitionsIndex.getAllReportDefinitions(UserAccessManager.getUserTenant(user),
UserAccessManager.getSearchAccessInfo(user),
request.fromIndex,
request.maxItems)
return GetAllReportDefinitionsResponse(reportDefinitionsList, UserAccessManager.hasAllInfoAccess(user))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ internal object ReportInstanceActions {
currentTime,
request.beginTime,
request.endTime,
UserAccessManager.getUserTenant(user),
UserAccessManager.getAllAccessInfo(user),
request.reportDefinitionDetails,
Status.Success, // TODO: Revert to request.status when background job execution supported
Expand All @@ -84,7 +85,7 @@ internal object ReportInstanceActions {
val reportDefinitionDetails = ReportDefinitionsIndex.getReportDefinition(request.reportDefinitionId)
reportDefinitionDetails
?: throw ElasticsearchStatusException("Report Definition ${request.reportDefinitionId} not found", RestStatus.NOT_FOUND)
if (!UserAccessManager.doesUserHasAccess(user, reportDefinitionDetails.access)) {
if (!UserAccessManager.doesUserHasAccess(user, reportDefinitionDetails.tenant, reportDefinitionDetails.access)) {
throw ElasticsearchStatusException("Permission denied for Report Definition ${request.reportDefinitionId}", RestStatus.FORBIDDEN)
}
val beginTime: Instant = currentTime.minus(reportDefinitionDetails.reportDefinition.format.duration)
Expand All @@ -95,6 +96,7 @@ internal object ReportInstanceActions {
currentTime,
beginTime,
endTime,
UserAccessManager.getUserTenant(user),
reportDefinitionDetails.access,
reportDefinitionDetails,
currentStatus)
Expand All @@ -115,7 +117,7 @@ internal object ReportInstanceActions {
val currentReportInstance = ReportInstancesIndex.getReportInstance(request.reportInstanceId)
currentReportInstance
?: throw ElasticsearchStatusException("Report Instance ${request.reportInstanceId} not found", RestStatus.NOT_FOUND)
if (!UserAccessManager.doesUserHasAccess(user, currentReportInstance.access)) {
if (!UserAccessManager.doesUserHasAccess(user, currentReportInstance.tenant, currentReportInstance.access)) {
throw ElasticsearchStatusException("Permission denied for Report Definition ${request.reportInstanceId}", RestStatus.FORBIDDEN)
}
if (request.status == Status.Scheduled) { // Don't allow changing status to Scheduled
Expand All @@ -142,7 +144,7 @@ internal object ReportInstanceActions {
val reportInstance = ReportInstancesIndex.getReportInstance(request.reportInstanceId)
reportInstance
?: throw ElasticsearchStatusException("Report Instance ${request.reportInstanceId} not found", RestStatus.NOT_FOUND)
if (!UserAccessManager.doesUserHasAccess(user, reportInstance.access)) {
if (!UserAccessManager.doesUserHasAccess(user, reportInstance.tenant, reportInstance.access)) {
throw ElasticsearchStatusException("Permission denied for Report Definition ${request.reportInstanceId}", RestStatus.FORBIDDEN)
}
return GetReportInstanceResponse(reportInstance, UserAccessManager.hasAllInfoAccess(user))
Expand All @@ -156,7 +158,8 @@ internal object ReportInstanceActions {
fun getAll(request: GetAllReportInstancesRequest, user: User?): GetAllReportInstancesResponse {
log.info("$LOG_PREFIX:ReportInstance-getAll fromIndex:${request.fromIndex} maxItems:${request.maxItems}")
UserAccessManager.validateUser(user)
val reportInstanceList = ReportInstancesIndex.getAllReportInstances(UserAccessManager.getSearchAccessInfo(user),
val reportInstanceList = ReportInstancesIndex.getAllReportInstances(UserAccessManager.getUserTenant(user),
UserAccessManager.getSearchAccessInfo(user),
request.fromIndex,
request.maxItems)
return GetAllReportInstancesResponse(reportInstanceList, UserAccessManager.hasAllInfoAccess(user))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.amazon.opendistroforelasticsearch.reportsscheduler.ReportsSchedulerPl
import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportDefinitionDetails
import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportDefinitionDetailsSearchResults
import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.ACCESS_LIST_FIELD
import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.TENANT_FIELD
import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.UPDATED_TIME_FIELD
import com.amazon.opendistroforelasticsearch.reportsscheduler.settings.PluginSettings
import com.amazon.opendistroforelasticsearch.reportsscheduler.util.SecureIndexClient
Expand Down Expand Up @@ -147,21 +148,28 @@ internal object ReportDefinitionsIndex {

/**
* Query index for report definition for given access details
* @param tenant the tenant of the user
* @param access the list of access details to search reports for.
* @param from the paginated start index
* @param maxItems the max items to query
* @return search result of Report definition details
*/
fun getAllReportDefinitions(access: List<String>, from: Int, maxItems: Int): ReportDefinitionDetailsSearchResults {
fun getAllReportDefinitions(tenant: String, access: List<String>, from: Int, maxItems: Int): ReportDefinitionDetailsSearchResults {
createIndex()
val sourceBuilder = SearchSourceBuilder()
.timeout(TimeValue(PluginSettings.operationTimeoutMs, TimeUnit.MILLISECONDS))
.sort(UPDATED_TIME_FIELD)
.size(maxItems)
.from(from)
val tenantQuery = QueryBuilders.termsQuery(TENANT_FIELD, tenant)
if (access.isNotEmpty()) {
val query = QueryBuilders.termsQuery(ACCESS_LIST_FIELD, access)
val accessQuery = QueryBuilders.termsQuery(ACCESS_LIST_FIELD, access)
val query = QueryBuilders.boolQuery()
query.filter(tenantQuery)
query.filter(accessQuery)
sourceBuilder.query(query)
} else {
sourceBuilder.query(tenantQuery)
}
val searchRequest = SearchRequest()
.indices(REPORT_DEFINITIONS_INDEX_NAME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportInstan
import com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportInstanceSearchResults
import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.ACCESS_LIST_FIELD
import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.STATUS_FIELD
import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.TENANT_FIELD
import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.UPDATED_TIME_FIELD
import com.amazon.opendistroforelasticsearch.reportsscheduler.settings.PluginSettings
import com.amazon.opendistroforelasticsearch.reportsscheduler.util.SecureIndexClient
Expand Down Expand Up @@ -151,21 +152,28 @@ internal object ReportInstancesIndex {

/**
* Query index for report instance for given access details
* @param tenant the tenant of the user
* @param access the list of access details to search reports for.
* @param from the paginated start index
* @param maxItems the max items to query
* @return search result of Report instance details
*/
fun getAllReportInstances(access: List<String>, from: Int, maxItems: Int): ReportInstanceSearchResults {
fun getAllReportInstances(tenant: String, access: List<String>, from: Int, maxItems: Int): ReportInstanceSearchResults {
createIndex()
val sourceBuilder = SearchSourceBuilder()
.timeout(TimeValue(PluginSettings.operationTimeoutMs, TimeUnit.MILLISECONDS))
.sort(UPDATED_TIME_FIELD)
.size(maxItems)
.from(from)
val tenantQuery = QueryBuilders.termsQuery(TENANT_FIELD, tenant)
if (access.isNotEmpty()) {
val query = QueryBuilders.termsQuery(ACCESS_LIST_FIELD, access)
val accessQuery = QueryBuilders.termsQuery(ACCESS_LIST_FIELD, access)
val query = QueryBuilders.boolQuery()
query.filter(tenantQuery)
query.filter(accessQuery)
sourceBuilder.query(query)
} else {
sourceBuilder.query(tenantQuery)
}
val searchRequest = SearchRequest()
.indices(REPORT_INSTANCES_INDEX_NAME)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.ACCE
import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.CREATED_TIME_FIELD
import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.ID_FIELD
import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.REPORT_DEFINITION_FIELD
import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.TENANT_FIELD
import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.UPDATED_TIME_FIELD
import com.amazon.opendistroforelasticsearch.reportsscheduler.security.UserAccessManager.DEFAULT_TENANT
import com.amazon.opendistroforelasticsearch.reportsscheduler.settings.PluginSettings
import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger
import com.amazon.opendistroforelasticsearch.reportsscheduler.util.stringList
Expand All @@ -44,7 +46,8 @@ import java.time.Instant
* "id":"id",
* "lastUpdatedTimeMs":1603506908773,
* "createdTimeMs":1603506908773,
* "access":["u:user", "r:sample_role", "ber:sample_backend_role"]
* "tenant":"__user__",
* "access":["User:user", "Role:sample_role", "BERole:sample_backend_role"]
* "reportDefinition":{
* // refer [com.amazon.opendistroforelasticsearch.reportsscheduler.model.ReportDefinition]
* }
Expand All @@ -55,6 +58,7 @@ internal data class ReportDefinitionDetails(
val id: String,
val updatedTime: Instant,
val createdTime: Instant,
val tenant: String,
val access: List<String>,
val reportDefinition: ReportDefinition
) : ScheduledJobParameter {
Expand All @@ -71,6 +75,7 @@ internal data class ReportDefinitionDetails(
var id: String? = useId
var updatedTime: Instant? = null
var createdTime: Instant? = null
var tenant: String? = null
var access: List<String> = listOf()
var reportDefinition: ReportDefinition? = null
XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser)
Expand All @@ -81,6 +86,7 @@ internal data class ReportDefinitionDetails(
ID_FIELD -> id = parser.text()
UPDATED_TIME_FIELD -> updatedTime = Instant.ofEpochMilli(parser.longValue())
CREATED_TIME_FIELD -> createdTime = Instant.ofEpochMilli(parser.longValue())
TENANT_FIELD -> tenant = parser.text()
ACCESS_LIST_FIELD -> access = parser.stringList()
REPORT_DEFINITION_FIELD -> reportDefinition = ReportDefinition.parse(parser)
else -> {
Expand All @@ -92,10 +98,12 @@ internal data class ReportDefinitionDetails(
id ?: throw IllegalArgumentException("$ID_FIELD field absent")
updatedTime ?: throw IllegalArgumentException("$UPDATED_TIME_FIELD field absent")
createdTime ?: throw IllegalArgumentException("$CREATED_TIME_FIELD field absent")
tenant = tenant ?: DEFAULT_TENANT
reportDefinition ?: throw IllegalArgumentException("$REPORT_DEFINITION_FIELD field absent")
return ReportDefinitionDetails(id,
updatedTime,
createdTime,
tenant,
access,
reportDefinition)
}
Expand All @@ -121,6 +129,7 @@ internal data class ReportDefinitionDetails(
}
builder.field(UPDATED_TIME_FIELD, updatedTime.toEpochMilli())
.field(CREATED_TIME_FIELD, createdTime.toEpochMilli())
.field(TENANT_FIELD, tenant)
if (params?.paramAsBoolean(ACCESS_LIST_FIELD, true) == true && access.isNotEmpty()) {
builder.field(ACCESS_LIST_FIELD, access)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.IN_C
import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.REPORT_DEFINITION_DETAILS_FIELD
import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.STATUS_FIELD
import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.STATUS_TEXT_FIELD
import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.TENANT_FIELD
import com.amazon.opendistroforelasticsearch.reportsscheduler.model.RestTag.UPDATED_TIME_FIELD
import com.amazon.opendistroforelasticsearch.reportsscheduler.security.UserAccessManager.DEFAULT_TENANT
import com.amazon.opendistroforelasticsearch.reportsscheduler.util.logger
import com.amazon.opendistroforelasticsearch.reportsscheduler.util.stringList
import org.elasticsearch.common.xcontent.ToXContent
Expand All @@ -48,7 +50,8 @@ import java.time.Instant
* "createdTimeMs":1603506908773,
* "beginTimeMs":1603506908773,
* "endTimeMs":1603506908773,
* "access":["u:user", "r:sample_role", "ber:sample_backend_role"]
* "tenant":"__user__",
* "access":["User:user", "Role:sample_role", "BERole:sample_backend_role"]
* "reportDefinitionDetails":{
* // refer [com.amazon.opendistroforelasticsearch.reportsscheduler.model.reportDefinitionDetails]
* },
Expand All @@ -64,6 +67,7 @@ internal data class ReportInstance(
val createdTime: Instant,
val beginTime: Instant,
val endTime: Instant,
val tenant: String,
val access: List<String>,
val reportDefinitionDetails: ReportDefinitionDetails?,
val status: Status,
Expand All @@ -86,6 +90,7 @@ internal data class ReportInstance(
var createdTime: Instant? = null
var beginTime: Instant? = null
var endTime: Instant? = null
var tenant: String? = null
var access: List<String> = listOf()
var reportDefinitionDetails: ReportDefinitionDetails? = null
var status: Status? = null
Expand All @@ -101,6 +106,7 @@ internal data class ReportInstance(
CREATED_TIME_FIELD -> createdTime = Instant.ofEpochMilli(parser.longValue())
BEGIN_TIME_FIELD -> beginTime = Instant.ofEpochMilli(parser.longValue())
END_TIME_FIELD -> endTime = Instant.ofEpochMilli(parser.longValue())
TENANT_FIELD -> tenant = parser.text()
ACCESS_LIST_FIELD -> access = parser.stringList()
REPORT_DEFINITION_DETAILS_FIELD -> reportDefinitionDetails = ReportDefinitionDetails.parse(parser)
STATUS_FIELD -> status = Status.valueOf(parser.text())
Expand All @@ -117,12 +123,14 @@ internal data class ReportInstance(
createdTime ?: throw IllegalArgumentException("$CREATED_TIME_FIELD field absent")
beginTime ?: throw IllegalArgumentException("$BEGIN_TIME_FIELD field absent")
endTime ?: throw IllegalArgumentException("$END_TIME_FIELD field absent")
tenant = tenant ?: DEFAULT_TENANT
status ?: throw IllegalArgumentException("$STATUS_FIELD field absent")
return ReportInstance(id,
updatedTime,
createdTime,
beginTime,
endTime,
tenant,
access,
reportDefinitionDetails,
status,
Expand Down Expand Up @@ -153,6 +161,7 @@ internal data class ReportInstance(
.field(CREATED_TIME_FIELD, createdTime.toEpochMilli())
.field(BEGIN_TIME_FIELD, beginTime.toEpochMilli())
.field(END_TIME_FIELD, endTime.toEpochMilli())
.field(TENANT_FIELD, tenant)
if (params?.paramAsBoolean(ACCESS_LIST_FIELD, true) == true && access.isNotEmpty()) {
builder.field(ACCESS_LIST_FIELD, access)
}
Expand Down
Loading

0 comments on commit e05cb36

Please sign in to comment.