Skip to content

Commit

Permalink
Add security ITs for Alerting Comments
Browse files Browse the repository at this point in the history
Signed-off-by: Dennis Toepker <[email protected]>
  • Loading branch information
toepkerd-zz committed Sep 25, 2024
1 parent 48f38cb commit 14c8426
Show file tree
Hide file tree
Showing 5 changed files with 498 additions and 99 deletions.
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 14c8426

Please sign in to comment.