From e05cb3667e057c8bfe20262b0f01a00c43a2998e Mon Sep 17 00:00:00 2001 From: Anantha Krishna Bhatta Date: Mon, 23 Nov 2020 17:12:27 -0800 Subject: [PATCH] Adding filtering the reports based on tenants. When user=null or tenant=null, tenant is considered "" The APIs match for tenant info from user context --- .../src/main/config/reports-scheduler.yml | 2 +- .../action/ReportDefinitionActions.kt | 11 ++++-- .../action/ReportInstanceActions.kt | 11 ++++-- .../index/ReportDefinitionsIndex.kt | 12 +++++- .../index/ReportInstancesIndex.kt | 12 +++++- .../model/ReportDefinitionDetails.kt | 11 +++++- .../reportsscheduler/model/ReportInstance.kt | 11 +++++- .../reportsscheduler/model/RestTag.kt | 1 + .../scheduler/ReportDefinitionJobRunner.kt | 1 + .../security/UserAccessManager.kt | 38 ++++++++++++++++--- .../resources/report-definitions-mapping.yml | 2 + .../resources/report-instances-mapping.yml | 2 + 12 files changed, 93 insertions(+), 21 deletions(-) diff --git a/reports-scheduler/src/main/config/reports-scheduler.yml b/reports-scheduler/src/main/config/reports-scheduler.yml index 95b77ba6..7a4c875f 100644 --- a/reports-scheduler/src/main/config/reports-scheduler.yml +++ b/reports-scheduler/src/main/config/reports-scheduler.yml @@ -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 diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/ReportDefinitionActions.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/ReportDefinitionActions.kt index 93d7160c..f35414bf 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/ReportDefinitionActions.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/ReportDefinitionActions.kt @@ -54,6 +54,7 @@ internal object ReportDefinitionActions { val reportDefinitionDetails = ReportDefinitionDetails("ignore", currentTime, currentTime, + UserAccessManager.getUserTenant(user), UserAccessManager.getAllAccessInfo(user), request.reportDefinition ) @@ -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 ) @@ -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)) @@ -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)) { @@ -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)) diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/ReportInstanceActions.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/ReportInstanceActions.kt index 97b2d8bf..082a227f 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/ReportInstanceActions.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/action/ReportInstanceActions.kt @@ -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 @@ -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) @@ -95,6 +96,7 @@ internal object ReportInstanceActions { currentTime, beginTime, endTime, + UserAccessManager.getUserTenant(user), reportDefinitionDetails.access, reportDefinitionDetails, currentStatus) @@ -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 @@ -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)) @@ -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)) diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/ReportDefinitionsIndex.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/ReportDefinitionsIndex.kt index 7f9048ef..b8fbe88f 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/ReportDefinitionsIndex.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/ReportDefinitionsIndex.kt @@ -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 @@ -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, from: Int, maxItems: Int): ReportDefinitionDetailsSearchResults { + fun getAllReportDefinitions(tenant: String, access: List, 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) diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/ReportInstancesIndex.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/ReportInstancesIndex.kt index 37666aa6..595a059f 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/ReportInstancesIndex.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/index/ReportInstancesIndex.kt @@ -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 @@ -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, from: Int, maxItems: Int): ReportInstanceSearchResults { + fun getAllReportInstances(tenant: String, access: List, 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) diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/ReportDefinitionDetails.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/ReportDefinitionDetails.kt index 4192933b..aea937d5 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/ReportDefinitionDetails.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/ReportDefinitionDetails.kt @@ -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 @@ -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] * } @@ -55,6 +58,7 @@ internal data class ReportDefinitionDetails( val id: String, val updatedTime: Instant, val createdTime: Instant, + val tenant: String, val access: List, val reportDefinition: ReportDefinition ) : ScheduledJobParameter { @@ -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 = listOf() var reportDefinition: ReportDefinition? = null XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser) @@ -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 -> { @@ -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) } @@ -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) } diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/ReportInstance.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/ReportInstance.kt index d4f507c9..90bef900 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/ReportInstance.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/ReportInstance.kt @@ -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 @@ -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] * }, @@ -64,6 +67,7 @@ internal data class ReportInstance( val createdTime: Instant, val beginTime: Instant, val endTime: Instant, + val tenant: String, val access: List, val reportDefinitionDetails: ReportDefinitionDetails?, val status: Status, @@ -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 = listOf() var reportDefinitionDetails: ReportDefinitionDetails? = null var status: Status? = null @@ -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()) @@ -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, @@ -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) } diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/RestTag.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/RestTag.kt index c2de3b2a..541a29db 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/RestTag.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/model/RestTag.kt @@ -27,6 +27,7 @@ internal object RestTag { const val STATUS_TEXT_FIELD = "statusText" const val UPDATED_TIME_FIELD = "lastUpdatedTimeMs" const val CREATED_TIME_FIELD = "createdTimeMs" + const val TENANT_FIELD = "tenant" const val ACCESS_LIST_FIELD = "access" const val REPORT_DEFINITION_LIST_FIELD = "reportDefinitionDetailsList" const val REPORT_INSTANCE_LIST_FIELD = "reportInstanceList" diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/scheduler/ReportDefinitionJobRunner.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/scheduler/ReportDefinitionJobRunner.kt index 3a1de01f..d661b28e 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/scheduler/ReportDefinitionJobRunner.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/scheduler/ReportDefinitionJobRunner.kt @@ -48,6 +48,7 @@ internal object ReportDefinitionJobRunner : ScheduledJobRunner { currentTime, beginTime, endTime, + job.tenant, job.access, reportDefinitionDetails, ReportInstance.Status.Success) // TODO: Revert to Scheduled when background job execution supported diff --git a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/security/UserAccessManager.kt b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/security/UserAccessManager.kt index e3f4468c..0d01f2d2 100644 --- a/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/security/UserAccessManager.kt +++ b/reports-scheduler/src/main/kotlin/com/amazon/opendistroforelasticsearch/reportsscheduler/security/UserAccessManager.kt @@ -27,11 +27,13 @@ import java.util.stream.Collectors * Class for checking/filtering user access. */ internal object UserAccessManager { - const val USER_TAG = "User:" - const val ROLE_TAG = "Role:" - const val BACKEND_ROLE_TAG = "BERole:" - const val ALL_ACCESS_ROLE = "all_access" - const val KIBANA_SERVER_USER = "kibanaserver" + private const val USER_TAG = "User:" + private const val ROLE_TAG = "Role:" + private const val BACKEND_ROLE_TAG = "BERole:" + private const val ALL_ACCESS_ROLE = "all_access" + private const val KIBANA_SERVER_USER = "kibanaserver" + private const val PRIVATE_TENANT = "__user__" + const val DEFAULT_TENANT = "" /** * Validate User if eligible to do operation @@ -45,6 +47,10 @@ internal object UserAccessManager { * -> backend roles should be present */ fun validateUser(user: User?) { + if (isUserPrivateTenant(user) && user?.name == null) { + throw ElasticsearchStatusException("User name not provided for private tenant access", + RestStatus.FORBIDDEN) + } when (PluginSettings.filterBy) { FilterBy.NoFilter -> { // No validation } @@ -82,6 +88,13 @@ internal object UserAccessManager { } } + /** + * Get tenant info from user object. + */ + fun getUserTenant(user: User?): String { + return DEFAULT_TENANT // TODO: extract from user object + } + /** * Get all user access info from user object. */ @@ -105,6 +118,9 @@ internal object UserAccessManager { if (user == null) { // Security is disabled return listOf() } + if (isUserPrivateTenant(user)) { + return listOf("$USER_TAG${user.name}") // No sharing allowed in private tenant. + } if (canAdminViewAllItems(user)) { return listOf() } @@ -122,10 +138,13 @@ internal object UserAccessManager { /** * validate if user has access based on given access list */ - fun doesUserHasAccess(user: User?, access: List): Boolean { + fun doesUserHasAccess(user: User?, tenant: String, access: List): Boolean { if (user == null) { // Security is disabled return true } + if (getUserTenant(user) != tenant) { + return false + } if (canAdminViewAllItems(user)) { return true } @@ -140,6 +159,9 @@ internal object UserAccessManager { } } + /** + * Check if user has all info access. + */ fun hasAllInfoAccess(user: User?): Boolean { if (user == null) { // Security is disabled return true @@ -154,4 +176,8 @@ internal object UserAccessManager { private fun isAdminUser(user: User): Boolean { return user.roles.contains(ALL_ACCESS_ROLE) } + + private fun isUserPrivateTenant(user: User?): Boolean { + return getUserTenant(user) == PRIVATE_TENANT + } } diff --git a/reports-scheduler/src/main/resources/report-definitions-mapping.yml b/reports-scheduler/src/main/resources/report-definitions-mapping.yml index d0a137fe..2ce98432 100644 --- a/reports-scheduler/src/main/resources/report-definitions-mapping.yml +++ b/reports-scheduler/src/main/resources/report-definitions-mapping.yml @@ -27,6 +27,8 @@ properties: createdTimeMs: type: date format: epoch_millis + tenant: + type: keyword access: # Array of access details like user,role,backend_role etc type: keyword reportDefinition: diff --git a/reports-scheduler/src/main/resources/report-instances-mapping.yml b/reports-scheduler/src/main/resources/report-instances-mapping.yml index a4eb8655..0fb4e586 100644 --- a/reports-scheduler/src/main/resources/report-instances-mapping.yml +++ b/reports-scheduler/src/main/resources/report-instances-mapping.yml @@ -27,6 +27,8 @@ properties: createdTimeMs: type: date format: epoch_millis + tenant: + type: keyword access: # Array of access details like user,role,backend_role etc type: keyword status: