Skip to content

Commit 3e0455f

Browse files
committed
#234 Check audit entity consistency
1 parent 88245f5 commit 3e0455f

File tree

1 file changed

+64
-46
lines changed

1 file changed

+64
-46
lines changed

thehive-backend/app/models/Audit.scala

+64-46
Original file line numberDiff line numberDiff line change
@@ -6,23 +6,23 @@ import javax.inject.{ Inject, Singleton }
66
import scala.collection.immutable
77
import play.api.{ Configuration, Logger }
88
import play.api.libs.json.JsObject
9-
import org.elastic4play.models.{ Attribute, AttributeDef, EntityDef, EnumerationAttributeFormat, ModelDef, MultiAttributeFormat, ObjectAttributeFormat, OptionalAttributeFormat, StringAttributeFormat, AttributeFormat F, AttributeOption O }
9+
import org.elastic4play.models.{ Attribute, AttributeFormat, AttributeDef, EntityDef, EnumerationAttributeFormat, ListEnumeration, ModelDef, MultiAttributeFormat, ObjectAttributeFormat, OptionalAttributeFormat, StringAttributeFormat, AttributeOption O }
1010
import org.elastic4play.services.AuditableAction
1111
import org.elastic4play.services.JsonFormat.auditableActionFormat
1212
import services.AuditedModel
1313

1414
trait AuditAttributes { _: AttributeDef
1515
def detailsAttributes: Seq[Attribute[_]]
1616

17-
val operation: A[AuditableAction.Value] = attribute("operation", F.enumFmt(AuditableAction), "Operation", O.readonly)
18-
val details: A[JsObject] = attribute("details", F.objectFmt(detailsAttributes), "Details", JsObject(Nil), O.readonly)
19-
val otherDetails: A[Option[String]] = optionalAttribute("otherDetails", F.textFmt, "Other details", O.readonly)
20-
val objectType: A[String] = attribute("objectType", F.stringFmt, "Table affected by the operation", O.readonly)
21-
val objectId: A[String] = attribute("objectId", F.stringFmt, "Object targeted by the operation", O.readonly)
22-
val base: A[Boolean] = attribute("base", F.booleanFmt, "Indicates if this operation is the first done for a http query", O.readonly)
23-
val startDate: A[Date] = attribute("startDate", F.dateFmt, "Date and time of the operation", new Date, O.readonly)
24-
val rootId: A[String] = attribute("rootId", F.stringFmt, "Root element id (routing id)", O.readonly)
25-
val requestId: A[String] = attribute("requestId", F.stringFmt, "Id of the request that do the operation", O.readonly)
17+
val operation: A[AuditableAction.Value] = attribute("operation", AttributeFormat.enumFmt(AuditableAction), "Operation", O.readonly)
18+
val details: A[JsObject] = attribute("details", AttributeFormat.objectFmt(detailsAttributes), "Details", JsObject(Nil), O.readonly)
19+
val otherDetails: A[Option[String]] = optionalAttribute("otherDetails", AttributeFormat.textFmt, "Other details", O.readonly)
20+
val objectType: A[String] = attribute("objectType", AttributeFormat.stringFmt, "Table affected by the operation", O.readonly)
21+
val objectId: A[String] = attribute("objectId", AttributeFormat.stringFmt, "Object targeted by the operation", O.readonly)
22+
val base: A[Boolean] = attribute("base", AttributeFormat.booleanFmt, "Indicates if this operation is the first done for a http query", O.readonly)
23+
val startDate: A[Date] = attribute("startDate", AttributeFormat.dateFmt, "Date and time of the operation", new Date, O.readonly)
24+
val rootId: A[String] = attribute("rootId", AttributeFormat.stringFmt, "Root element id (routing id)", O.readonly)
25+
val requestId: A[String] = attribute("requestId", AttributeFormat.stringFmt, "Id of the request that do the operation", O.readonly)
2626
}
2727

2828
@Singleton
@@ -37,46 +37,64 @@ class AuditModel(
3737
configuration.getString("audit.name").get,
3838
auditedModels)
3939

40-
lazy val log = Logger(getClass)
40+
lazy val logger = Logger(getClass)
4141

42-
def detailsAttributes: Seq[Attribute[_]] = {
43-
auditedModels
44-
.flatMap(_.attributes)
45-
.flatMap {
46-
// if attribute is object, add sub attributes
47-
case attr @ Attribute(_, _, ObjectAttributeFormat(subAttributes), _, _, _) attr +: subAttributes
48-
case attr Seq(attr)
49-
}
50-
.filter(_.isModel)
51-
.map {
52-
case attr @ Attribute(_, _, OptionalAttributeFormat(_), _, _, _) attr
53-
case attr @ Attribute(_, _, MultiAttributeFormat(_), _, _, _) attr
54-
case attr attr.toOptional
55-
}
42+
def mergeAttributeFormat(context: String, format1: AttributeFormat[_], format2: AttributeFormat[_]): Option[AttributeFormat[_]] = {
43+
(format1, format2) match {
44+
case (OptionalAttributeFormat(f1), f2) mergeAttributeFormat(context, f1, f2)
45+
case (f1, OptionalAttributeFormat(f2)) mergeAttributeFormat(context, f1, f2)
46+
case (MultiAttributeFormat(f1), MultiAttributeFormat(f2)) mergeAttributeFormat(context, f1, f2).map(MultiAttributeFormat(_))
47+
case (f1, EnumerationAttributeFormat(_) | ListEnumeration(_)) mergeAttributeFormat(context, f1, StringAttributeFormat)
48+
case (EnumerationAttributeFormat(_) | ListEnumeration(_), f2) mergeAttributeFormat(context, StringAttributeFormat, f2)
49+
case (ObjectAttributeFormat(subAttributes1), ObjectAttributeFormat(subAttributes2)) mergeAttributes(context, subAttributes1 ++ subAttributes2)
50+
case (f1, f2) if f1 == f2 Some(f1)
51+
case (f1, f2)
52+
logger.warn(s"$f1 != $f2")
53+
None
54+
55+
}
56+
}
57+
58+
def mergeAttributes(context: String, attributes: Seq[Attribute[_]]): Option[ObjectAttributeFormat] = {
59+
val mergeAttributes: Iterable[Option[Attribute[_]]] = attributes
5660
.groupBy(_.name)
57-
.flatMap {
58-
// if only one attribute is found for a name, get it
59-
case (_, attribute @ Seq(_)) attribute
60-
// otherwise, check if attribute format is compatible
61-
case (_, attributes)
62-
attributes.headOption.foreach { first
63-
val isSensitive = first.isSensitive
64-
val formatName = first.format.name
65-
if (!attributes.forall(a a.isSensitive == isSensitive && a.format.name == formatName)) {
66-
log.error("Mapping is not consistent :")
67-
attributes.foreach { attr
68-
val s = if (attr.isSensitive) " (is sensitive)" else ""
69-
log.error(s"\t${attr.name} : ${attr.format.name} $s")
70-
}
71-
}
72-
}
73-
attributes.headOption
74-
}
7561
.map {
76-
case attr @ Attribute(_, _, EnumerationAttributeFormat(_), _, _, _) attr.copy(format = StringAttributeFormat, defaultValue = None)
77-
case attr attr
62+
case (_name, _attributes)
63+
_attributes
64+
.map(a Some(a.format))
65+
.reduce[Option[AttributeFormat[_]]] {
66+
case (Some(f1), Some(f2)) mergeAttributeFormat(context + "." + _name, f1, f2)
67+
case _ None
68+
}
69+
.map {
70+
case oaf: OptionalAttributeFormat[_] oaf: AttributeFormat[_]
71+
case maf: MultiAttributeFormat[_] maf: AttributeFormat[_]
72+
case f OptionalAttributeFormat(f): AttributeFormat[_]
73+
}
74+
.map(format Attribute("audit", _name, format, Nil, None, ""))
75+
.orElse {
76+
logger.error(s"Mapping is not consistent on attribute $context:\n${_attributes.map(a a.modelName + "/" + a.name + ": " + a.format.name).mkString("\n")}")
77+
None
78+
}
7879
}
79-
.toSeq
80+
81+
if (mergeAttributes.exists(_.isEmpty))
82+
None
83+
else
84+
Some(ObjectAttributeFormat(mergeAttributes.flatten.toSeq))
85+
}
86+
def detailsAttributes: Seq[Attribute[_]] = {
87+
mergeAttributes("audit", auditedModels
88+
.flatMap(_.attributes)
89+
.filter(a a.isModel && !a.isUnaudited)
90+
.toSeq)
91+
.map(_.subAttributes)
92+
.getOrElse(Nil)
93+
}
94+
95+
logger.info("Audit attributes:")
96+
detailsAttributes.foreach { a
97+
logger.info(s"\t${a.name}: ${a.format}")
8098
}
8199
override def apply(attributes: JsObject): Audit = new Audit(this, attributes)
82100
}

0 commit comments

Comments
 (0)