Skip to content

Commit

Permalink
Adding Alerting Comments system indices and Security ITs (#1659)
Browse files Browse the repository at this point in the history
* Adding Alerting Comments system indices

Signed-off-by: Dennis Toepker <[email protected]>

* Add security ITs for Alerting Comments

Signed-off-by: Dennis Toepker <[email protected]>

* removed unused imports

Signed-off-by: Dennis Toepker <[email protected]>

* uncomment system index viewing IT

Signed-off-by: Dennis Toepker <[email protected]>

* uncommenting system index IT for now

Signed-off-by: Dennis Toepker <[email protected]>

* adding IT for admin editting someone else's comment

Signed-off-by: Dennis Toepker <[email protected]>

---------

Signed-off-by: Dennis Toepker <[email protected]>
Co-authored-by: Dennis Toepker <[email protected]>
  • Loading branch information
toepkerd and toepkerd-zz authored Oct 2, 2024
1 parent 5b27d24 commit d8f47a0
Show file tree
Hide file tree
Showing 6 changed files with 521 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import org.opensearch.alerting.action.SearchEmailGroupAction
import org.opensearch.alerting.alerts.AlertIndices
import org.opensearch.alerting.alerts.AlertIndices.Companion.ALL_ALERT_INDEX_PATTERN
import org.opensearch.alerting.comments.CommentsIndices
import org.opensearch.alerting.comments.CommentsIndices.Companion.ALL_COMMENTS_INDEX_PATTERN
import org.opensearch.alerting.core.JobSweeper
import org.opensearch.alerting.core.ScheduledJobIndices
import org.opensearch.alerting.core.action.node.ScheduledJobsStatsAction
Expand Down Expand Up @@ -434,7 +435,8 @@ internal class AlertingPlugin : PainlessExtension, ActionPlugin, ScriptPlugin, R
override fun getSystemIndexDescriptors(settings: Settings): Collection<SystemIndexDescriptor> {
return listOf(
SystemIndexDescriptor(ALL_ALERT_INDEX_PATTERN, "Alerting Plugin system index pattern"),
SystemIndexDescriptor(SCHEDULED_JOBS_INDEX, "Alerting Plugin Configuration index")
SystemIndexDescriptor(SCHEDULED_JOBS_INDEX, "Alerting Plugin Configuration index"),
SystemIndexDescriptor(ALL_COMMENTS_INDEX_PATTERN, "Alerting Comments system index pattern")
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import org.opensearch.commons.alerting.action.AlertingActions
val ALL_ACCESS_ROLE = "all_access"
val READALL_AND_MONITOR_ROLE = "readall_and_monitor"
val ALERTING_FULL_ACCESS_ROLE = "alerting_full_access"
val ALERTING_ACK_ALERTS_ROLE = "alerting_ack_alerts"
val ALERTING_READ_ONLY_ACCESS = "alerting_read_access"
val ALERTING_NO_ACCESS_ROLE = "no_access"
val ALERTING_GET_EMAIL_ACCOUNT_ACCESS = "alerting_get_email_account_access"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import org.opensearch.commons.alerting.model.Alert
import org.opensearch.commons.alerting.model.BucketLevelTrigger
import org.opensearch.commons.alerting.model.ChainedAlertTrigger
import org.opensearch.commons.alerting.model.Comment
import org.opensearch.commons.alerting.model.Comment.Companion.COMMENT_CONTENT_FIELD
import org.opensearch.commons.alerting.model.DocLevelMonitorInput
import org.opensearch.commons.alerting.model.DocLevelQuery
import org.opensearch.commons.alerting.model.DocumentLevelTrigger
Expand All @@ -66,6 +67,7 @@ import org.opensearch.core.xcontent.XContentBuilder
import org.opensearch.core.xcontent.XContentParser
import org.opensearch.core.xcontent.XContentParserUtils
import org.opensearch.search.SearchModule
import org.opensearch.search.builder.SearchSourceBuilder
import java.net.URLEncoder
import java.nio.file.Files
import java.time.Instant
Expand Down Expand Up @@ -524,40 +526,6 @@ abstract class AlertingRestTestCase : ODFERestTestCase() {
return alert.copy(id = alertJson["_id"] as String, version = (alertJson["_version"] as Int).toLong())
}

protected fun createAlertComment(alertId: String, content: String): Comment {
val createRequestBody = jsonBuilder()
.startObject()
.field(Comment.COMMENT_CONTENT_FIELD, content)
.endObject()
.string()

val createResponse = client().makeRequest(
"POST",
"$COMMENTS_BASE_URI/$alertId",
StringEntity(createRequestBody, APPLICATION_JSON)
)

assertEquals("Unable to create a new alert", RestStatus.CREATED, createResponse.restStatus())

val responseBody = createResponse.asMap()
val commentId = responseBody["_id"] as String
assertNotEquals("response is missing Id", Comment.NO_ID, commentId)

val comment = responseBody["comment"] as Map<*, *>

return Comment(
id = commentId,
entityId = comment["entity_id"] as String,
entityType = comment["entity_type"] as String,
content = comment["content"] as String,
createdTime = Instant.ofEpochMilli(comment["created_time"] as Long),
lastUpdatedTime = if (comment["last_updated_time"] != null) {
Instant.ofEpochMilli(comment["last_updated_time"] as Long)
} else null,
user = comment["user"]?.let { User(it as String, emptyList(), emptyList(), emptyList()) }
)
}

protected fun createRandomMonitor(refresh: Boolean = false, withMetadata: Boolean = false): Monitor {
val monitor = randomQueryLevelMonitor(withMetadata = withMetadata)
val monitorId = createMonitor(monitor, refresh).id
Expand Down Expand Up @@ -1889,4 +1857,97 @@ abstract class AlertingRestTestCase : ODFERestTestCase() {
}

protected fun Workflow.relativeUrl() = "$WORKFLOW_ALERTING_BASE_URI/$id"

protected fun createAlertComment(alertId: String, content: String, client: RestClient): Comment {
val createRequestBody = jsonBuilder()
.startObject()
.field(COMMENT_CONTENT_FIELD, content)
.endObject()
.string()

val createResponse = client.makeRequest(
"POST",
"$COMMENTS_BASE_URI/$alertId",
StringEntity(createRequestBody, APPLICATION_JSON)
)

assertEquals("Unable to create a new comment", RestStatus.CREATED, createResponse.restStatus())

val responseBody = createResponse.asMap()
val commentId = responseBody["_id"] as String
assertNotEquals("response is missing Id", Comment.NO_ID, commentId)

val comment = responseBody["comment"] as Map<*, *>

return Comment(
id = commentId,
entityId = comment["entity_id"] as String,
entityType = comment["entity_type"] as String,
content = comment["content"] as String,
createdTime = Instant.ofEpochMilli(comment["created_time"] as Long),
lastUpdatedTime = if (comment["last_updated_time"] != null) {
Instant.ofEpochMilli(comment["last_updated_time"] as Long)
} else null,
user = comment["user"]?.let { User(it as String, emptyList(), emptyList(), emptyList()) }
)
}

protected fun updateAlertComment(commentId: String, content: String, client: RestClient): Comment {
val updateRequestBody = jsonBuilder()
.startObject()
.field(COMMENT_CONTENT_FIELD, content)
.endObject()
.string()

val updateResponse = client.makeRequest(
"PUT",
"$COMMENTS_BASE_URI/$commentId",
StringEntity(updateRequestBody, APPLICATION_JSON)
)

assertEquals("Update comment failed", RestStatus.OK, updateResponse.restStatus())

val updateResponseBody = updateResponse.asMap()

val comment = updateResponseBody["comment"] as Map<*, *>

return Comment(
id = commentId,
entityId = comment["entity_id"] as String,
entityType = comment["entity_type"] as String,
content = comment["content"] as String,
createdTime = Instant.ofEpochMilli(comment["created_time"] as Long),
lastUpdatedTime = if (comment["last_updated_time"] != null) {
Instant.ofEpochMilli(comment["last_updated_time"] as Long)
} else null,
user = comment["user"]?.let { User(it as String, emptyList(), emptyList(), emptyList()) }
)
}

protected fun searchAlertComments(query: SearchSourceBuilder, client: RestClient): XContentParser {
val searchResponse = client.makeRequest(
"GET",
"$COMMENTS_BASE_URI/_search",
StringEntity(query.toString(), APPLICATION_JSON)
)

val xcp = createParser(XContentType.JSON.xContent(), searchResponse.entity.content)

return xcp
}

// returns the ID of the delete comment
protected fun deleteAlertComment(commentId: String, client: RestClient): String {
val deleteResponse = client.makeRequest(
"DELETE",
"$COMMENTS_BASE_URI/$commentId"
)

assertEquals("Delete comment failed", RestStatus.OK, deleteResponse.restStatus())

val deleteResponseBody = deleteResponse.asMap()
val deletedCommentId = deleteResponseBody["_id"] as String

return deletedCommentId
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,13 @@

package org.opensearch.alerting.resthandler

import org.apache.hc.core5.http.ContentType
import org.apache.hc.core5.http.io.entity.StringEntity
import org.opensearch.alerting.AlertingPlugin.Companion.COMMENTS_BASE_URI
import org.opensearch.alerting.AlertingRestTestCase
import org.opensearch.alerting.makeRequest
import org.opensearch.alerting.randomAlert
import org.opensearch.alerting.settings.AlertingSettings.Companion.ALERTING_COMMENTS_ENABLED
import org.opensearch.common.xcontent.XContentFactory
import org.opensearch.common.xcontent.XContentType
import org.opensearch.commons.alerting.model.Alert
import org.opensearch.commons.alerting.model.Comment.Companion.COMMENT_CONTENT_FIELD
import org.opensearch.commons.alerting.util.string
import org.opensearch.core.rest.RestStatus
import org.opensearch.index.query.QueryBuilders
import org.opensearch.search.builder.SearchSourceBuilder
import org.opensearch.test.OpenSearchTestCase
import org.opensearch.test.junit.annotations.TestLogging
import java.util.concurrent.TimeUnit

@TestLogging("level:DEBUG", reason = "Debug for tests.")
@Suppress("UNCHECKED_CAST")
Expand All @@ -36,7 +25,7 @@ class AlertingCommentsRestApiIT : AlertingRestTestCase() {
val alertId = alert.id
val commentContent = "test comment"

val comment = createAlertComment(alertId, commentContent)
val comment = createAlertComment(alertId, commentContent, client())

assertEquals("Comment does not have correct content", commentContent, comment.content)
assertEquals("Comment does not have correct alert ID", alertId, comment.entityId)
Expand All @@ -50,27 +39,11 @@ class AlertingCommentsRestApiIT : AlertingRestTestCase() {
val alertId = alert.id
val commentContent = "test comment"

val commentId = createAlertComment(alertId, commentContent).id
val commentId = createAlertComment(alertId, commentContent, client()).id

val updateContent = "updated comment"
val updateRequestBody = XContentFactory.jsonBuilder()
.startObject()
.field(COMMENT_CONTENT_FIELD, updateContent)
.endObject()
.string()
val actualContent = updateAlertComment(commentId, updateContent, client()).content

val updateResponse = client().makeRequest(
"PUT",
"$COMMENTS_BASE_URI/$commentId",
StringEntity(updateRequestBody, ContentType.APPLICATION_JSON)
)

assertEquals("Update comment failed", RestStatus.OK, updateResponse.restStatus())

val updateResponseBody = updateResponse.asMap()

val comment = updateResponseBody["comment"] as Map<*, *>
val actualContent = comment["content"] as String
assertEquals("Comment does not have correct content after update", updateContent, actualContent)
}

Expand All @@ -82,20 +55,11 @@ class AlertingCommentsRestApiIT : AlertingRestTestCase() {
val alertId = alert.id
val commentContent = "test comment"

createAlertComment(alertId, commentContent)
createAlertComment(alertId, commentContent, client())

OpenSearchTestCase.waitUntil({
return@waitUntil false
}, 3, TimeUnit.SECONDS)
val search = SearchSourceBuilder().query(QueryBuilders.matchAllQuery())
val xcp = searchAlertComments(search, client())

val search = SearchSourceBuilder().query(QueryBuilders.matchAllQuery()).toString()
val searchResponse = client().makeRequest(
"GET",
"$COMMENTS_BASE_URI/_search",
StringEntity(search, ContentType.APPLICATION_JSON)
)

val xcp = createParser(XContentType.JSON.xContent(), searchResponse.entity.content)
val hits = xcp.map()["hits"]!! as Map<String, Map<String, Any>>
logger.info("hits: $hits")
val numberDocsFound = hits["total"]?.get("value")
Expand All @@ -116,21 +80,10 @@ class AlertingCommentsRestApiIT : AlertingRestTestCase() {
val alertId = alert.id
val commentContent = "test comment"

val commentId = createAlertComment(alertId, commentContent).id
OpenSearchTestCase.waitUntil({
return@waitUntil false
}, 3, TimeUnit.SECONDS)

val deleteResponse = client().makeRequest(
"DELETE",
"$COMMENTS_BASE_URI/$commentId"
)

assertEquals("Delete comment failed", RestStatus.OK, deleteResponse.restStatus())
val commentId = createAlertComment(alertId, commentContent, client()).id

val deleteResponseBody = deleteResponse.asMap()
val deletedCommentId = deleteAlertComment(commentId, client())

val deletedCommentId = deleteResponseBody["_id"] as String
assertEquals("Deleted Comment ID does not match Comment ID in delete request", commentId, deletedCommentId)
}

Expand Down
Loading

0 comments on commit d8f47a0

Please sign in to comment.