diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/EventLogRepository.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/EventLogRepository.scala
index 9c4a29e052f..6ddb0e78481 100644
--- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/EventLogRepository.scala
+++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/EventLogRepository.scala
@@ -374,6 +374,8 @@ trait EventLogRepository {
*/
def getEventLogByCriteria(criteria: Option[String], limit :Option[Int] = None, orderBy: Option[String] = None) : IOResult[Vector[EventLog]]
+ def getEventLogCount: IOResult[Long]
+
def getEventLogByChangeRequest(
changeRequest : ChangeRequestId
, xpath : String
diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/jdbc/EventLogJdbcRepository.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/jdbc/EventLogJdbcRepository.scala
index 68aa070dcdb..aab153af5d2 100644
--- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/jdbc/EventLogJdbcRepository.scala
+++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/jdbc/EventLogJdbcRepository.scala
@@ -200,6 +200,16 @@ class EventLogJdbcRepository(
}).transact(xa))
}
+ def getEventLogCount: IOResult[Long] = {
+ val q ="SELECT count(*) FROM eventlog"
+ //sql"SELECT count(*) FROM eventlog".query[Long].option.transact(xa).unsafeRunSync
+ transactIOResult(s"Error when retrieving event logs count with request: ${q}")(xa => (for {
+ entries <- query[Long](q).unique
+ } yield {
+ entries
+ }).transact(xa))
+ }
+
def getEventLogByCriteria(criteria : Option[String], optLimit:Option[Int] = None, orderBy:Option[String]) : IOResult[Vector[EventLog]] = {
val where = criteria.map(c => s"where ${c}").getOrElse("")
diff --git a/webapp/sources/rudder/rudder-rest/pom.xml b/webapp/sources/rudder/rudder-rest/pom.xml
index ee598f7b4c4..d073c007ca0 100644
--- a/webapp/sources/rudder/rudder-rest/pom.xml
+++ b/webapp/sources/rudder/rudder-rest/pom.xml
@@ -98,6 +98,11 @@ along with Rudder. If not, see .
+
+ org.springframework.security
+ spring-security-core
+ ${spring-security-version}
+ com.normation.rudder
diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/internal/EventLogAPI.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/internal/EventLogAPI.scala
new file mode 100644
index 00000000000..3a8695792a8
--- /dev/null
+++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/internal/EventLogAPI.scala
@@ -0,0 +1,161 @@
+/*
+*************************************************************************************
+* Copyright 2019 Normation SAS
+*************************************************************************************
+*
+* This file is part of Rudder.
+*
+* Rudder is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* In accordance with the terms of section 7 (7. Additional Terms.) of
+* the GNU General Public License version 3, the copyright holders add
+* the following Additional permissions:
+* Notwithstanding to the terms of section 5 (5. Conveying Modified Source
+* Versions) and 6 (6. Conveying Non-Source Forms.) of the GNU General
+* Public License version 3, when you create a Related Module, this
+* Related Module is not considered as a part of the work and may be
+* distributed under the license agreement of your choice.
+* A "Related Module" means a set of sources files including their
+* documentation that, without modification of the Source Code, enables
+* supplementary functions or services in addition to those offered by
+* the Software.
+*
+* Rudder is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with Rudder. If not, see .
+
+*
+*************************************************************************************
+*/
+
+package com.normation.rudder.rest.internal
+import com.normation.box._
+import com.normation.eventlog._
+import com.normation.rudder.repository.EventLogRepository
+import com.normation.rudder.rest.RestExtractorService
+import com.normation.rudder.rest.RestUtils._
+import com.normation.rudder.web.components.DateFormaterService
+import com.normation.rudder.web.services._
+import net.liftweb.common._
+import net.liftweb.http.rest.RestHelper
+import net.liftweb.http.{JsonResponse, LiftResponse, Req, S}
+import net.liftweb.json.JValue
+import net.liftweb.json.JsonDSL._
+
+class EventLogAPI (
+ repos: EventLogRepository
+ , restExtractor : RestExtractorService
+ , eventLogDetail : EventListDisplayer
+) extends RestHelper with Loggable {
+
+ def serialize(event: EventLog): JValue = {
+ import net.liftweb.json.JsonDSL._
+
+ ( ("id" -> (event.id.map(_.toString).getOrElse("Unknown"): String))
+ ~ ("date" -> DateFormaterService.getFormatedDate(event.creationDate))
+ ~ ("actor" -> event.principal.name)
+ ~ ("type" -> S.?("rudder.log.eventType.names." + event.eventType.serialize))
+ ~ ("description" -> eventLogDetail.displayDescription(event).toString)
+ ~ ("hasDetails" -> (if(event.details != ) true else false))
+ )
+ }
+
+ def responseFormater(draw: Int, totalRecord: Long, totalFiltered: Long, logs: Vector[EventLog], errorMsg: Option[String] = None): JValue = {
+ errorMsg match {
+ case Some(msg) =>
+ ( ("draw" -> draw)
+ ~ ("recordsTotal" -> totalRecord)
+ ~ ("recordsFiltered" -> totalFiltered)
+ ~ ("data" -> logs.map(serialize))
+ ~ ("error" -> msg)
+ )
+ case _ =>
+ ( ("draw" -> draw)
+ ~ ("recordsTotal" -> totalRecord)
+ ~ ("recordsFiltered" -> totalFiltered)
+ ~ ("data" -> logs.map(serialize))
+ )
+ }
+ }
+
+ def getEventLogBySlice(start: Int, nbelement: Int, criteria : Option[String], optLimit:Option[Int] = None, orderBy:Option[String]): Box[(Int,Vector[EventLog])] = {
+ repos.getEventLogByCriteria(criteria, optLimit, orderBy).toBox match {
+ case Full(events) => Full((events.size, events.slice(start,start+nbelement)))
+ case eb: EmptyBox => eb ?~! s"Error when trying fetch eventlogs from database for page ${(start/nbelement)+1}"
+ }
+ }
+
+ def requestDispatch: PartialFunction[Req, () => Box[LiftResponse]] = {
+ case Get(Nil, req) =>
+
+ val draw = req.params.get("draw") match {
+ case Some(value :: Nil) => Full(value.toInt)
+ case None => Failure("Missing 'draw' field from datatable's request")
+ }
+ val start = req.params.get("start") match {
+ case Some(value :: Nil) => Full(value)
+ case None => Failure("Missing 'start' field from datatable's request")
+ }
+ val length = req.params.get("length") match {
+ case Some(value :: Nil) => Full(value)
+ case None => Failure("Missing 'length' field from datatable's request")
+ }
+
+ val response = (draw, start, length) match {
+ case (Full(d), Full(s), Full(l)) =>
+ repos.getEventLogCount.toBox match {
+ case Full(totalRecord) =>
+ getEventLogBySlice(s.toInt, l.toInt, None, None, Some("creationdate DESC" )) match {
+ case Full((totalFiltered, events)) =>
+ responseFormater(d, totalRecord, totalFiltered, events)
+ case eb: EmptyBox =>
+ val fail = eb ?~! "Failed to get eventlogs"
+ responseFormater(d, totalRecord, 0, Vector.empty, Some(fail.messageChain))
+ }
+ case eb: EmptyBox =>
+ val fail = eb ?~! "Failed to get event log's count"
+ responseFormater(d, 0, 0, Vector.empty, Some(fail.messageChain))
+ }
+
+ case (ebDraw: EmptyBox,_, _) =>
+ val fail = ebDraw ?~! "Missing parameter in request"
+ responseFormater(0, 0, 0, Vector.empty, Some(fail.messageChain))
+
+ case (_,ebStart: EmptyBox, _) =>
+ val fail = ebStart ?~! "Missing parameter in request"
+ responseFormater(0, 0, 0, Vector.empty, Some(fail.messageChain))
+
+ case (_,_, ebLength: EmptyBox) =>
+ val fail = ebLength ?~! "Missing parameter in request"
+ responseFormater(0, 0, 0, Vector.empty, Some(fail.messageChain))
+ }
+ JsonResponse(response, Nil, Nil, 200)
+
+ case Get(id :: "details" :: Nil, _) =>
+ repos.getEventLogByCriteria(Some(s"id = $id")).toBox match {
+ case Full(e) =>
+ e.headOption match {
+ case Some(eventLog) =>
+ val crid = eventLog.id.flatMap(repos.getEventLogWithChangeRequest(_).toBox match {
+ case Full(Some((_, crId))) => crId
+ case _ => None
+ })
+ val htmlDetails = eventLogDetail.displayDetails(eventLog, crid)
+ toJsonResponse(None, "content" -> htmlDetails.toString())("eventdetails", prettify = false)
+ case None =>
+ toJsonError(None, s"EventLog $id not found")("eventdetails", prettify = false)
+ }
+ case eb: EmptyBox =>
+ val e = eb ?~! s"Error when trying to retrieve eventlog : $id"
+ toJsonError(None, e.messageChain)("eventdetails", prettify = false)
+ }
+ }
+ serve("secure" / "api" / "eventlog" prefix requestDispatch)
+}
diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/CurrentUserService.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/CurrentUserService.scala
new file mode 100644
index 00000000000..533218b7d44
--- /dev/null
+++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/CurrentUserService.scala
@@ -0,0 +1,86 @@
+/*
+*************************************************************************************
+* Copyright 2011 Normation SAS
+*************************************************************************************
+*
+* This file is part of Rudder.
+*
+* Rudder is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* In accordance with the terms of section 7 (7. Additional Terms.) of
+* the GNU General Public License version 3, the copyright holders add
+* the following Additional permissions:
+* Notwithstanding to the terms of section 5 (5. Conveying Modified Source
+* Versions) and 6 (6. Conveying Non-Source Forms.) of the GNU General
+* Public License version 3, when you create a Related Module, this
+* Related Module is not considered as a part of the work and may be
+* distributed under the license agreement of your choice.
+* A "Related Module" means a set of sources files including their
+* documentation that, without modification of the Source Code, enables
+* supplementary functions or services in addition to those offered by
+* the Software.
+*
+* Rudder is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with Rudder. If not, see .
+
+*
+*************************************************************************************
+*/
+
+package com.normation.rudder.web.services
+
+import com.normation.rudder.{AuthorizationType, Rights, RudderAccount, User}
+import com.normation.rudder.api.ApiAuthorization
+import net.liftweb.http.SessionVar
+import org.springframework.security.core.context.SecurityContextHolder
+
+/**
+ * An utility class that get the currently logged user
+ * (if any)
+ *
+ */
+object CurrentUserService extends SessionVar[Option[RudderUserDetail]] ({
+ SecurityContextHolder.getContext.getAuthentication match {
+ case null => None
+ case auth => auth.getPrincipal match {
+ case u:RudderUserDetail => Some(u)
+ case _ => None
+ }
+ }
+
+}) with User {
+
+ def getRights : Rights = this.get match {
+ case Some(u) => u.authz
+ case None => new Rights(AuthorizationType.NoRights)
+ }
+
+ def account : RudderAccount = this.get match {
+ case None => RudderAccount.User("unknown", "")
+ case Some(u) => u.account
+ }
+
+ def checkRights(auth:AuthorizationType) : Boolean = {
+ val authz = getRights.authorizationTypes
+ if (authz.contains(AuthorizationType.NoRights)) false
+ else auth match{
+ case AuthorizationType.NoRights => false
+ case _ => authz.contains(auth)
+ }
+ }
+
+ def getApiAuthz: ApiAuthorization = {
+ this.get match {
+ case None => ApiAuthorization.None
+ case Some(u) => u.apiAuthz
+ }
+ }
+}
diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/DiffDisplayer.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/DiffDisplayer.scala
similarity index 69%
rename from webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/DiffDisplayer.scala
rename to webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/DiffDisplayer.scala
index d461a32a1ac..d1b751da901 100644
--- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/DiffDisplayer.scala
+++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/DiffDisplayer.scala
@@ -37,17 +37,14 @@
package com.normation.rudder.web.services
-import scala.xml.NodeSeq
-import com.normation.rudder.domain.policies.DirectiveId
-import bootstrap.liftweb.RudderConfig
+import com.normation.rudder.domain.policies.{DirectiveId, _}
import com.normation.rudder.repository.FullNodeGroupCategory
-import com.normation.rudder.rule.category.RuleCategoryId
-import net.liftweb.common.Full
-import net.liftweb.common.EmptyBox
-import net.liftweb.common.Loggable
-import com.normation.rudder.domain.policies._
-import com.normation.rudder.rule.category.RuleCategory
+import com.normation.rudder.rule.category.{RuleCategory, RuleCategoryId, RuleCategoryService}
+import com.normation.rudder.web.model.LinkUtil
+import net.liftweb.common.{EmptyBox, Full, Loggable}
+
import scala.language.implicitConversions
+import scala.xml.NodeSeq
trait DiffItem[T] {
@@ -56,39 +53,39 @@ trait DiffItem[T] {
}
case class Added[T](
- value:T
+ value:T
) extends DiffItem[T] {
val newValue = Some(value)
def display(implicit displayer : T => NodeSeq) : NodeSeq =
- + {displayer(value)}
+ + {displayer(value)}
}
case class Deleted[T](
- value:T
+ value:T
) extends DiffItem[T] {
def display(implicit displayer : T => NodeSeq) : NodeSeq =
- - {displayer(value)}
+ - {displayer(value)}
}
case class Unchanged[T](
- value:T
+ value:T
) extends DiffItem[T] {
def display(implicit displayer : T => NodeSeq) : NodeSeq =
- {displayer(value)}
+ {displayer(value)}
}
// Not used yet, but for later use
case class Modified[T](
- oldValue:T
+ oldValue:T
, newValue:T
) extends DiffItem[T] {
@@ -96,20 +93,17 @@ case class Modified[T](
private[this] val add = Added(oldValue)
def display(implicit displayer : T => NodeSeq) : NodeSeq =
- delete.display ++ add.display
+ delete.display ++ add.display
}
-object DiffDisplayer extends Loggable {
-
- //Directive targets Displayer
- private[this] val linkUtil = RudderConfig.linkUtil
+class DiffDisplayer(linkUtil: LinkUtil) extends Loggable {
private[this] implicit def displayDirective(directiveId: DirectiveId) = {
Directive {linkUtil.createDirectiveLink(directiveId)}
}
def displayDirectiveChangeList (
- oldDirectives:Seq[DirectiveId]
- , newDirectives:Seq[DirectiveId]
+ oldDirectives:Seq[DirectiveId]
+ , newDirectives:Seq[DirectiveId]
) : NodeSeq = {
// First, find unchanged and deleted (have find no clean way to make a 3 way partition)
@@ -123,19 +117,19 @@ object DiffDisplayer extends Loggable {
val changeMap:Seq[DiffItem[DirectiveId]] = deletedMap ++ unchangedMap ++ added
{ for {
- change <- changeMap
- } yield {
- // Implicit used here (displayDirective)
- change.display
- } }
+ change <- changeMap
+ } yield {
+ // Implicit used here (displayDirective)
+ change.display
+ } }
}
// Almost the same as display Directive see comments there for more details
def displayRuleTargets (
- oldTargets:Seq[RuleTarget]
- , newTargets:Seq[RuleTarget]
- , groupLib: FullNodeGroupCategory
+ oldTargets:Seq[RuleTarget]
+ , newTargets:Seq[RuleTarget]
+ , groupLib: FullNodeGroupCategory
) : NodeSeq = {
implicit def displayNodeGroup(target: RuleTarget) : NodeSeq= {
@@ -146,16 +140,16 @@ object DiffDisplayer extends Loggable {
Nodes that belongs to all these groups:
{targets.map(t =>
{displayNodeGroup(t)}
)}
case TargetExclusion(included,excluded) =>
Include {displayNodeGroup(included)}
- Exclude {displayNodeGroup(excluded)}
+ Exclude {displayNodeGroup(excluded)}
case GroupTarget(nodeGroupId) =>
Group {linkUtil.createGroupLink(nodeGroupId)}
case x => groupLib.allTargets.get(x).map{ targetInfo =>
-
- {targetInfo.name}
- {if (targetInfo.isSystem) (System)}
-
- }.getOrElse( {x.target})
+
+ {targetInfo.name}
+ {if (targetInfo.isSystem) (System)}
+
+ }.getOrElse( {x.target})
}
}
@@ -166,30 +160,30 @@ object DiffDisplayer extends Loggable {
case _:TargetUnion =>
all Nodes from:
case _:TargetIntersection =>
- Nodes that belongs to all these groups:
+ Nodes that belongs to all these groups:
}
}
- val includedKind = {
- ((newIncluded,oldIncluded) match {
- case (_:TargetUnion,_:TargetUnion) | (_:TargetIntersection,_:TargetIntersection) =>
- Seq(Unchanged (newIncluded))
- case _ =>
- (Seq(Deleted(oldIncluded),Added(newIncluded)))
- }).flatMap(_.display(displayKind))
- }
- val excludedKind = {
- ((newExcluded,oldExcluded) match {
- case (_:TargetUnion,_:TargetUnion) | (_:TargetIntersection,_:TargetIntersection) =>
- Seq(Unchanged (newExcluded))
- case _ =>
- (Seq(Deleted(oldExcluded),Added(newExcluded)))
- }).flatMap(_.display(displayKind))
- }
- val includedTargets = displayRuleTargets(newIncluded.targets.toSeq,oldIncluded.targets.toSeq, groupLib)
- val excludedTargets = displayRuleTargets(newExcluded.targets.toSeq,oldExcluded.targets.toSeq, groupLib)
- Include ++ includedKind ++ includedTargets ++
- Exclude ++ excludedKind ++ excludedTargets
+ val includedKind = {
+ ((newIncluded,oldIncluded) match {
+ case (_:TargetUnion,_:TargetUnion) | (_:TargetIntersection,_:TargetIntersection) =>
+ Seq(Unchanged (newIncluded))
+ case _ =>
+ (Seq(Deleted(oldIncluded),Added(newIncluded)))
+ }).flatMap(_.display(displayKind))
+ }
+ val excludedKind = {
+ ((newExcluded,oldExcluded) match {
+ case (_:TargetUnion,_:TargetUnion) | (_:TargetIntersection,_:TargetIntersection) =>
+ Seq(Unchanged (newExcluded))
+ case _ =>
+ (Seq(Deleted(oldExcluded),Added(newExcluded)))
+ }).flatMap(_.display(displayKind))
+ }
+ val includedTargets = displayRuleTargets(newIncluded.targets.toSeq,oldIncluded.targets.toSeq, groupLib)
+ val excludedTargets = displayRuleTargets(newExcluded.targets.toSeq,oldExcluded.targets.toSeq, groupLib)
+ Include ++ includedKind ++ includedTargets ++
+ Exclude ++ excludedKind ++ excludedTargets
case (_,_) =>
val (unchanged,deleted) = oldTargets.partition(newTargets.contains)
@@ -200,20 +194,21 @@ object DiffDisplayer extends Loggable {
val changeMap:Seq[DiffItem[RuleTarget]] = deletedMap ++ unchangedMap ++ added
{ for {
- change <- changeMap
- } yield {
- // Implicit used here (displayNodeGroup)
- change.display
- }
+ change <- changeMap
+ } yield {
+ // Implicit used here (displayNodeGroup)
+ change.display
+ }
}
+ { for {
change <- changes
} yield {
// Implicit used here (displayRuleCategory)
change.display
}
- }
-
+ }
+
case None => displayRuleCategory(oldCategory)
}
diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/EventLogDetailsService.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/EventLogDetailsService.scala
new file mode 100644
index 00000000000..890aa6c57d5
--- /dev/null
+++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/EventLogDetailsService.scala
@@ -0,0 +1,1567 @@
+/*
+*************************************************************************************
+* Copyright 2011 Normation SAS
+*************************************************************************************
+*
+* This file is part of Rudder.
+*
+* Rudder is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* In accordance with the terms of section 7 (7. Additional Terms.) of
+* the GNU General Public License version 3, the copyright holders add
+* the following Additional permissions:
+* Notwithstanding to the terms of section 5 (5. Conveying Modified Source
+* Versions) and 6 (6. Conveying Non-Source Forms.) of the GNU General
+* Public License version 3, when you create a Related Module, this
+* Related Module is not considered as a part of the work and may be
+* distributed under the license agreement of your choice.
+* A "Related Module" means a set of sources files including their
+* documentation that, without modification of the Source Code, enables
+* supplementary functions or services in addition to those offered by
+* the Software.
+*
+* Rudder is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with Rudder. If not, see .
+
+*
+*************************************************************************************
+*/
+package com.normation.rudder.web.services
+
+import com.normation.box._
+import com.normation.cfclerk.domain.TechniqueName
+import com.normation.eventlog.EventLog
+import com.normation.inventory.domain.NodeId
+import com.normation.rudder.api._
+import com.normation.rudder.batch.{ErrorStatus, SuccessStatus}
+import com.normation.rudder.domain.eventlog.{WorkflowStepChanged, _}
+import com.normation.rudder.domain.nodes._
+import com.normation.rudder.domain.parameters._
+import com.normation.rudder.domain.policies._
+import com.normation.rudder.domain.queries.Query
+import com.normation.rudder.domain.workflows.{ChangeRequestId, WorkflowStepChange}
+import com.normation.rudder.reports.{AgentRunInterval, HeartbeatConfiguration}
+import com.normation.rudder.repository._
+import com.normation.rudder.rule.category.{RoRuleCategoryRepository, RuleCategory}
+import com.normation.rudder.services.eventlog.{EventLogDetailsService, RollbackInfo}
+import com.normation.rudder.services.modification.ModificationService
+import com.normation.rudder.services.nodes.NodeInfoService
+import com.normation.rudder.services.user.PersonIdentService
+import com.normation.rudder.web.components.DateFormaterService
+import com.normation.rudder.web.model.LinkUtil
+import net.liftweb.common._
+import net.liftweb.http.{S, SHtml}
+import net.liftweb.http.js.JE._
+import net.liftweb.http.js.JsCmds._
+import net.liftweb.http.js._
+import net.liftweb.json._
+import net.liftweb.util.Helpers._
+import org.eclipse.jgit.lib.PersonIdent
+import org.joda.time.DateTime
+import org.joda.time.format.{DateTimeFormat, ISODateTimeFormat}
+
+import scala.util.{Success, Try, Failure => Catch}
+import scala.xml._
+
+/**
+ * Used to display the event list, in the pending modification (AsyncDeployment),
+ * or in the administration EventLogsViewer
+ */
+class EventListDisplayer(
+ logDetailsService : EventLogDetailsService
+ , repos : EventLogRepository
+ , nodeGroupRepository : RoNodeGroupRepository
+ , directiveRepository : RoDirectiveRepository
+ , nodeInfoService : NodeInfoService
+ , ruleCatRepository : RoRuleCategoryRepository
+ , modificationService : ModificationService
+ , personIdentService : PersonIdentService
+ , linkUtil : LinkUtil
+) extends Loggable {
+
+ private[this] val xmlPretty = new scala.xml.PrettyPrinter(80, 2)
+
+ private[this] val gridName = "eventLogsGrid"
+
+ def display(refreshEvents:() => Box[Seq[EventLog]]) : NodeSeq = {
+ val limit: Int = 500
+ //common part between last events and interval
+ def displayEvents(events: Box[Seq[EventLog]]) :JsCmd = {
+ events match {
+ case Full(events) =>
+ val lines = {
+ val el = events.map(EventLogLine(_)).toList.sortWith(_.event.creationDate.getMillis > _.event.creationDate.getMillis)
+ if(el.size > limit) JsTableData(el.take(limit)) else JsTableData(el)
+ }
+ JsRaw(s"refreshTable('${gridName}',${lines.json.toJsCmd})")
+ case eb : EmptyBox =>
+ val fail = eb ?~! "Could not get latest event logs"
+ logger.error(fail.messageChain)
+ val xml =
Error when trying to get last event logs. Error message was: {fail.messageChain}
+ SetHtml("eventLogsError",xml)
+ }
+ }
+
+ def getLastEvents : JsCmd = {
+ displayEvents(refreshEvents())
+ }
+
+ def getEventsInterval(jsonInterval: String): JsCmd = {
+ import java.sql.Timestamp
+ val format = DateTimeFormat.forPattern("YYYY-MM-dd HH:mm:ss")
+
+ displayEvents(for {
+ parsed <- tryo(parse(jsonInterval)) ?~! s"Error when trying to parse '${jsonInterval}' as a JSON datastructure with fields 'start' and 'end'"
+ startStr <- parsed \ "start" match {
+ case JString(startStr) if startStr.nonEmpty =>
+ val date = tryo(DateTime.parse(startStr, format)) ?~! s"Error when trying to parse start date '${startStr}"
+ date match {
+ case Full(d) => Full(Some(new Timestamp(d.getMillis)))
+ case eb: EmptyBox =>
+ eb ?~! s"Invalid start date"
+ }
+ case _ => Full(None)
+ }
+ endStr <- parsed \ "end" match {
+ case JString(endStr) if endStr.nonEmpty =>
+ val date = tryo(DateTime.parse(endStr, format)) ?~! s"Error when trying to parse end date '${endStr}"
+ date match {
+ case Full(d) => Full(Some(new Timestamp(d.getMillis)))
+ case eb: EmptyBox =>
+ eb ?~! s"Invalid end date"
+ }
+ case _ => Full(None)
+ }
+ whereStatement = (startStr, endStr) match {
+ case (None, None) => None
+ case (Some(start), None) => Some(s" creationdate > '$start'")
+ case (None, Some(end)) => Some(s" creationdate < '$end'")
+ case (Some(start), Some(end)) =>
+ val orderedDate = if(start.after(end)) (end, start) else (start, end)
+ Some(s" creationdate > '${orderedDate._1}' and creationdate < '${orderedDate._2}'")
+ }
+ logs <- repos.getEventLogByCriteria(whereStatement, None, Some("id DESC")).toBox
+ } yield {
+ logs
+ })
+ }
+
+ val refresh = AnonFunc(SHtml.ajaxInvoke( () => getLastEvents))
+
+ Script(OnLoad(JsRaw(s"""
+ var pickEventLogsInInterval = ${AnonFunc(SHtml.ajaxCall(JsRaw(
+ """'{"start":"'+$(".pickStartInput").val()+'", "end":"'+$(".pickEndInput").val()+'"}'"""
+ ), getEventsInterval)._2).toJsCmd}
+ var refreshEventLogs = ${refresh.toJsCmd};
+ createEventLogTable('${gridName}',[], '${S.contextPath}', refreshEventLogs, pickEventLogsInInterval)
+ refreshEventLogs();
+ """)))
+
+ }
+
+ /*
+ * Javascript object containing all data to create a line in event logs table
+ * { "id" : Event log id [Int]
+ * , "date": date the event log was produced [Date/String]
+ * , "actor": Name of the actor making the event [String]
+ * , "type" : Type of the event log [String]
+ * , "description" : Description of the event [String]
+ * , "details" : function/ajax call, setting the details content, takes the id of the element to set [Function(String)]
+ * , "hasDetails" : do our event needs to display details (do we need to be able to open the row [Boolean]
+ * }
+ */
+ case class EventLogLine(event : EventLog) extends JsTableLine {
+ val json = {
+ JsObj(
+ "id" -> (event.id.map(_.toString).getOrElse("Unknown"): String)
+ , "date" -> DateFormaterService.getFormatedDate(event.creationDate)
+ , "actor" -> event.principal.name
+ , "type" -> S.?("rudder.log.eventType.names." + event.eventType.serialize)
+ , "description" -> displayDescription(event).toString
+ , "hasDetails" -> boolToJsExp(event.details != )
+ )
+ }
+ }
+
+ //////////////////// Display description/details of ////////////////////
+
+ //convention: "X" means "ignore"
+
+ def displayDescription(event:EventLog) = {
+ import linkUtil._
+ def crDesc(x:EventLog, actionName: NodeSeq) = {
+ val id = (x.details \ "rule" \ "id").text
+ val name = (x.details \ "rule" \ "displayName").text
+ Text("Rule ") ++ {
+ if(id.size < 1) Text(name)
+ else {name} ++ actionName
+ }
+ }
+
+ def piDesc(x:EventLog, actionName: NodeSeq) = {
+ val id = (x.details \ "directive" \ "id").text
+ val name = (x.details \ "directive" \ "displayName").text
+ Text("Directive ") ++ {
+ if(id.size < 1) Text(name)
+ else {name} ++ actionName
+ }
+ }
+
+ def groupDesc(x:EventLog, actionName: NodeSeq) = {
+ val id = (x.details \ "nodeGroup" \ "id").text
+ val name = (x.details \ "nodeGroup" \ "displayName").text
+ Text("Group ") ++ {
+ if(id.size < 1) Text(name)
+ else {name} ++ actionName
+ }
+ }
+
+ def nodeDesc(x:EventLog, actionName: NodeSeq) = {
+ val id = (x.details \\ "node" \ "id").text
+ val name = (x.details \\ "node" \ "hostname").text
+ Text("Node ") ++ {
+ if ((id.size < 1)||(actionName==Text(" deleted"))) Text(name)
+ else {name} ++ actionName
+ }
+ }
+
+ def techniqueDesc(x:EventLog, actionName: NodeSeq) = {
+ val name = (x.details \ "activeTechnique" \ "techniqueName").text
+ Text("Technique %s".format(name)) ++ actionName
+ }
+
+ def globalParamDesc(x:EventLog, actionName: NodeSeq) = {
+ val name = (x.details \ "globalParameter" \ "name").text
+ Text(s"Global Parameter ${name} ${actionName}")
+ }
+
+ def changeRequestDesc(x:EventLog, actionName: NodeSeq) = {
+ val name = (x.details \ "changeRequest" \ "name").text
+ val idNode = (x.details \ "changeRequest" \ "id").text.trim
+ val xml = Try(idNode.toInt) match {
+ case Success(id) =>
+ if (actionName==Text(" deleted"))
+ Text(name)
+ else
+ {name}
+
+ case Catch(e) =>
+ logger.error(s"could not translate ${idNode} to a correct chage request identifier: ${e.getMessage()}")
+ Text(name)
+ }
+ Text("Change request ") ++ xml ++ actionName
+ }
+
+ def workflowStepDesc(x:EventLog) = {
+ logDetailsService.getWorkflotStepChange(x.details) match {
+ case Full(WorkflowStepChange(crId,from,to)) =>
+ Text("Change request #") ++
+ {crId} ++
+ Text(s" status modified from ${from} to ${to}")
+
+ case eb:EmptyBox => val fail = eb ?~! "could not display workflow step event log"
+ logger.error(fail.msg)
+ Text("Change request status modified")
+ }
+ }
+
+ def apiAccountDesc(x:EventLog, actionName: NodeSeq) = {
+ val name = (x.details \ "apiAccount" \ "name").text
+ Text(s"API Account ${name} ${actionName}")
+ }
+
+ event match {
+ case x:ActivateRedButton => Text("Stop Rudder agents on all nodes")
+ case x:ReleaseRedButton => Text("Start again Rudder agents on all nodes")
+ case x:AcceptNodeEventLog => nodeDesc(x, Text(" accepted"))
+ case x:RefuseNodeEventLog => nodeDesc(x, Text(" refused"))
+ case x:DeleteNodeEventLog => nodeDesc(x, Text(" deleted"))
+ case x:LoginEventLog => Text("User '%s' login".format(x.principal.name))
+ case x:LogoutEventLog => Text("User '%s' logout".format(x.principal.name))
+ case x:BadCredentialsEventLog => Text("User '%s' failed to login: bad credentials".format(x.principal.name))
+ case x:AutomaticStartDeployement => Text("Automatically update policies")
+ case x:ManualStartDeployement => Text("Manually update policies")
+ case x:ApplicationStarted => Text("Rudder starts")
+ case x:ModifyRule => crDesc(x,Text(" modified"))
+ case x:DeleteRule => crDesc(x,Text(" deleted"))
+ case x:AddRule => crDesc(x,Text(" added"))
+ case x:ModifyDirective => piDesc(x,Text(" modified"))
+ case x:DeleteDirective => piDesc(x,Text(" deleted"))
+ case x:AddDirective => piDesc(x,Text(" added"))
+ case x:ModifyNodeGroup => groupDesc(x,Text(" modified"))
+ case x:DeleteNodeGroup => groupDesc(x,Text(" deleted"))
+ case x:AddNodeGroup => groupDesc(x,Text(" added"))
+ case x:ClearCacheEventLog => Text("Clear caches")
+ case x:UpdatePolicyServer => Text("Change Policy Server authorized network")
+ case x:ReloadTechniqueLibrary => Text("Technique library updated")
+ case x:ModifyTechnique => techniqueDesc(x, Text(" modified"))
+ case x:DeleteTechnique => techniqueDesc(x, Text(" deleted"))
+ case x:SuccessfulDeployment => Text("Successful policy update")
+ case x:FailedDeployment => Text("Failed policy update")
+ case x:ExportGroupsArchive => Text("New groups archive")
+ case x:ExportTechniqueLibraryArchive => Text("New Directive library archive")
+ case x:ExportRulesArchive => Text("New Rules archives")
+ case x:ExportParametersArchive => Text("New Parameters archives")
+ case x:ExportFullArchive => Text("New full archive")
+ case x:ImportGroupsArchive => Text("Restoring group archive")
+ case x:ImportTechniqueLibraryArchive => Text("Restoring Directive library archive")
+ case x:ImportRulesArchive => Text("Restoring Rules archive")
+ case x:ImportParametersArchive => Text("Restoring Parameters archive")
+ case x:ImportFullArchive => Text("Restoring full archive")
+ case _:AddChangeRequest => changeRequestDesc(event,Text(" created"))
+ case _:DeleteChangeRequest => changeRequestDesc(event,Text(" deleted"))
+ case _:ModifyChangeRequest => changeRequestDesc(event,Text(" modified"))
+ case _:Rollback => Text("Restore a previous state of configuration policy")
+ case x:WorkflowStepChanged => workflowStepDesc(x)
+ case x:AddGlobalParameter => globalParamDesc(x, Text(" added"))
+ case x:ModifyGlobalParameter => globalParamDesc(x, Text(" modified"))
+ case x:DeleteGlobalParameter => globalParamDesc(x, Text(" deleted"))
+ case x:CreateAPIAccountEventLog => apiAccountDesc(x, Text(" added"))
+ case x:ModifyAPIAccountEventLog => apiAccountDesc(x, Text(" modified"))
+ case x:DeleteAPIAccountEventLog => apiAccountDesc(x, Text(" deleted"))
+ case x:ModifyGlobalProperty => Text(s"Modify '${x.propertyName}' global property")
+ case x:ModifyNode => nodeDesc(x, Text(" modified"))
+ case _ => Text("Unknow event type")
+ }
+ }
+
+ def displayDetails(event:EventLog,changeRequestId:Option[ChangeRequestId]): NodeSeq = {
+
+ val groupLib = nodeGroupRepository.getFullGroupLibrary().toBox.openOr(return
System error when trying to get the group library
)
+ val rootRuleCategory = ruleCatRepository.getRootCategory.toBox.openOr(return
System error when trying to get the rule categories
)
+
+ val generatedByChangeRequest =
+ changeRequestId match {
+ case None => NodeSeq.Empty
+ case Some(id) =>
+ NodeSeq.Empty
+ //
This change was introduced by change request {SHtml.a(() => S.redirectTo(linkUtil.changeRequestLink(id)),Text(s"#${id}"))}
+ }
+ def xmlParameters(eventId: Option[Int]) = {
+ eventId match {
+ case None => NodeSeq.Empty
+ case Some(id) =>
+
+
{ event.details.map { n =>xmlPretty.format(n) + "\n"} }
+ }
+ }
+
+ val reasonHtml = {
+ val r = event.eventDetails.reason.getOrElse("")
+ if(r == "") NodeSeq.Empty
+ else
Details for that node were not in a recognized format.
+ Raw data are displayed next:
+ { xmlParameters(event.id) }
+
+
+ }
+
{
+ (event match {
+ /*
+ * bug in scalac : https://issues.scala-lang.org/browse/SI-6897
+ * A workaround is to type with a Nodeseq
+ */
+ case add:AddRule =>
+ "*" #> { val xml : NodeSeq = logDetailsService.getRuleAddDetails(add.details) match {
+ case Full(addDiff) =>
+
+ case e:EmptyBox => errorMessage(e)
+ }
+ xml
+ }
+
+ case del:DeleteRule =>
+ "*" #> { val xml : NodeSeq = logDetailsService.getRuleDeleteDetails(del.details) match {
+ case Full(delDiff) =>
+
+ case e:EmptyBox => errorMessage(e)
+ }
+ xml }
+
+ case mod:ModifyRule =>
+ val diffDisplayer = new DiffDisplayer(linkUtil)
+ "*" #> { val xml : NodeSeq = logDetailsService.getRuleModifyDetails(mod.details) match {
+ case Full(modDiff) =>
+
+ case e:EmptyBox => errorMessage(e)
+ }
+ xml }
+
+ case x:AddDirective =>
+ "*" #> { val xml : NodeSeq = logDetailsService.getDirectiveAddDetails(x.details) match {
+ case Full((diff,sectionVal)) =>
+
+ case e:EmptyBox => errorMessage(e)
+ }
+ xml }
+
+ ///////// Node Group /////////
+
+ case x:ModifyNodeGroup =>
+ "*" #> { val xml : NodeSeq = logDetailsService.getNodeGroupModifyDetails(x.details) match {
+ case Full(modDiff) =>
+
+ case e:EmptyBox => errorMessage(e)
+ }
+ xml }
+
+ case x:AddNodeGroup =>
+ "*" #> { val xml : NodeSeq = logDetailsService.getNodeGroupAddDetails(x.details) match {
+ case Full(diff) =>
+
+ case e:EmptyBox => errorMessage(e)
+ }
+ xml }
+
+ case x:DeleteNodeGroup =>
+ "*" #> { val xml : NodeSeq = logDetailsService.getNodeGroupDeleteDetails(x.details) match {
+ case Full(diff) =>
+
+ case e:EmptyBox => errorMessage(e)
+ }
+ xml }
+
+ ///////// Node Group /////////
+
+ case x:AcceptNodeEventLog =>
+ "*" #> { val xml : NodeSeq = logDetailsService.getAcceptNodeLogDetails(x.details) match {
+ case Full(details) =>
+
+ case e:EmptyBox => errorMessage(e)
+ }
+ xml }
+
+ case x:RefuseNodeEventLog =>
+ "*" #> { val xml : NodeSeq = logDetailsService.getRefuseNodeLogDetails(x.details) match {
+ case Full(details) =>
+
+ case e:EmptyBox => errorMessage(e)
+ }
+ xml }
+
+ case x:DeleteNodeEventLog =>
+ "*" #> { val xml : NodeSeq = logDetailsService.getDeleteNodeLogDetails(x.details) match {
+ case Full(details) =>
+
End Time: {DateFormaterService.getFormatedDate(ended)}
+
+ { reasonHtml }
+ { xmlParameters(event.id) }
+
+ case Full(_) => errorMessage(Failure("Unconsistant policy update status"))
+ case e:EmptyBox => errorMessage(e)
+ }
+ xml }
+
+ case x:FailedDeployment =>
+ "*" #> { val xml : NodeSeq = logDetailsService.getDeploymentStatusDetails(x.details) match {
+ case Full(ErrorStatus(id,started,ended,failure)) =>
+
+ case e:EmptyBox => logger.warn(e)
+ errorMessage(e)
+ }
+ }
+
+ case mod:ModifyGlobalProperty =>
+ "*" #> { logDetailsService.getModifyGlobalPropertyDetails(mod.details) match {
+ case Full((oldProp,newProp)) =>
+
+
Global property overview:
+
+
Name: { mod.propertyName }
+
Value:
+ { val diff = Some(SimpleDiff(oldProp.value,newProp.value))
+ displaySimpleDiff(diff,"value",newProp.value) }
+
+ { reasonHtml }
+ { xmlParameters(event.id) }
+
+ case e:EmptyBox => logger.warn(e)
+ errorMessage(e)
+ }
+ }
+
+ // Node modifiction
+ case mod:ModifyNode =>
+ "*" #> { logDetailsService.getModifyNodeDetails(mod.details) match {
+ case Full(modDiff) =>
+ logger.info(modDiff.modAgentRun)
+
+
+ { generatedByChangeRequest }
+
Node '{modDiff.id.value}' modified:
+
+
Node ID:{modDiff.id.value}
+
+ {
+ mapComplexDiff(modDiff.modAgentRun){ (optAr:Option[AgentRunInterval]) =>
+ optAr match {
+ case None => No value
+ case Some(ar) => agentRunDetails(ar)
+ }
+ }
+ }
+ {
+ mapComplexDiff(modDiff.modHeartbeat){ (optHb:Option[HeartbeatConfiguration]) =>
+ optHb match {
+ case None => No value
+ case Some(hb) => heartbeatDetails(hb)
+ }
+ }
+ }
+ {
+ mapComplexDiff(modDiff.modProperties){ (props:Seq[NodeProperty]) =>
+ nodePropertiesDetails(props)
+ }
+ }
+ {
+ mapComplexDiff(modDiff.modPolicyMode){ (optMode:Option[PolicyMode]) =>
+ optMode match {
+ case None => Use global policy mode
+ case Some(mode) => {mode.name}
+ }
+ }
+ }
+ { reasonHtml }
+ { xmlParameters(event.id) }
+
+ case e:EmptyBox => logger.warn(e)
+ errorMessage(e)
+ }
+ }
+
+ // other case: do not display details at all
+ case _ => "*" #> ""
+
+ })(event.details)}
+ Networks authorized on policy server were updated:
+
+
from:
to:
+
+
+
old value
+
new value
+
+
+
+
+ )
+
+ trait RollBackAction {
+ def name : String
+ def op : String
+ def action : (EventLog,PersonIdent,Seq[EventLog],EventLog) => Box[GitCommitId]
+ def selectRollbackedEventsRequest(id:Int) = s" id ${op} ${id} and modificationid IS NOT NULL"
+ }
+
+ case object RollbackTo extends RollBackAction{
+ val name = "after"
+ val op = ">"
+ def action = modificationService.restoreToEventLog _
+ }
+
+ case object RollbackBefore extends RollBackAction{
+ val name = "before"
+ val op = ">="
+ def action = modificationService.restoreBeforeEventLog _
+ }
+
+}
diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/JsTableData.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/JsTableData.scala
new file mode 100644
index 00000000000..4baa7aeaedf
--- /dev/null
+++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/JsTableData.scala
@@ -0,0 +1,71 @@
+/*
+*************************************************************************************
+* Copyright 2014 Normation SAS
+*************************************************************************************
+*
+* This file is part of Rudder.
+*
+* Rudder is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 3 of the License, or
+* (at your option) any later version.
+*
+* In accordance with the terms of section 7 (7. Additional Terms.) of
+* the GNU General Public License version 3, the copyright holders add
+* the following Additional permissions:
+* Notwithstanding to the terms of section 5 (5. Conveying Modified Source
+* Versions) and 6 (6. Conveying Non-Source Forms.) of the GNU General
+* Public License version 3, when you create a Related Module, this
+* Related Module is not considered as a part of the work and may be
+* distributed under the license agreement of your choice.
+* A "Related Module" means a set of sources files including their
+* documentation that, without modification of the Source Code, enables
+* supplementary functions or services in addition to those offered by
+* the Software.
+*
+* Rudder is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with Rudder. If not, see .
+
+*
+*************************************************************************************
+*/
+
+package com.normation.rudder.web.services
+
+import net.liftweb.http.js.JsExp
+import net.liftweb.http.js.JsObj
+import net.liftweb.http.js.JE.JsArray
+import com.normation.rudder.domain.reports.ComplianceLevel
+import net.liftweb.common.Loggable
+
+
+/*
+ * That class represent a Line in a DataTable.
+ */
+trait JsTableLine extends Loggable {
+
+ def json : JsObj
+
+ // this is needed because DataTable doesn't escape HTML element when using table.rows.add
+ def escapeHTML(s: String): JsExp = JsExp.strToJsExp(xml.Utility.escape(s))
+
+ import com.normation.rudder.domain.reports.ComplianceLevelSerialisation._
+ def jsCompliance(compliance: ComplianceLevel) = compliance.toJsArray()
+
+}
+
+/*
+ * That class a set of Data to use in datatable
+ * It should be used as data parameter when creating datatable in javascript
+ */
+case class JsTableData[T <: JsTableLine] ( lines : List[T] ) {
+
+ def json = JsArray(lines.map(_.json))
+
+}
+
diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/RudderUserDetails.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/RudderUserDetails.scala
new file mode 100644
index 00000000000..7af61571633
--- /dev/null
+++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/RudderUserDetails.scala
@@ -0,0 +1,64 @@
+package com.normation.rudder.web.services
+
+import java.util.Collection
+import com.normation.rudder.{AuthorizationType, Rights, Role, RudderAccount}
+import com.normation.rudder.api.ApiAuthorization
+import com.normation.utils.HashcodeCaching
+import org.springframework.security.core.GrantedAuthority
+import org.springframework.security.core.userdetails.UserDetails
+import scala.collection.JavaConverters.asJavaCollectionConverter
+
+/**
+ * We don't use at all Spring Authority to implements
+ * our authorizations.
+ * That because we want something more typed than String for
+ * authority, and as a bonus, that allows to be able to switch
+ * from Spring more easily
+ *
+ * So we have one Authority type known by Spring Security for
+ * authenticated user: ROLE_USER
+ * And one other for API accounts: ROLE_REMOTE
+ */
+sealed trait RudderAuthType {
+ def grantedAuthorities: Collection[GrantedAuthority]
+}
+
+
+final object RudderAuthType {
+ // build a GrantedAuthority from the string
+ private def buildAuthority(s: String): Collection[GrantedAuthority] = {
+ Seq(new GrantedAuthority { override def getAuthority: String = s }).asJavaCollection
+ }
+
+ final case object User extends RudderAuthType {
+ override val grantedAuthorities = buildAuthority("ROLE_USER")
+ }
+ final case object Api extends RudderAuthType {
+ override val grantedAuthorities = buildAuthority("ROLE_REMOTE")
+
+ val apiRudderRights = new Rights(AuthorizationType.NoRights)
+ val apiRudderRole: Set[Role] = Set(Role.NoRights)
+ }
+}
+
+/**
+ * Our simple model for for user authentication and authorizations.
+ * Note that authorizations are not managed by spring, but by the
+ * 'authz' token of RudderUserDetail.
+ */
+final case class RudderUserDetail(
+ account : RudderAccount
+ , roles : Set[Role]
+ , apiAuthz: ApiAuthorization
+) extends UserDetails with HashcodeCaching {
+ // merge roles rights
+ val authz = new Rights(roles.flatMap(_.rights.authorizationTypes).toSeq:_*)
+ override val (getUsername, getPassword, getAuthorities) = account match {
+ case RudderAccount.User(login, password) => (login , password , RudderAuthType.User.grantedAuthorities)
+ case RudderAccount.Api(api) => (api.name.value, api.token.value, RudderAuthType.Api.grantedAuthorities)
+ }
+ override val isAccountNonExpired = true
+ override val isAccountNonLocked = true
+ override val isCredentialsNonExpired = true
+ override val isEnabled = true
+}
\ No newline at end of file
diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/Boot.scala b/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/Boot.scala
index 669d74ede5f..1e2660b2e43 100644
--- a/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/Boot.scala
+++ b/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/Boot.scala
@@ -282,6 +282,7 @@ class Boot extends Loggable {
LiftRules.statelessDispatch.append(RudderConfig.restQuicksearch)
LiftRules.statelessDispatch.append(RudderConfig.restCompletion)
LiftRules.statelessDispatch.append(RudderConfig.sharedFileApi)
+ LiftRules.statelessDispatch.append(RudderConfig.eventLogApi)
// REST API (all public/internal API)
// we need to add "info" API here to have all used API (even plugins)
diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/RudderConfig.scala b/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/RudderConfig.scala
index b92eeb2909a..f21ecb934da 100644
--- a/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/RudderConfig.scala
+++ b/webapp/sources/rudder/rudder-web/src/main/scala/bootstrap/liftweb/RudderConfig.scala
@@ -806,8 +806,10 @@ object RudderConfig extends Loggable {
api
}
+
// Internal APIs
val sharedFileApi = new SharedFilesAPI(restExtractorService,RUDDER_DIR_SHARED_FILES_FOLDER)
+ val eventLogApi= new EventLogAPI(eventLogRepository, restExtractorService, eventListDisplayerImpl)
lazy val asyncWorkflowInfo = new AsyncWorkflowInfo
lazy val configService: ReadConfigService with UpdateConfigService = {
diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/EventListDisplayer.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/EventListDisplayer.scala
index fb67f5e6ce2..4fe1e4406d5 100644
--- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/EventListDisplayer.scala
+++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/services/EventListDisplayer.scala
@@ -36,51 +36,27 @@
*/
package com.normation.rudder.web.services
-import scala.xml._
+import com.normation.box._
import com.normation.eventlog.EventLog
-import com.normation.rudder.domain.eventlog._
-import net.liftweb.common._
-import net.liftweb.util.Helpers._
-import com.normation.rudder.domain.policies._
-import com.normation.rudder.domain.nodes._
+import com.normation.rudder.repository._
+import com.normation.rudder.rule.category.RoRuleCategoryRepository
import com.normation.rudder.services.eventlog.EventLogDetailsService
+import com.normation.rudder.services.modification.ModificationService
+import com.normation.rudder.services.nodes.NodeInfoService
+import com.normation.rudder.services.user.PersonIdentService
import com.normation.rudder.web.components.DateFormaterService
-import com.normation.cfclerk.domain.TechniqueName
-import com.normation.inventory.domain.NodeId
-import com.normation.rudder.domain.queries.Query
-import net.liftweb.http.js._
+import com.normation.rudder.web.model.LinkUtil
+import net.liftweb.common._
+import net.liftweb.http.{S, SHtml}
import net.liftweb.http.js.JE._
import net.liftweb.http.js.JsCmds._
-import net.liftweb.http.SHtml
-import net.liftweb.http.S
-import com.normation.rudder.batch.SuccessStatus
-import com.normation.rudder.batch.ErrorStatus
-import com.normation.rudder.repository._
-import com.normation.rudder.services.nodes.NodeInfoService
-import com.normation.rudder.services.modification.ModificationService
-import com.normation.rudder.services.user.PersonIdentService
-import com.normation.rudder.web.model.CurrentUser
-import org.eclipse.jgit.lib.PersonIdent
-import org.joda.time.DateTime
-import com.normation.rudder.services.eventlog.RollbackInfo
-import com.normation.rudder.domain.workflows.ChangeRequestId
-
-import scala.util.Try
-import scala.util.Success
-import scala.util.{Failure => Catch}
-import com.normation.rudder.domain.eventlog.WorkflowStepChanged
-import com.normation.rudder.domain.workflows.WorkflowStepChange
-import com.normation.rudder.domain.parameters._
-import com.normation.rudder.api._
+import net.liftweb.http.js._
import net.liftweb.json._
-import com.normation.rudder.reports.HeartbeatConfiguration
-import com.normation.rudder.reports.AgentRunInterval
-import com.normation.rudder.rule.category.RuleCategory
-import com.normation.rudder.rule.category.RoRuleCategoryRepository
+import net.liftweb.util.Helpers._
+import org.joda.time.DateTime
import org.joda.time.format.DateTimeFormat
-import com.normation.rudder.web.model.LinkUtil
-import org.joda.time.format.ISODateTimeFormat
-import com.normation.box._
+
+import scala.xml._
/**
* Used to display the event list, in the pending modification (AsyncDeployment),
@@ -112,9 +88,7 @@ class EventListDisplayer(
val el = events.map(EventLogLine(_)).toList.sortWith(_.event.creationDate.getMillis > _.event.creationDate.getMillis)
if(el.size > limit) JsTableData(el.take(limit)) else JsTableData(el)
}
- val limitMsg = if(events.size > limit)
{limit} results taken over {events.size} results
else
- val call = JsRaw(s"refreshTable('${gridName}',${lines.json.toJsCmd})") & SetHtml("limitEventDisplayer", limitMsg)
- call
+ JsRaw(s"refreshTable('${gridName}',${lines.json.toJsCmd})")
case eb : EmptyBox =>
val fail = eb ?~! "Could not get latest event logs"
logger.error(fail.messageChain)
@@ -127,7 +101,6 @@ class EventListDisplayer(
displayEvents(refreshEvents())
}
-
def getEventsInterval(jsonInterval: String): JsCmd = {
import java.sql.Timestamp
val format = DateTimeFormat.forPattern("YYYY-MM-dd HH:mm:ss")
@@ -178,7 +151,6 @@ class EventListDisplayer(
createEventLogTable('${gridName}',[], '${S.contextPath}', refreshEventLogs, pickEventLogsInInterval)
refreshEventLogs();
""")))
-
}
/*
@@ -187,1516 +159,18 @@ class EventListDisplayer(
* , "date": date the event log was produced [Date/String]
* , "actor": Name of the actor making the event [String]
* , "type" : Type of the event log [String]
- * , "description" : Description of the event [String]
- * , "details" : function/ajax call, setting the details content, takes the id of the element to set [Function(String)]
* , "hasDetails" : do our event needs to display details (do we need to be able to open the row [Boolean]
* }
*/
case class EventLogLine(event : EventLog) extends JsTableLine {
- val detailsCallback = {
- AnonFunc("details",SHtml.ajaxCall(JsVar("details"), {(abc) =>
- val crId = event.id.flatMap(repos.getEventLogWithChangeRequest(_).toBox match {
- case Full(Some((_,crId))) => crId
- case _ => None
- })
- SetHtml(abc,displayDetails(event,crId))
- }
- )
- )
- }
val json = {
JsObj(
"id" -> (event.id.map(_.toString).getOrElse("Unknown"): String)
, "date" -> DateFormaterService.getFormatedDate(event.creationDate)
, "actor" -> event.principal.name
, "type" -> S.?("rudder.log.eventType.names." + event.eventType.serialize)
- , "description" -> displayDescription(event).toString
- , "details" -> detailsCallback
, "hasDetails" -> boolToJsExp(event.details != )
)
}
}
-
- //////////////////// Display description/details of ////////////////////
-
- //convention: "X" means "ignore"
-
- def displayDescription(event:EventLog) = {
- import linkUtil._
- def crDesc(x:EventLog, actionName: NodeSeq) = {
- val id = (x.details \ "rule" \ "id").text
- val name = (x.details \ "rule" \ "displayName").text
- Text("Rule ") ++ {
- if(id.size < 1) Text(name)
- else {name} ++ actionName
- }
- }
-
- def piDesc(x:EventLog, actionName: NodeSeq) = {
- val id = (x.details \ "directive" \ "id").text
- val name = (x.details \ "directive" \ "displayName").text
- Text("Directive ") ++ {
- if(id.size < 1) Text(name)
- else {name} ++ actionName
- }
- }
-
- def groupDesc(x:EventLog, actionName: NodeSeq) = {
- val id = (x.details \ "nodeGroup" \ "id").text
- val name = (x.details \ "nodeGroup" \ "displayName").text
- Text("Group ") ++ {
- if(id.size < 1) Text(name)
- else {name} ++ actionName
- }
- }
-
- def nodeDesc(x:EventLog, actionName: NodeSeq) = {
- val id = (x.details \\ "node" \ "id").text
- val name = (x.details \\ "node" \ "hostname").text
- Text("Node ") ++ {
- if ((id.size < 1)||(actionName==Text(" deleted"))) Text(name)
- else {name} ++ actionName
- }
- }
-
- def techniqueDesc(x:EventLog, actionName: NodeSeq) = {
- val name = (x.details \ "activeTechnique" \ "techniqueName").text
- Text("Technique %s".format(name)) ++ actionName
- }
-
- def globalParamDesc(x:EventLog, actionName: NodeSeq) = {
- val name = (x.details \ "globalParameter" \ "name").text
- Text(s"Global Parameter ${name} ${actionName}")
- }
-
- def changeRequestDesc(x:EventLog, actionName: NodeSeq) = {
- val name = (x.details \ "changeRequest" \ "name").text
- val idNode = (x.details \ "changeRequest" \ "id").text.trim
- val xml = Try(idNode.toInt) match {
- case Success(id) =>
- if (actionName==Text(" deleted"))
- Text(name)
- else
- {name}
-
- case Catch(e) =>
- logger.error(s"could not translate ${idNode} to a correct chage request identifier: ${e.getMessage()}")
- Text(name)
- }
- Text("Change request ") ++ xml ++ actionName
- }
-
- def workflowStepDesc(x:EventLog) = {
- logDetailsService.getWorkflotStepChange(x.details) match {
- case Full(WorkflowStepChange(crId,from,to)) =>
- Text("Change request #") ++
- {crId} ++
- Text(s" status modified from ${from} to ${to}")
-
- case eb:EmptyBox => val fail = eb ?~! "could not display workflow step event log"
- logger.error(fail.msg)
- Text("Change request status modified")
- }
- }
-
- def apiAccountDesc(x:EventLog, actionName: NodeSeq) = {
- val name = (x.details \ "apiAccount" \ "name").text
- Text(s"API Account ${name} ${actionName}")
- }
-
- event match {
- case x:ActivateRedButton => Text("Stop Rudder agents on all nodes")
- case x:ReleaseRedButton => Text("Start again Rudder agents on all nodes")
- case x:AcceptNodeEventLog => nodeDesc(x, Text(" accepted"))
- case x:RefuseNodeEventLog => nodeDesc(x, Text(" refused"))
- case x:DeleteNodeEventLog => nodeDesc(x, Text(" deleted"))
- case x:LoginEventLog => Text("User '%s' login".format(x.principal.name))
- case x:LogoutEventLog => Text("User '%s' logout".format(x.principal.name))
- case x:BadCredentialsEventLog => Text("User '%s' failed to login: bad credentials".format(x.principal.name))
- case x:AutomaticStartDeployement => Text("Automatically update policies")
- case x:ManualStartDeployement => Text("Manually update policies")
- case x:ApplicationStarted => Text("Rudder starts")
- case x:ModifyRule => crDesc(x,Text(" modified"))
- case x:DeleteRule => crDesc(x,Text(" deleted"))
- case x:AddRule => crDesc(x,Text(" added"))
- case x:ModifyDirective => piDesc(x,Text(" modified"))
- case x:DeleteDirective => piDesc(x,Text(" deleted"))
- case x:AddDirective => piDesc(x,Text(" added"))
- case x:ModifyNodeGroup => groupDesc(x,Text(" modified"))
- case x:DeleteNodeGroup => groupDesc(x,Text(" deleted"))
- case x:AddNodeGroup => groupDesc(x,Text(" added"))
- case x:ClearCacheEventLog => Text("Clear caches")
- case x:UpdatePolicyServer => Text("Change Policy Server authorized network")
- case x:ReloadTechniqueLibrary => Text("Technique library updated")
- case x:ModifyTechnique => techniqueDesc(x, Text(" modified"))
- case x:DeleteTechnique => techniqueDesc(x, Text(" deleted"))
- case x:SuccessfulDeployment => Text("Successful policy update")
- case x:FailedDeployment => Text("Failed policy update")
- case x:ExportGroupsArchive => Text("New groups archive")
- case x:ExportTechniqueLibraryArchive => Text("New Directive library archive")
- case x:ExportRulesArchive => Text("New Rules archives")
- case x:ExportParametersArchive => Text("New Parameters archives")
- case x:ExportFullArchive => Text("New full archive")
- case x:ImportGroupsArchive => Text("Restoring group archive")
- case x:ImportTechniqueLibraryArchive => Text("Restoring Directive library archive")
- case x:ImportRulesArchive => Text("Restoring Rules archive")
- case x:ImportParametersArchive => Text("Restoring Parameters archive")
- case x:ImportFullArchive => Text("Restoring full archive")
- case _:AddChangeRequest => changeRequestDesc(event,Text(" created"))
- case _:DeleteChangeRequest => changeRequestDesc(event,Text(" deleted"))
- case _:ModifyChangeRequest => changeRequestDesc(event,Text(" modified"))
- case _:Rollback => Text("Restore a previous state of configuration policy")
- case x:WorkflowStepChanged => workflowStepDesc(x)
- case x:AddGlobalParameter => globalParamDesc(x, Text(" added"))
- case x:ModifyGlobalParameter => globalParamDesc(x, Text(" modified"))
- case x:DeleteGlobalParameter => globalParamDesc(x, Text(" deleted"))
- case x:CreateAPIAccountEventLog => apiAccountDesc(x, Text(" added"))
- case x:ModifyAPIAccountEventLog => apiAccountDesc(x, Text(" modified"))
- case x:DeleteAPIAccountEventLog => apiAccountDesc(x, Text(" deleted"))
- case x:ModifyGlobalProperty => Text(s"Modify '${x.propertyName}' global property")
- case x:ModifyNode => nodeDesc(x, Text(" modified"))
- case _ => Text("Unknow event type")
- }
- }
-
- def displayDetails(event:EventLog,changeRequestId:Option[ChangeRequestId]): NodeSeq = {
-
- val groupLib = nodeGroupRepository.getFullGroupLibrary().toBox.openOr(return
System error when trying to get the group library
)
- val rootRuleCategory = ruleCatRepository.getRootCategory.toBox.openOr(return
System error when trying to get the rule categories
)
-
- val generatedByChangeRequest =
- changeRequestId match {
- case None => NodeSeq.Empty
- case Some(id) =>
This change was introduced by change request {SHtml.a(() => S.redirectTo(linkUtil.changeRequestLink(id)),Text(s"#${id}"))}
- }
- def xmlParameters(eventId: Option[Int]) = {
- eventId match {
- case None => NodeSeq.Empty
- case Some(id) =>
-
-
{ event.details.map { n =>xmlPretty.format(n) + "\n"} }
Details for that node were not in a recognized format.
- Raw data are displayed next:
- { xmlParameters(event.id) }
-
-
- }
-
{
- (event match {
- /*
- * bug in scalac : https://issues.scala-lang.org/browse/SI-6897
- * A workaround is to type with a Nodeseq
- */
- case add:AddRule =>
- "*" #> { val xml : NodeSeq = logDetailsService.getRuleAddDetails(add.details) match {
- case Full(addDiff) =>
-
- case e:EmptyBox => errorMessage(e)
- }
- xml
- }
-
- case del:DeleteRule =>
- "*" #> { val xml : NodeSeq = logDetailsService.getRuleDeleteDetails(del.details) match {
- case Full(delDiff) =>
-
- case e:EmptyBox => errorMessage(e)
- }
- xml }
-
- case mod:ModifyRule =>
- "*" #> { val xml : NodeSeq = logDetailsService.getRuleModifyDetails(mod.details) match {
- case Full(modDiff) =>
-
- case e:EmptyBox => errorMessage(e)
- }
- xml }
-
- case x:AddDirective =>
- "*" #> { val xml : NodeSeq = logDetailsService.getDirectiveAddDetails(x.details) match {
- case Full((diff,sectionVal)) =>
-
- case e:EmptyBox => errorMessage(e)
- }
- xml }
-
- ///////// Node Group /////////
-
- case x:ModifyNodeGroup =>
- "*" #> { val xml : NodeSeq = logDetailsService.getNodeGroupModifyDetails(x.details) match {
- case Full(modDiff) =>
-
- case e:EmptyBox => errorMessage(e)
- }
- xml }
-
- case x:AddNodeGroup =>
- "*" #> { val xml : NodeSeq = logDetailsService.getNodeGroupAddDetails(x.details) match {
- case Full(diff) =>
-
- case e:EmptyBox => errorMessage(e)
- }
- xml }
-
- case x:DeleteNodeGroup =>
- "*" #> { val xml : NodeSeq = logDetailsService.getNodeGroupDeleteDetails(x.details) match {
- case Full(diff) =>
-
- case e:EmptyBox => errorMessage(e)
- }
- xml }
-
- ///////// Node Group /////////
-
- case x:AcceptNodeEventLog =>
- "*" #> { val xml : NodeSeq = logDetailsService.getAcceptNodeLogDetails(x.details) match {
- case Full(details) =>
-
- case e:EmptyBox => errorMessage(e)
- }
- xml }
-
- case x:RefuseNodeEventLog =>
- "*" #> { val xml : NodeSeq = logDetailsService.getRefuseNodeLogDetails(x.details) match {
- case Full(details) =>
-
- case e:EmptyBox => errorMessage(e)
- }
- xml }
-
- case x:DeleteNodeEventLog =>
- "*" #> { val xml : NodeSeq = logDetailsService.getDeleteNodeLogDetails(x.details) match {
- case Full(details) =>
-
End Time: {DateFormaterService.getFormatedDate(ended)}
-
- { reasonHtml }
- { xmlParameters(event.id) }
-
- case Full(_) => errorMessage(Failure("Inconsistant policy update status"))
- case e:EmptyBox => errorMessage(e)
- }
- xml }
-
- case x:FailedDeployment =>
- "*" #> { val xml : NodeSeq = logDetailsService.getDeploymentStatusDetails(x.details) match {
- case Full(ErrorStatus(id,started,ended,failure)) =>
-
- {
- mapComplexDiff(modDiff.modAgentRun){ (optAr:Option[AgentRunInterval]) =>
- optAr match {
- case None => No value
- case Some(ar) => agentRunDetails(ar)
- }
- }
- }
- {
- mapComplexDiff(modDiff.modHeartbeat){ (optHb:Option[HeartbeatConfiguration]) =>
- optHb match {
- case None => No value
- case Some(hb) => heartbeatDetails(hb)
- }
- }
- }
- {
- mapComplexDiff(modDiff.modProperties){ (props:Seq[NodeProperty]) =>
- nodePropertiesDetails(props)
- }
- }
- {
- mapComplexDiff(modDiff.modPolicyMode){ (optMode:Option[PolicyMode]) =>
- optMode match {
- case None => Use global policy mode
- case Some(mode) => {mode.name}
- }
- }
- }
- {
- mapComplexDiff(modDiff.modKeyValue) { keyValue => {keyValue.key} }
- }
- {
- mapComplexDiff(modDiff.modKeyStatus) { keyStatus => {keyStatus.value} }
- }
- { reasonHtml }
- { xmlParameters(event.id) }
-
- case e:EmptyBox => logger.warn(e)
- errorMessage(e)
- }
- }
-
- // other case: do not display details at all
- case _ => "*" #> ""
-
- })(event.details)}