diff --git a/dto/src/main/scala/org/thp/thehive/dto/v1/Share.scala b/dto/src/main/scala/org/thp/thehive/dto/v1/Share.scala new file mode 100644 index 0000000000..90dcfdfebc --- /dev/null +++ b/dto/src/main/scala/org/thp/thehive/dto/v1/Share.scala @@ -0,0 +1,48 @@ +package org.thp.thehive.dto.v1 + +import org.thp.thehive.dto.v1.ObservablesFilter.ObservablesFilter +import org.thp.thehive.dto.v1.TasksFilter.TasksFilter +import play.api.libs.json.{Format, Json, Writes} + +import java.util.Date + +case class InputShare(organisationName: String, profile: String, tasks: TasksFilter, observables: ObservablesFilter) + +object TasksFilter extends Enumeration { + type TasksFilter = Value + + val all: TasksFilter = Value("all") + val none: TasksFilter = Value("none") + + implicit val format: Format[TasksFilter] = Json.formatEnum(TasksFilter) +} + +object ObservablesFilter extends Enumeration { + type ObservablesFilter = Value + + val all: ObservablesFilter = Value("all") + val none: ObservablesFilter = Value("none") + + implicit val format: Format[ObservablesFilter] = Json.formatEnum(ObservablesFilter) +} + +object InputShare { + implicit val writes: Writes[InputShare] = Json.writes[InputShare] +} + +case class OutputShare( + _id: String, + _type: String, + _createdBy: String, + _updatedBy: Option[String] = None, + _createdAt: Date, + _updatedAt: Option[Date] = None, + caseId: String, + profileName: String, + organisationName: String, + owner: Boolean +) + +object OutputShare { + implicit val format: Format[OutputShare] = Json.format[OutputShare] +} diff --git a/thehive/app/org/thp/thehive/controllers/v0/ShareCtrl.scala b/thehive/app/org/thp/thehive/controllers/v0/ShareCtrl.scala index 4d3c6f890c..1ab624af99 100644 --- a/thehive/app/org/thp/thehive/controllers/v0/ShareCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v0/ShareCtrl.scala @@ -174,7 +174,8 @@ class ShareCtrl @Inject() ( val shares = caseSrv .get(EntityIdOrName(caseId)) .shares - .filter(_.organisation.filterNot(_.get(request.organisation)).visible) + .visible + .filterNot(_.get(request.organisation)) .richShare .toSeq diff --git a/thehive/app/org/thp/thehive/controllers/v1/CaseCtrl.scala b/thehive/app/org/thp/thehive/controllers/v1/CaseCtrl.scala index 0440346733..e52b9b8b75 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/CaseCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/CaseCtrl.scala @@ -1,6 +1,5 @@ package org.thp.thehive.controllers.v1 -import javax.inject.{Inject, Named, Singleton} import org.thp.scalligraph.controllers.{Entrypoint, FieldsParser} import org.thp.scalligraph.models.{Database, Entity} import org.thp.scalligraph.query.{ParamQuery, PropertyUpdater, PublicProperties, Query} @@ -14,10 +13,12 @@ import org.thp.thehive.services.AlertOps._ import org.thp.thehive.services.CaseOps._ import org.thp.thehive.services.CaseTemplateOps._ import org.thp.thehive.services.OrganisationOps._ +import org.thp.thehive.services.ShareOps._ import org.thp.thehive.services.UserOps._ import org.thp.thehive.services._ import play.api.mvc.{Action, AnyContent, Results} +import javax.inject.{Inject, Named, Singleton} import scala.util.{Success, Try} @Singleton @@ -58,7 +59,8 @@ class CaseCtrl @Inject() ( Query[Traversal.V[Case], Traversal.V[Observable]]("observables", (caseSteps, authContext) => caseSteps.observables(authContext)), Query[Traversal.V[Case], Traversal.V[User]]("assignableUsers", (caseSteps, authContext) => caseSteps.assignableUsers(authContext)), Query[Traversal.V[Case], Traversal.V[Organisation]]("organisations", (caseSteps, authContext) => caseSteps.organisations.visible(authContext)), - Query[Traversal.V[Case], Traversal.V[Alert]]("alerts", (caseSteps, authContext) => caseSteps.alert.visible(authContext)) + Query[Traversal.V[Case], Traversal.V[Alert]]("alerts", (caseSteps, authContext) => caseSteps.alert.visible(authContext)), + Query[Traversal.V[Case], Traversal.V[Share]]("shares", (caseSteps, authContext) => caseSteps.shares.visible(authContext)) ) def create: Action[AnyContent] = diff --git a/thehive/app/org/thp/thehive/controllers/v1/Conversion.scala b/thehive/app/org/thp/thehive/controllers/v1/Conversion.scala index ac556fca70..f1dd620ad6 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/Conversion.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/Conversion.scala @@ -288,6 +288,14 @@ object Conversion { .transform } + implicit val shareOutput: Renderer.Aux[RichShare, OutputShare] = Renderer.toJson[RichShare, OutputShare]( + _.into[OutputShare] + .withFieldComputed(_._id, _.share._id.toString) + .withFieldConst(_._type, "Share") + .withFieldComputed(_.caseId, _.caseId.toString) + .transform + ) + implicit val profileOutput: Renderer.Aux[Profile with Entity, OutputProfile] = Renderer.toJson[Profile with Entity, OutputProfile](profile => profile .asInstanceOf[Profile] diff --git a/thehive/app/org/thp/thehive/controllers/v1/ObservableCtrl.scala b/thehive/app/org/thp/thehive/controllers/v1/ObservableCtrl.scala index 49c9f3e3b2..86388efde6 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/ObservableCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/ObservableCtrl.scala @@ -82,7 +82,8 @@ class ObservableCtrl @Inject() ( (observableSteps, authContext) => observableSteps.filteredSimilar.visible(authContext) ), Query[Traversal.V[Observable], Traversal.V[Case]]("case", (observableSteps, _) => observableSteps.`case`), - Query[Traversal.V[Observable], Traversal.V[Alert]]("alert", (observableSteps, _) => observableSteps.alert) + Query[Traversal.V[Observable], Traversal.V[Alert]]("alert", (observableSteps, _) => observableSteps.alert), + Query[Traversal.V[Observable], Traversal.V[Share]]("shares", (observableSteps, authContext) => observableSteps.shares.visible(authContext)) ) def create(caseId: String): Action[AnyContent] = diff --git a/thehive/app/org/thp/thehive/controllers/v1/Properties.scala b/thehive/app/org/thp/thehive/controllers/v1/Properties.scala index fae8188f4e..b9c3c8f87d 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/Properties.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/Properties.scala @@ -21,6 +21,7 @@ import org.thp.thehive.services.ObservableOps._ import org.thp.thehive.services.OrganisationOps._ import org.thp.thehive.services.TagOps._ import org.thp.thehive.services.TaskOps._ +import org.thp.thehive.services.ShareOps._ import org.thp.thehive.services.UserOps._ import org.thp.thehive.services._ import play.api.libs.json.{JsObject, JsValue, Json} @@ -193,10 +194,7 @@ class Properties @Inject() ( .property("pap", UMapping.int)(_.field.updatable) .property("status", UMapping.enum[CaseStatus.type])(_.field.updatable) .property("summary", UMapping.string.optional)(_.field.updatable) - .property("actionRequired", UMapping.boolean)(_ - .authSelect((t, auth) => t.isActionRequired(auth)) - .readonly - ) + .property("actionRequired", UMapping.boolean)(_.authSelect((t, auth) => t.isActionRequired(auth)).readonly) .property("assignee", UMapping.string.optional)(_.select(_.user.value(_.login)).custom { (_, login, vertex, _, graph, authContext) => for { c <- caseSrv.get(vertex)(graph).getOrFail("Case") @@ -407,6 +405,17 @@ class Properties @Inject() ( .property("permissions", UMapping.string.set)(_.field.updatable) .build + lazy val share: PublicProperties = + PublicPropertyListBuilder[Share] + .property("caseId", UMapping.entityId)(_.select(_.`case`._id).readonly) + .property("caseNumber", UMapping.int)(_.select(_.`case`.value(_.number)).readonly) + .property("organisationId", UMapping.entityId)(_.select(_.organisation._id).readonly) + .property("organisationName", UMapping.string)(_.select(_.organisation.value(_.name)).readonly) + .property("profileId", UMapping.entityId)(_.select(_.profile._id).readonly) + .property("profileName", UMapping.string)(_.select(_.profile.value(_.name)).readonly) + .property("owner", UMapping.boolean)(_.field.readonly) + .build + lazy val task: PublicProperties = PublicPropertyListBuilder[Task] .property("title", UMapping.string)(_.field.updatable) @@ -433,12 +442,9 @@ class Properties @Inject() ( } .map(_ => Json.obj("assignee" -> value)) }) - .property("actionRequired", UMapping.boolean)(_ - .authSelect((t, authContext) => { - t.actionRequired(authContext) - }) - .readonly - ) + .property("actionRequired", UMapping.boolean)(_.authSelect { (t, authContext) => + t.actionRequired(authContext) + }.readonly) .build lazy val log: PublicProperties = diff --git a/thehive/app/org/thp/thehive/controllers/v1/Router.scala b/thehive/app/org/thp/thehive/controllers/v1/Router.scala index 82f1ad699a..314dd5ed5f 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/Router.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/Router.scala @@ -25,7 +25,7 @@ class Router @Inject() ( // permissionCtrl: PermissionCtrl, profileCtrl: ProfileCtrl, taskCtrl: TaskCtrl, - // shareCtrl: ShareCtrl, + shareCtrl: ShareCtrl, userCtrl: UserCtrl, statusCtrl: StatusCtrl // streamCtrl: StreamCtrl, @@ -58,7 +58,7 @@ class Router @Inject() ( case PATCH(p"/observable/_bulk") => observableCtrl.bulkUpdate case PATCH(p"/observable/$observableId") => observableCtrl.update(observableId) // case GET(p"/observable/$observableId/similar") => observableCtrl.findSimilar(observableId) -// case POST(p"/observable/$observableId/shares") => shareCtrl.shareObservable(observableId) + case POST(p"/observable/$observableId/shares") => shareCtrl.shareObservable(observableId) case GET(p"/caseTemplate") => caseTemplateCtrl.list case POST(p"/caseTemplate") => caseTemplateCtrl.create @@ -83,18 +83,25 @@ class Router @Inject() ( case GET(p"/organisation/$organisationId") => organisationCtrl.get(organisationId) case PATCH(p"/organisation/$organisationId") => organisationCtrl.update(organisationId) -// case GET(p"/share") => shareCtrl.list -// case POST(p"/share") => shareCtrl.create -// case GET(p"/share/$shareId") => shareCtrl.get(shareId) -// case PATCH(p"/share/$shareId") => shareCtrl.update(shareId) - - case GET(p"/task") => taskCtrl.list - case POST(p"/task") => taskCtrl.create - case GET(p"/task/$taskId") => taskCtrl.get(taskId) - case PATCH(p"/task/$taskId") => taskCtrl.update(taskId) - case GET(p"/task/$taskId/actionRequired") => taskCtrl.isActionRequired(taskId) - case PUT(p"/task/$taskId/actionRequired/$orgaId") => taskCtrl.actionRequired(taskId, orgaId, required = true) - case PUT(p"/task/$taskId/actionDone/$orgaId") => taskCtrl.actionRequired(taskId, orgaId, required = false) + case DELETE(p"/case/shares") => shareCtrl.removeShares() + case POST(p"/case/$caseId/shares") => shareCtrl.shareCase(caseId) + case DELETE(p"/case/$caseId/shares") => shareCtrl.removeShares(caseId) + case DELETE(p"/task/$taskId/shares") => shareCtrl.removeTaskShares(taskId) + case DELETE(p"/observable/$observableId/shares") => shareCtrl.removeObservableShares(observableId) + case GET(p"/case/$caseId/shares") => shareCtrl.listShareCases(caseId) + case GET(p"/case/$caseId/task/$taskId/shares") => shareCtrl.listShareTasks(caseId, taskId) + case GET(p"/case/$caseId/observable/$observableId/shares") => shareCtrl.listShareObservables(caseId, observableId) + case POST(p"/case/task/$taskId/shares") => shareCtrl.shareTask(taskId) + case DELETE(p"/case/share/$shareId") => shareCtrl.removeShare(shareId) + case PATCH(p"/case/share/$shareId") => shareCtrl.updateShare(shareId) + + case GET(p"/task") => taskCtrl.list + case POST(p"/task") => taskCtrl.create + case GET(p"/task/$taskId") => taskCtrl.get(taskId) + case PATCH(p"/task/$taskId") => taskCtrl.update(taskId) + case GET(p"/task/$taskId/actionRequired") => taskCtrl.isActionRequired(taskId) + case PUT(p"/task/$taskId/actionRequired/$orgaId") => taskCtrl.actionRequired(taskId, orgaId, required = true) + case PUT(p"/task/$taskId/actionDone/$orgaId") => taskCtrl.actionRequired(taskId, orgaId, required = false) // POST /case/:caseId/task/_search controllers.TaskCtrl.findInCase(caseId) // POST /case/task/_stats controllers.TaskCtrl.stats() diff --git a/thehive/app/org/thp/thehive/controllers/v1/ShareCtrl.scala b/thehive/app/org/thp/thehive/controllers/v1/ShareCtrl.scala new file mode 100644 index 0000000000..b905cac510 --- /dev/null +++ b/thehive/app/org/thp/thehive/controllers/v1/ShareCtrl.scala @@ -0,0 +1,266 @@ +package org.thp.thehive.controllers.v1 + +import org.apache.tinkerpop.gremlin.structure.Graph +import org.thp.scalligraph.auth.AuthContext +import org.thp.scalligraph.controllers.{Entrypoint, FieldsParser} +import org.thp.scalligraph.models.Database +import org.thp.scalligraph.query.{ParamQuery, PublicProperties, Query} +import org.thp.scalligraph.traversal.TraversalOps._ +import org.thp.scalligraph.traversal.{IteratorOutput, Traversal} +import org.thp.scalligraph.{AuthorizationError, BadRequestError, EntityIdOrName, RichSeq} +import org.thp.thehive.controllers.v1.Conversion._ +import org.thp.thehive.dto.v1.{InputShare, ObservablesFilter, TasksFilter} +import org.thp.thehive.models.{Case, Observable, Organisation, Permissions, RichShare, Share, Task} +import org.thp.thehive.services.CaseOps._ +import org.thp.thehive.services.ObservableOps._ +import org.thp.thehive.services.OrganisationOps._ +import org.thp.thehive.services.ShareOps._ +import org.thp.thehive.services.TaskOps._ +import org.thp.thehive.services._ +import play.api.mvc.{Action, AnyContent, Results} + +import javax.inject.{Inject, Named} +import scala.util.{Failure, Success, Try} + +class ShareCtrl @Inject() ( + entrypoint: Entrypoint, + shareSrv: ShareSrv, + properties: Properties, + organisationSrv: OrganisationSrv, + caseSrv: CaseSrv, + taskSrv: TaskSrv, + observableSrv: ObservableSrv, + profileSrv: ProfileSrv, + @Named("with-thehive-schema") implicit val db: Database +) extends QueryableCtrl { + override val entityName: String = "share" + override val publicProperties: PublicProperties = properties.share + override val initialQuery: Query = + Query.init[Traversal.V[Share]]("listShare", (graph, authContext) => organisationSrv.startTraversal(graph).visible(authContext).shares) + override val pageQuery: ParamQuery[OutputParam] = Query.withParam[OutputParam, Traversal.V[Share], IteratorOutput]( + "page", + FieldsParser[OutputParam], + (range, shareSteps, _) => shareSteps.richPage(range.from, range.to, range.extraData.contains("total"))(_.richShare) + ) + override val outputQuery: Query = Query.outputWithContext[RichShare, Traversal.V[Share]]((shareSteps, _) => shareSteps.richShare) + override val getQuery: ParamQuery[EntityIdOrName] = Query.initWithParam[EntityIdOrName, Traversal.V[Share]]( + "getShare", + FieldsParser[EntityIdOrName], + (idOrName, graph, authContext) => shareSrv.get(idOrName)(graph).visible(authContext) + ) + override val extraQueries: Seq[ParamQuery[_]] = Seq( + Query[Traversal.V[Share], Traversal.V[Case]]("case", (shareSteps, _) => shareSteps.`case`), + Query[Traversal.V[Share], Traversal.V[Observable]]("observables", (shareSteps, _) => shareSteps.observables), + Query[Traversal.V[Share], Traversal.V[Task]]("tasks", (shareSteps, _) => shareSteps.tasks), + Query[Traversal.V[Share], Traversal.V[Organisation]]("organisation", (shareSteps, _) => shareSteps.organisation) + ) + + def shareCase(caseId: String): Action[AnyContent] = + entrypoint("create case shares") + .extract("shares", FieldsParser[InputShare].sequence.on("shares")) + .authTransaction(db) { implicit request => implicit graph => + val inputShares: Seq[InputShare] = request.body("shares") + caseSrv + .get(EntityIdOrName(caseId)) + .can(Permissions.manageShare) + .getOrFail("Case") + .flatMap { `case` => + inputShares.toTry { inputShare => + for { + organisation <- + organisationSrv + .get(request.organisation) + .visibleOrganisationsFrom + .get(EntityIdOrName(inputShare.organisationName)) + .getOrFail("Organisation") + profile <- profileSrv.getOrFail(EntityIdOrName(inputShare.profile)) + share <- shareSrv.shareCase(owner = false, `case`, organisation, profile) + richShare <- shareSrv.get(share).richShare.getOrFail("Share") + _ <- if (inputShare.tasks == TasksFilter.all) shareSrv.shareCaseTasks(share) else Success(Nil) + _ <- if (inputShare.observables == ObservablesFilter.all) shareSrv.shareCaseObservables(share) else Success(Nil) + } yield richShare + } + } + .map(shares => Results.Ok(shares.toJson)) + } + + def removeShare(shareId: String): Action[AnyContent] = + entrypoint("remove share") + .authTransaction(db) { implicit request => implicit graph => + doRemoveShare(EntityIdOrName(shareId)).map(_ => Results.NoContent) + } + + def removeShares(): Action[AnyContent] = + entrypoint("remove share") + .extract("shares", FieldsParser[String].sequence.on("ids")) + .authTransaction(db) { implicit request => implicit graph => + val shareIds: Seq[String] = request.body("shares") + shareIds.map(EntityIdOrName.apply).toTry(doRemoveShare(_)).map(_ => Results.NoContent) + } + + def removeShares(caseId: String): Action[AnyContent] = + entrypoint("remove share") + .extract("organisations", FieldsParser[String].sequence.on("organisations")) + .authTransaction(db) { implicit request => implicit graph => + val organisations: Seq[String] = request.body("organisations") + organisations + .map(EntityIdOrName(_)) + .toTry { organisationId => + for { + organisation <- organisationSrv.get(organisationId).getOrFail("Organisation") + _ <- + if (request.organisation.fold(_ == organisation._id, _ == organisation.name)) + Failure(BadRequestError("You cannot remove your own share")) + else Success(()) + shareId <- + caseSrv + .get(EntityIdOrName(caseId)) + .can(Permissions.manageShare) + .share(organisationId) + .has(_.owner, false) + ._id + .orFail(AuthorizationError("Operation not permitted")) + _ <- shareSrv.remove(shareId) + } yield () + } + .map(_ => Results.NoContent) + } + + def removeTaskShares(taskId: String): Action[AnyContent] = + entrypoint("remove share tasks") + .extract("organisations", FieldsParser[String].sequence.on("organisations")) + .authTransaction(db) { implicit request => implicit graph => + val organisations: Seq[String] = request.body("organisations") + + taskSrv + .getOrFail(EntityIdOrName(taskId)) + .flatMap { task => + organisations.toTry { organisationName => + organisationSrv + .getOrFail(EntityIdOrName(organisationName)) + .flatMap(shareSrv.removeShareTasks(task, _)) + } + } + .map(_ => Results.NoContent) + } + + def removeObservableShares(observableId: String): Action[AnyContent] = + entrypoint("remove share observables") + .extract("organisations", FieldsParser[String].sequence.on("organisations")) + .authTransaction(db) { implicit request => implicit graph => + val organisations: Seq[String] = request.body("organisations") + + observableSrv + .getOrFail(EntityIdOrName(observableId)) + .flatMap { observable => + organisations.toTry { organisationName => + organisationSrv + .getOrFail(EntityIdOrName(organisationName)) + .flatMap(shareSrv.removeShareObservable(observable, _)) + } + } + .map(_ => Results.NoContent) + } + + private def doRemoveShare(shareId: EntityIdOrName)(implicit graph: Graph, authContext: AuthContext): Try[Unit] = + if (!shareSrv.get(shareId).`case`.can(Permissions.manageShare).exists) + Failure(AuthorizationError("You are not authorized to remove share")) + else if (shareSrv.get(shareId).byOrganisation(authContext.organisation).exists) + Failure(AuthorizationError("You can't remove your share")) + else if (shareSrv.get(shareId).has(_.owner, true).exists) + Failure(AuthorizationError("You can't remove initial shares")) + else + shareSrv.remove(shareId) + + def updateShare(shareId: String): Action[AnyContent] = + entrypoint("update share") + .extract("profile", FieldsParser.string.on("profile")) + .authTransaction(db) { implicit request => implicit graph => + val profile: String = request.body("profile") + if (!shareSrv.get(EntityIdOrName(shareId)).`case`.can(Permissions.manageShare).exists) + Failure(AuthorizationError("You are not authorized to remove share")) + for { + richShare <- + shareSrv + .get(EntityIdOrName(shareId)) + .filter(_.organisation.visibleOrganisationsTo.visible) + .richShare + .getOrFail("Share") + profile <- profileSrv.getOrFail(EntityIdOrName(profile)) + _ <- shareSrv.update(richShare.share, profile) + } yield Results.Ok + } + + def listShareCases(caseId: String): Action[AnyContent] = + entrypoint("list case shares") + .authRoTransaction(db) { implicit request => implicit graph => + val shares = caseSrv + .get(EntityIdOrName(caseId)) + .shares + .visible + .filterNot(_.get(request.organisation)) + .richShare + .toSeq + + Success(Results.Ok(shares.toJson)) + } + + def listShareTasks(caseId: String, taskId: String): Action[AnyContent] = + entrypoint("list task shares") + .authRoTransaction(db) { implicit request => implicit graph => + val shares = caseSrv + .get(EntityIdOrName(caseId)) + .can(Permissions.manageShare) + .shares + .visible + .filterNot(_.get(request.organisation)) + .byTask(EntityIdOrName(taskId)) + .richShare + .toSeq + + Success(Results.Ok(shares.toJson)) + } + + def listShareObservables(caseId: String, observableId: String): Action[AnyContent] = + entrypoint("list observable shares") + .authRoTransaction(db) { implicit request => implicit graph => + val shares = caseSrv + .get(EntityIdOrName(caseId)) + .can(Permissions.manageShare) + .shares + .visible + .filterNot(_.get(request.organisation)) + .byObservable(EntityIdOrName(observableId)) + .richShare + .toSeq + + Success(Results.Ok(shares.toJson)) + } + + def shareTask(taskId: String): Action[AnyContent] = + entrypoint("share task") + .extract("organisations", FieldsParser.string.sequence.on("organisations")) + .authTransaction(db) { implicit request => implicit graph => + val organisationIds: Seq[String] = request.body("organisations") + + for { + task <- taskSrv.getOrFail(EntityIdOrName(taskId)) + _ <- taskSrv.get(task).`case`.can(Permissions.manageShare).existsOrFail + organisations <- organisationIds.map(EntityIdOrName(_)).toTry(organisationSrv.get(_).visible.getOrFail("Organisation")) + _ <- shareSrv.addTaskShares(task, organisations) + } yield Results.NoContent + } + + def shareObservable(observableId: String): Action[AnyContent] = + entrypoint("share observable") + .extract("organisations", FieldsParser.string.sequence.on("organisations")) + .authTransaction(db) { implicit request => implicit graph => + val organisationIds: Seq[String] = request.body("organisations") + for { + observable <- observableSrv.getOrFail(EntityIdOrName(observableId)) + _ <- observableSrv.get(observable).`case`.can(Permissions.manageShare).existsOrFail + organisations <- organisationIds.map(EntityIdOrName(_)).toTry(organisationSrv.get(_).visible.getOrFail("Organisation")) + _ <- shareSrv.addObservableShares(observable, organisations) + } yield Results.NoContent + } +} diff --git a/thehive/app/org/thp/thehive/controllers/v1/TaskCtrl.scala b/thehive/app/org/thp/thehive/controllers/v1/TaskCtrl.scala index 55be07a869..7330f74f89 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/TaskCtrl.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/TaskCtrl.scala @@ -59,7 +59,8 @@ class TaskCtrl @Inject() ( Query[Traversal.V[Task], Traversal.V[Log]]("logs", (taskSteps, _) => taskSteps.logs), Query[Traversal.V[Task], Traversal.V[Case]]("case", (taskSteps, _) => taskSteps.`case`), Query[Traversal.V[Task], Traversal.V[CaseTemplate]]("caseTemplate", (taskSteps, _) => taskSteps.caseTemplate), - Query[Traversal.V[Task], Traversal.V[Organisation]]("organisations", (taskSteps, authContext) => taskSteps.organisations.visible(authContext)) + Query[Traversal.V[Task], Traversal.V[Organisation]]("organisations", (taskSteps, authContext) => taskSteps.organisations.visible(authContext)), + Query[Traversal.V[Task], Traversal.V[Share]]("shares", (taskSteps, authContext) => taskSteps.shares.visible(authContext)) ) def create: Action[AnyContent] = @@ -115,14 +116,14 @@ class TaskCtrl @Inject() ( def isActionRequired(taskId: String): Action[AnyContent] = entrypoint("is action required") - .authTransaction(db){ implicit request => implicit graph => + .authTransaction(db) { implicit request => implicit graph => val actionTraversal = taskSrv.get(EntityIdOrName(taskId)).visible.actionRequiredMap Success(Results.Ok(actionTraversal.toSeq.toMap.toJson)) } def actionRequired(taskId: String, orgaId: String, required: Boolean): Action[AnyContent] = entrypoint("action required") - .authTransaction(db){ implicit request => implicit graph => + .authTransaction(db) { implicit request => implicit graph => for { organisation <- organisationSrv.get(EntityIdOrName(orgaId)).visible.getOrFail("Organisation") task <- taskSrv.get(EntityIdOrName(taskId)).visible.getOrFail("Task") diff --git a/thehive/app/org/thp/thehive/controllers/v1/TheHiveQueryExecutor.scala b/thehive/app/org/thp/thehive/controllers/v1/TheHiveQueryExecutor.scala index bbc3b86b81..e1f64bf01c 100644 --- a/thehive/app/org/thp/thehive/controllers/v1/TheHiveQueryExecutor.scala +++ b/thehive/app/org/thp/thehive/controllers/v1/TheHiveQueryExecutor.scala @@ -30,6 +30,7 @@ class TheHiveQueryExecutor @Inject() ( observableTypeCtrl: ObservableTypeCtrl, organisationCtrl: OrganisationCtrl, profileCtrl: ProfileCtrl, + shareCtrl: ShareCtrl, taskCtrl: TaskCtrl, userCtrl: UserCtrl, // dashboardCtrl: DashboardCtrl, @@ -51,6 +52,7 @@ class TheHiveQueryExecutor @Inject() ( organisationCtrl, // pageCtrl, profileCtrl, + shareCtrl, // tagCtrl, taskCtrl, userCtrl diff --git a/thehive/app/org/thp/thehive/services/ShareSrv.scala b/thehive/app/org/thp/thehive/services/ShareSrv.scala index a385687ed3..acf44a3d65 100644 --- a/thehive/app/org/thp/thehive/services/ShareSrv.scala +++ b/thehive/app/org/thp/thehive/services/ShareSrv.scala @@ -149,7 +149,7 @@ class ShareSrv @Inject() ( get(share) .`case` .tasks - .filterNot(_.taskToShares.hasId(share._id)) + .filterNot(_.shares.hasId(share._id)) .toIterator .toTry(shareTaskSrv.create(ShareTask(), share, _)) @@ -213,7 +213,7 @@ class ShareSrv @Inject() ( )(implicit graph: Graph, authContext: AuthContext): Try[Unit] = { val (orgsToAdd, orgsToRemove) = taskSrv .get(task) - .taskToShares + .shares .organisation .toIterator .foldLeft((organisations.toSet, Set.empty[Organisation with Entity])) { @@ -239,7 +239,7 @@ class ShareSrv @Inject() ( )(implicit graph: Graph, authContext: AuthContext): Try[Unit] = { val existingOrgs = taskSrv .get(task) - .taskToShares + .shares .organisation .toSeq @@ -326,6 +326,8 @@ object ShareOps { def organisation: Traversal.V[Organisation] = traversal.in[OrganisationShare].v[Organisation] + def visible(implicit authContext: AuthContext): Traversal.V[Share] = traversal.filter(_.organisation.visible) + def tasks: Traversal.V[Task] = traversal.out[ShareTask].v[Task] def byTask(taskId: EntityIdOrName): Traversal.V[Share] = diff --git a/thehive/app/org/thp/thehive/services/TaskSrv.scala b/thehive/app/org/thp/thehive/services/TaskSrv.scala index 5602d2e68a..5f680f339e 100644 --- a/thehive/app/org/thp/thehive/services/TaskSrv.scala +++ b/thehive/app/org/thp/thehive/services/TaskSrv.scala @@ -47,7 +47,7 @@ class TaskSrv @Inject() (caseSrvProvider: Provider[CaseSrv], auditSrv: AuditSrv, get(task).caseTemplate.headOption match { case None => get(task) - .taskToShares + .shares .toIterator .toTry { share => auditSrv @@ -145,7 +145,7 @@ object TaskOps { def can(permission: Permission)(implicit authContext: AuthContext): Traversal.V[Task] = if (authContext.permissions.contains(permission)) - traversal.filter(_.taskToShares.filter(_.profile.has(_.permissions, permission)).organisation.current) + traversal.filter(_.shares.filter(_.profile.has(_.permissions, permission)).organisation.current) else traversal.limit(0) @@ -166,9 +166,9 @@ object TaskOps { def organisations: Traversal.V[Organisation] = traversal.in[ShareTask].in[OrganisationShare].v[Organisation] def organisations(permission: Permission): Traversal.V[Organisation] = - taskToShares.filter(_.profile.has(_.permissions, permission)).organisation + shares.filter(_.profile.has(_.permissions, permission)).organisation - def origin: Traversal.V[Organisation] = taskToShares.has(_.owner, true).organisation + def origin: Traversal.V[Organisation] = shares.has(_.owner, true).organisation def assignableUsers(implicit authContext: AuthContext): Traversal.V[User] = organisations(Permissions.manageTask) @@ -226,7 +226,7 @@ object TaskOps { def unassign(): Unit = traversal.outE[TaskUser].remove() - def taskToShares: Traversal.V[Share] = traversal.in[ShareTask].v[Share] + def shares: Traversal.V[Share] = traversal.in[ShareTask].v[Share] def share(implicit authContext: AuthContext): Traversal.V[Share] = share(authContext.organisation)