diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/apidata/JsonQueryObjects.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/apidata/JsonQueryObjects.scala index 976a761cebd..46a604b8ddd 100644 --- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/apidata/JsonQueryObjects.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/apidata/JsonQueryObjects.scala @@ -82,6 +82,7 @@ import io.scalaland.chimney.Transformer import io.scalaland.chimney.dsl.* import net.liftweb.common.* import net.liftweb.http.Req +import zio.Chunk import zio.NonEmptyChunk import zio.json.* @@ -115,6 +116,9 @@ object JsonQueryObjects { final case class JQClasses( classes: Option[List[String]] ) + final case class JQNodeIdChunk( + nodeId: Option[Chunk[NodeId]] + ) final case class JQNodeIdStatus( nodeId: List[NodeId], status: JQNodeStatusAction @@ -141,6 +145,10 @@ object JsonQueryObjects { ) } + final case class JQNodeInherited( + inherited: Option[Boolean] + ) + final case class JQNodePropertyInfo( inherited: Boolean, value: String @@ -166,6 +174,10 @@ object JsonQueryObjects { } } + final case class JQIncludeSystem( + includeSystem: Option[Boolean] + ) + final case class JQDirectiveSectionVar( name: String, value: String @@ -443,6 +455,7 @@ trait RudderJsonDecoders { implicit val classesDecoder: JsonDecoder[JQClasses] = DeriveJsonDecoder.gen[JQClasses].orElse((_, _) => JQClasses(None)) implicit val nodeIdDecoder: JsonDecoder[NodeId] = JsonDecoder[String].map(NodeId.apply) + implicit val nodeIdChunkDecoder: JsonDecoder[JQNodeIdChunk] = DeriveJsonDecoder.gen[JQNodeIdChunk] implicit val nodeStatusActionDecoder: JsonDecoder[JQNodeStatusAction] = JsonDecoder[String].mapOrFail(JQNodeStatusAction.withNameInsensitiveEither(_).left.map(_.getMessage())) implicit val nodeIdStatusDecoder: JsonDecoder[JQNodeIdStatus] = DeriveJsonDecoder @@ -454,6 +467,7 @@ trait RudderJsonDecoders { Right(x) } }) + implicit val nodeInheritedDecoder: JsonDecoder[JQNodeInherited] = DeriveJsonDecoder.gen[JQNodeInherited] implicit val nodestatusDecoder: JsonDecoder[JQNodeStatus] = DeriveJsonDecoder.gen[JQNodeStatus] implicit val nodePropertyInfoDecoder: JsonDecoder[JQNodePropertyInfo] = DeriveJsonDecoder.gen[JQNodePropertyInfo] implicit val nodeIdsSoftwareProperties: JsonDecoder[JQNodeIdsSoftwareProperties] = @@ -517,6 +531,8 @@ trait RudderJsonDecoders { implicit val ruleCategoryDecoder: JsonDecoder[JQRuleCategory] = DeriveJsonDecoder.gen + implicit val includeSystemDecoder: JsonDecoder[JQIncludeSystem] = DeriveJsonDecoder.gen + // RestDirective - lazy because section/sectionVar/directive are mutually recursive implicit lazy val sectionDecoder: JsonDecoder[JQDirectiveSection] = DeriveJsonDecoder.gen implicit lazy val variableDecoder: JsonDecoder[JQDirectiveSectionVar] = DeriveJsonDecoder.gen @@ -664,6 +680,14 @@ class ZioJsonExtractor(queryParser: CmdbQueryParser with JsonQueryLexer) { } } + def extractNodeIdChunk(req: Req): PureResult[Option[Chunk[NodeId]]] = { + parseJson[JQNodeIdChunk](req).map(_.nodeId) + } + + def extractNodeInherited(req: Req): PureResult[Option[Boolean]] = { + parseJson[JQNodeInherited](req).map(_.inherited) + } + def extractNodeIdStatus(req: Req): PureResult[JQNodeIdStatus] = { if (req.json_?) { parseJson[JQNodeIdStatus](req) @@ -696,6 +720,14 @@ class ZioJsonExtractor(queryParser: CmdbQueryParser with JsonQueryLexer) { } } + def extractIncludeSystem(req: Req): PureResult[Option[Boolean]] = { + if (req.json_?) { + parseJson[JQIncludeSystem](req).map(_.includeSystem) + } else { + extractIncludeSystemFromParams(req.params) + } + } + def extractIdsFromParams(params: Map[String, List[String]]): PureResult[Option[List[String]]] = { params.parseString("ids", s => Right(s.split(",").map(_.trim).toList)) } @@ -900,4 +932,7 @@ class ZioJsonExtractor(queryParser: CmdbQueryParser with JsonQueryLexer) { } } + def extractIncludeSystemFromParams(params: Map[String, List[String]]): PureResult[Option[Boolean]] = { + params.parse("includeSystem", JsonDecoder[Boolean]) + } } diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/UserPropertyService.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/config/UserPropertyService.scala similarity index 69% rename from webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/UserPropertyService.scala rename to webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/config/UserPropertyService.scala index cb4bc5cb694..65d5e688ffb 100644 --- a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/web/services/UserPropertyService.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/config/UserPropertyService.scala @@ -34,14 +34,16 @@ * ************************************************************************************* */ -package com.normation.rudder.web.services +package com.normation.rudder.config import com.normation.box.* import com.normation.errors.* -import zio.syntax.* -object ReasonBehavior extends Enumeration { - type ReasonBehavior = Value - val Disabled, Mandatory, Optionnal = Value +sealed trait ReasonBehavior + +object ReasonBehavior { + case object Disabled extends ReasonBehavior + case object Mandatory extends ReasonBehavior + case object Optional extends ReasonBehavior } /** @@ -54,20 +56,11 @@ trait UserPropertyService { * @return the strategy */ // def reasonsFieldEnabled() : Boolean - def reasonsFieldBehavior: ReasonBehavior.ReasonBehavior + def reasonsFieldBehavior: ReasonBehavior def reasonsFieldExplanation: String } -class UserPropertyServiceImpl(val opt: ReasonsMessageInfo) extends UserPropertyService { - - private val impl = - new StatelessUserPropertyService(() => opt.enabled.succeed, () => opt.mandatory.succeed, () => opt.explanation.succeed) - - override val reasonsFieldBehavior = impl.reasonsFieldBehavior - override val reasonsFieldExplanation: String = impl.reasonsFieldExplanation -} - /** * Reloadable service */ @@ -76,17 +69,16 @@ class StatelessUserPropertyService( getMandatory: () => IOResult[Boolean], getExplanation: () => IOResult[String] ) extends UserPropertyService { + import ReasonBehavior.* // TODO: handle errors here! - override def reasonsFieldBehavior: ReasonBehavior.ReasonBehavior = { + override def reasonsFieldBehavior: ReasonBehavior = { (getEnable().toBox.getOrElse(true), getMandatory().toBox.getOrElse(true)) match { - case (true, true) => ReasonBehavior.Mandatory - case (true, false) => ReasonBehavior.Optionnal - case (false, _) => ReasonBehavior.Disabled + case (true, true) => Mandatory + case (true, false) => Optional + case (false, _) => Disabled } } - override def reasonsFieldExplanation: String = getExplanation().toBox.getOrElse("") + override def reasonsFieldExplanation: String = getExplanation().toBox.getOrElse("") } - -class ReasonsMessageInfo(val enabled: Boolean, val mandatory: Boolean, val explanation: String) diff --git a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/json/JsonExctractorUtils.scala b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/json/JsonExctractorUtils.scala index c5b2444a0b9..73d2570afef 100644 --- a/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/json/JsonExctractorUtils.scala +++ b/webapp/sources/rudder/rudder-core/src/main/scala/com/normation/rudder/repository/json/JsonExctractorUtils.scala @@ -88,59 +88,44 @@ trait JsonExtractorUtils[A[_]] { } } + /* + * Still used in apiaccount/eventlog/group API, tags + */ def extractJsonString[T](json: JValue, key: String, convertTo: String => Box[T] = boxedIdentity[String]): Box[A[T]] = { extractJson(json, key, convertTo, { case JString(value) => value }) } - def extractJsonStringMultipleKeys[T]( - json: JValue, - keys: List[String], - convertTo: String => Box[T] = boxedIdentity[String] - ): Box[A[T]] = { - extractJson(json, keys, convertTo, { case JString(value) => value }) - } + /* + * Still used in apiaccount API + */ def extractJsonBoolean[T](json: JValue, key: String, convertTo: Boolean => Box[T] = boxedIdentity[Boolean]): Box[A[T]] = { extractJson(json, key, convertTo, { case JBool(value) => value }) } + /* + * Still used in eventlog API + */ def extractJsonInt(json: JValue, key: String): Box[A[Int]] = { extractJsonBigInt(json, key, i => Full(i.toInt)) } + /* + * Still used in eventlog API + */ def extractJsonBigInt[T](json: JValue, key: String, convertTo: BigInt => Box[T] = boxedIdentity[BigInt]): Box[A[T]] = { extractJson(json, key, convertTo, { case JInt(value) => value }) } - def extractJsonObj[T](json: JValue, key: String, jsonValueFun: JObject => Box[T]): Box[A[T]] = { + /* + * Still used in eventlog API + */ + def extractJsonObj[T](json: JValue, key: String, jsonValueFun: JObject => Box[T]): Box[A[T]] = { extractJson(json, key, jsonValueFun, { case obj: JObject => obj }) } - def extractJsonObjMultipleKeys[T](json: JValue, keys: List[String], jsonValueFun: JObject => Box[T]): Box[A[T]] = { - extractJson(json, keys, jsonValueFun, { case obj: JObject => obj }) - } - - def extractJsonListString[T]( - json: JValue, - key: String, - convertTo: List[String] => Box[T] = boxedIdentity[List[String]] - ): Box[A[T]] = { - json \ key match { - case JArray(values) => - (for { - strings <- traverse(values) { - _ match { - case JString(s) => Full(s) - case x => Failure(s"Error extracting a string from json: '${x}'") - } - } - converted <- convertTo(strings.toList) - } yield { - converted - }).map(monad.pure(_)) - case JNothing => emptyValue(key) ?~! s"Array of string is empty" - case _ => Failure(s"Not a good value for parameter ${key}") - } - } + /* + * Still used in tags, eventlog/apiaccount API + */ def extractJsonArray[T](json: JValue, key: String)(convertTo: JValue => Box[T]): Box[A[List[T]]] = { val trueJson = if (key.isEmpty) json else json \ key @@ -155,6 +140,9 @@ trait JsonExtractorUtils[A[_]] { case _ => Failure(s"Invalid json to extract a json array, current value is: ${compactRender(json)}") } } + /* + * Still used in tags, eventlog/apiaccount API + */ def extractJsonArray[T](json: JValue, keys: List[String])(convertTo: JValue => Box[T]): Box[A[List[T]]] = { keys.map(k => extractJsonArray(json, k)(convertTo)).reduce[Box[A[List[T]]]] { case (Full(res), _) => Full(res) diff --git a/webapp/sources/rudder/rudder-core/src/test/scala/com/normation/rudder/services/nodes/TestMergeGroupProperties.scala b/webapp/sources/rudder/rudder-core/src/test/scala/com/normation/rudder/services/nodes/TestMergeGroupProperties.scala index 082e7cc0a31..107110eb50d 100644 --- a/webapp/sources/rudder/rudder-core/src/test/scala/com/normation/rudder/services/nodes/TestMergeGroupProperties.scala +++ b/webapp/sources/rudder/rudder-core/src/test/scala/com/normation/rudder/services/nodes/TestMergeGroupProperties.scala @@ -78,7 +78,6 @@ import scala.reflect.ClassTag import zio.Chunk import zio.NonEmptyChunk - @RunWith(classOf[JUnitRunner]) class TestMergeGroupProperties extends Specification { diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/RestExtractorService.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/RestExtractorService.scala index b6c28bf322e..9c760146ac3 100644 --- a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/RestExtractorService.scala +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/RestExtractorService.scala @@ -37,15 +37,8 @@ package com.normation.rudder.rest -import com.normation.GitVersion import com.normation.box.* -import com.normation.cfclerk.domain.Technique -import com.normation.cfclerk.domain.TechniqueId -import com.normation.cfclerk.domain.TechniqueName -import com.normation.cfclerk.domain.TechniqueVersion import com.normation.cfclerk.services.TechniqueRepository -import com.normation.errors.* -import com.normation.inventory.domain.NodeId import com.normation.rudder.api.AclPath import com.normation.rudder.api.ApiAccountId import com.normation.rudder.api.ApiAccountName @@ -53,44 +46,22 @@ import com.normation.rudder.api.ApiAclElement import com.normation.rudder.api.ApiAuthorization as ApiAuthz import com.normation.rudder.api.ApiAuthorizationKind import com.normation.rudder.api.HttpAction +import com.normation.rudder.config.UserPropertyService import com.normation.rudder.domain.nodes.NodeGroupCategoryId -import com.normation.rudder.domain.policies.* -import com.normation.rudder.domain.policies.PolicyMode -import com.normation.rudder.domain.properties.GenericProperty -import com.normation.rudder.domain.properties.GroupProperty -import com.normation.rudder.domain.properties.InheritMode -import com.normation.rudder.domain.properties.NodeProperty -import com.normation.rudder.domain.properties.PropertyProvider -import com.normation.rudder.domain.queries.Query -import com.normation.rudder.domain.queries.QueryReturnType -import com.normation.rudder.domain.queries.QueryReturnType.NodeReturnType import com.normation.rudder.domain.reports.CompliancePrecision -import com.normation.rudder.domain.workflows.* import com.normation.rudder.facts.nodes.NodeSecurityContext import com.normation.rudder.ncf.ParameterType.ParameterTypeService import com.normation.rudder.repository.* import com.normation.rudder.repository.json.DataExtractor.CompleteJson import com.normation.rudder.rest.data.* -import com.normation.rudder.rule.category.RuleCategoryId -import com.normation.rudder.services.policies.PropertyParser import com.normation.rudder.services.queries.CmdbQueryParser -import com.normation.rudder.services.queries.CmdbQueryParser.* import com.normation.rudder.services.queries.JsonQueryLexer -import com.normation.rudder.services.queries.StringCriterionLine -import com.normation.rudder.services.queries.StringQuery import com.normation.rudder.services.workflows.WorkflowLevelService -import com.normation.rudder.web.services.ReasonBehavior -import com.normation.rudder.web.services.UserPropertyService -import com.normation.utils.Control -import com.normation.utils.Control.* import com.normation.utils.DateFormaterService import com.normation.utils.StringUuidGenerator import net.liftweb.common.* -import net.liftweb.common.Box.option2Box import net.liftweb.http.Req import net.liftweb.json.* -import net.liftweb.json.JObject -import net.liftweb.json.JsonDSL.* final case class RestExtractorService( readRule: RoRuleRepository, @@ -119,60 +90,6 @@ final case class RestExtractorService( } } - private def extractList[T](params: Map[String, List[String]], key: String)( - to: (List[String]) => Box[T] - ): Box[Option[T]] = { - params.get(key) match { - case None => Full(None) - case Some(list) => to(list).map(Some(_)) - } - } - - /* - * Convert value functions - */ - private def toBoolean(value: String): Box[Boolean] = { - value match { - case "true" => Full(true) - case "false" => Full(false) - case _ => Failure(s"value for boolean should be true or false instead of ${value}") - } - } - - private[this] def toInt(value: String): Box[Int] = { - try { - Full(value.toInt) - } catch { - case _: java.lang.NumberFormatException => Failure(s"value for integer should be an integer instead of ${value}") - } - } - - private def toQuery(value: String): Box[Query] = { - queryParser(value) - } - - private def toQueryCriterion(value: String): Box[List[StringCriterionLine]] = { - JsonParser.parseOpt(value) match { - case None => Failure("Could not parse 'select' cause in api query ") - case Some(value) => - // Need to encapsulate this in a json Object, so it parse correctly - val json = (CRITERIA -> value) - queryParser.parseCriterionLine(json) - } - } - - private def toQueryReturnType(value: String): Box[QueryReturnType] = { - QueryReturnType(value).toBox - } - - private def toQueryComposition(value: String): Box[Option[String]] = { - Full(Some(value)) - } - - private def toQueryTransform(value: String): Box[Option[String]] = { - Full(if (value.isEmpty) None else Some(value)) - } - private def toMinimalSizeString(minimalSize: Int)(value: String): Box[String] = { if (value.size >= minimalSize) { Full(value) @@ -181,39 +98,9 @@ final case class RestExtractorService( } } - private def toParameterName(value: String): Box[String] = { - toMinimalSizeString(1)(value) match { - case Full(value) => - if (GenericProperty.patternName.matcher(value).matches) - Full(value) - else Failure(s"Parameter Name should be respect the following regex : ${GenericProperty.patternName.pattern()}") - - case eb: EmptyBox => eb ?~! "Parameter Name should not be empty" - } - } - - private def toDirectiveParam(value: String): Box[Map[String, Seq[String]]] = { - parseSectionVal(parse(value)).map(SectionVal.toMapVariables(_)) - } - - private def extractJsonDirectiveParam(json: JValue): Box[Option[Map[String, Seq[String]]]] = { - json \ "parameters" match { - case JObject(Nil) | JNothing => Full(None) - case x @ JObject(_) => parseSectionVal(x).map(x => Some(SectionVal.toMapVariables(x))) - case _ => Failure(s"The value for parameter 'parameters' is malformed.") - } - } - private def toNodeGroupCategoryId(value: String): Box[NodeGroupCategoryId] = { Full(NodeGroupCategoryId(value)) } - private def toRuleCategoryId(value: String): Box[RuleCategoryId] = { - Full(RuleCategoryId(value)) - } - - private def toGroupCategoryId(value: String): Box[NodeGroupCategoryId] = { - Full(NodeGroupCategoryId(value)) - } private def toApiAccountId(value: String): Box[ApiAccountId] = { Full(ApiAccountId(value)) @@ -223,346 +110,9 @@ final case class RestExtractorService( Full(ApiAccountName(value)) } - private def toWorkflowStatus(value: String): Box[Seq[WorkflowNodeId]] = { - val possiblestates = workflowLevelService.getWorkflowService().stepsValue - value.toLowerCase match { - case "open" => Full(workflowLevelService.getWorkflowService().openSteps) - case "closed" => Full(workflowLevelService.getWorkflowService().closedSteps) - case "all" => Full(possiblestates) - case value => - possiblestates.find(_.value.equalsIgnoreCase(value)) match { - case Some(state) => Full(Seq(state)) - case None => Failure(s"'${value}' is not a possible state for change requests") - } - } - } - - private def toWorkflowTargetStatus(value: String): Box[WorkflowNodeId] = { - val possiblestates = workflowLevelService.getWorkflowService().stepsValue - possiblestates.find(_.value.equalsIgnoreCase(value)) match { - case Some(state) => Full(state) - case None => - Failure( - s"'${value}' is not a possible state for change requests, availabled values are: ${possiblestates.mkString("[ ", ", ", " ]")}" - ) - } - } - - /* - * Converting ruletarget. - * - * We support two use cases, in both json and parameters mode: - * - simple rule target name (list of string) => - * converted into a list of included target - * - full-fledge include/exclude - * converted into one composite target. - * - * For now, it is an error to give several include/exclude, - * as of Rudder 3.0, the UI does not know how to handle it. - * - * So in all case, the result is exactly ONE target. - */ - private def toRuleTarget(parameters: Map[String, List[String]], key: String): Box[Option[RuleTarget]] = { - parameters.get(key) match { - case Some(values) => - traverse(values)(value => RuleTarget.unser(value)).flatMap(mergeTarget) - case None => Full(None) - } - } - - def toRuleTarget(json: JValue, key: String): Box[Option[RuleTarget]] = { - for { - targets <- traverse((json \\ key).children) { child => - child match { - case JArray(values) => - traverse(values.children)(value => RuleTarget.unserJson(value)).flatMap(mergeTarget) - case x => RuleTarget.unserJson(x).map(Some(_)) - } - } - merged <- mergeTarget(targets.flatten) - } yield { - merged - } - } - - private def mergeTarget(seq: Seq[RuleTarget]): Box[Option[RuleTarget]] = { - seq match { - case Seq() => Full(None) - case head +: Seq() => Full(Some(head)) - case several => - // if we have only simple target, build a composite including - if (several.exists(x => x.isInstanceOf[CompositeRuleTarget])) { - Failure( - "Composing several composite target with include/exclude is not supported now, please only one composite target." - ) - } else { - Full(Some(RuleTarget.merge(several.toSet))) - } - } - } - - /* - * Convert List Functions - */ - private def convertListToDirectiveId(values: Seq[String]): Box[Set[DirectiveId]] = { - def toDirectiveId(value: String): Box[DirectiveId] = { - // TODO: parse value correctly - readDirective.getDirective(DirectiveUid(value)).notOptional(s"Directive '$value' not found").map(_.id).toBox - } - traverse(values)(toDirectiveId).map(_.toSet) - } - - private def convertListToNodeId(values: List[String]): Box[List[NodeId]] = { - Full(values.map(NodeId(_))) - } - - def parseSectionVal(root: JValue): Box[SectionVal] = { - - def parseSectionName(section: JValue): Box[String] = { - section \ "name" match { - case JString(sectionName) => Full(sectionName) - case a => - Failure(s"A section should be an object with a 'name' element, you got: ${net.liftweb.json.compactRender(section)}") - } - } - - def parseSection(section: JValue): Box[(String, SectionVal)] = { - section \ "section" match { - case values: JObject => recValParseSection(values) - case _ => - Failure( - s"A 'section' section should be an object containing a 'section' element (ie: { 'section' : ... }), you got: ${net.liftweb.json - .compactRender(section)}" - ) - } - - } - - def parseSections(section: JValue): Box[Map[String, Seq[SectionVal]]] = { - section \ "sections" match { - case JNothing => Full(Map()) - case JArray(sections) => (traverse(sections.toSeq)(parseSection)).map(_.groupMap(_._1)(_._2)) - case a => - Failure( - s"A 'sections' element in a section should either be empty (no child section), or an array of section element, you got: ${net.liftweb.json - .compactRender(a)}" - ) - } - } - - def parseVar(varSection: JValue): Box[(String, String)] = { - varSection \ "var" match { - case varObject: JObject => - (varObject \ "name", varObject \ "value") match { - case (JString(varName), JString(varValue)) => Full((varName, varValue)) - case _ => - Failure( - s"A var object should be an object containing a 'name' and a 'value' element (ie: { 'name' : ..., 'value' : ... }), you got: ${net.liftweb.json - .compactRender(varObject)}" - ) - } - case _ => - Failure( - s"A 'var' section should be an object containing a 'var' element, containing an object with a 'name' and 'value' element (ie: { 'var' : { 'name' : ..., 'value' : ... } }, you got: ${net.liftweb.json - .compactRender(varSection)}" - ) - } - } - def parseSectionVars(section: JValue): Box[Map[String, String]] = { - section \ "vars" match { - case JNothing => Full(Map()) - case JArray(vars) => (traverse(vars)(parseVar)).map(_.toMap) - case a => - Failure( - s"A 'vars' element in a section should either be empty (no variable), or an array of var sections, you got: ${net.liftweb.json - .compactRender(a)}" - ) - } - } - - def recValParseSection(section: JValue): Box[(String, SectionVal)] = { - for { - sectionName <- parseSectionName(section) - vars <- parseSectionVars(section) - sections <- parseSections(section) - } yield { - (sectionName, SectionVal(sections, vars)) - } - } - - for { - (_, sectionVal) <- parseSection(root) - } yield { - sectionVal - } - } - /* - * Data extraction functions + * Still used in group API */ - def extractPrettify(params: Map[String, List[String]]): Boolean = { - extractOneValue(params, "prettify")(toBoolean).map(_.getOrElse(false)).getOrElse(false) - } - - def extractReason(req: Req): Box[Option[String]] = { - import ReasonBehavior.* - userPropertyService.reasonsFieldBehavior match { - case Disabled => Full(None) - case mode => - val reason = extractString("reason")(req)(Full(_)) - (mode: @unchecked) match { - case Mandatory => - reason match { - case Full(None) => Failure("Reason field is mandatory and should be at least 5 characters long") - case Full(Some(v)) if v.size < 5 => Failure("Reason field should be at least 5 characters long") - case _ => reason - } - case Optionnal => reason - } - } - } - - def extractChangeRequestName(req: Req): Box[Option[String]] = { - extractString("changeRequestName")(req)(Full(_)) - } - - def extractChangeRequestDescription(req: Req): String = { - extractString("changeRequestDescription")(req)(Full(_)).getOrElse(None).getOrElse("") - } - - def extractParameterName(params: Map[String, List[String]]): Box[String] = { - extractOneValue(params, "id")(toParameterName) match { - case Full(None) => Failure("Parameter id should not be empty") - case Full(Some(value)) => Full(value) - case eb: EmptyBox => eb ?~ "Error while fetch parameter Name" - } - } - - def extractWorkflowStatus(params: Map[String, List[String]]): Box[Seq[WorkflowNodeId]] = { - extractOneValue(params, "status")(toWorkflowStatus) match { - case Full(None) => Full(workflowLevelService.getWorkflowService().openSteps) - case Full(Some(value)) => Full(value) - case eb: EmptyBox => eb ?~ "Error while fetching workflow status" - } - } - - def extractWorkflowTargetStatus(params: Map[String, List[String]]): Box[WorkflowNodeId] = { - extractOneValue(params, "status")(toWorkflowTargetStatus) match { - case Full(Some(value)) => Full(value) - case Full(None) => Failure("workflow status should not be empty") - case eb: EmptyBox => eb ?~ "Error while fetching workflow status" - } - } - - def extractChangeRequestInfo(params: Map[String, List[String]]): Box[APIChangeRequestInfo] = { - def ident = (value: String) => Full(value) - for { - name <- extractOneValue(params, "name")(ident) - description <- extractOneValue(params, "description")(ident) - } yield { - APIChangeRequestInfo(name, description) - } - } - - def extractTechnique(optTechniqueName: Option[TechniqueName], opTechniqueVersion: Option[TechniqueVersion]): Box[Technique] = { - optTechniqueName match { - case Some(techniqueName) => - opTechniqueVersion match { - case Some(version) => - techniqueRepository.getTechniqueVersions(techniqueName).find(_ == version) match { - case Some(version) => - techniqueRepository.get(TechniqueId(techniqueName, version)) match { - case Some(technique) => Full(technique) - case None => - Failure(s" Technique '${techniqueName.value}' version '${version.serialize}' is not a valid Technique") - } - case None => Failure(s" version '${version.serialize}' of Technique '${techniqueName.value}' is not valid") - } - case None => - techniqueRepository.getLastTechniqueByName(techniqueName) match { - case Some(technique) => Full(technique) - case None => Failure(s"Error while fetching last version of technique '${techniqueName.value}''") - } - } - case None => Failure("techniqueName should not be empty") - } - } - - def checkTechniqueVersion( - techniqueName: TechniqueName, - techniqueVersion: Option[TechniqueVersion] - ): Box[Option[TechniqueVersion]] = { - techniqueVersion match { - case Some(version) => - techniqueRepository.getTechniqueVersions(techniqueName).find(_ == version) match { - case Some(version) => Full(Some(version)) - case None => Failure(s" version '${version.serialize}' of technique '${techniqueName.value}' is not valid") - } - case None => Full(None) - } - } - - def extractNodeGroupCategoryId(params: Map[String, List[String]]): Box[NodeGroupCategoryId] = { - extractOneValue(params, "nodeGroupCategory")(toNodeGroupCategoryId) match { - case Full(Some(category)) => Full(category) - case Full(None) => Failure("nodeGroupCategory cannot be empty") - case eb: EmptyBox => eb ?~ "error when deserializing node group category" - } - } - - def toTag(s: String): Box[Tag] = { - import Tag.* - val list = s.split(":") - val name = list.headOption.getOrElse(s) - val value = list.tail.headOption.getOrElse("") - Full(Tag(name, value)) - } - def extractRule(params: Map[String, List[String]]): Box[RestRule] = { - - for { - name <- extractOneValue(params, "displayName")(toMinimalSizeString(3)) - category <- extractOneValue(params, "category")(toRuleCategoryId) - shortDescription <- extractOneValue(params, "shortDescription")() - longDescription <- extractOneValue(params, "longDescription")() - enabled <- extractOneValue(params, "enabled")(toBoolean) - directives <- extractList(params, "directives")(convertListToDirectiveId) - target <- toRuleTarget(params, "targets") - tagsList <- extractList(params, "tags")(traverse(_)(toTag)) - tags = tagsList.map(t => Tags(t.toSet)) - } yield { - RestRule(name, category, shortDescription, longDescription, directives, target.map(Set(_)), enabled, tags) - } - } - - def extractRuleCategory(params: Map[String, List[String]]): Box[RestRuleCategory] = { - - for { - name <- extractOneValue(params, "name")(toMinimalSizeString(3)) - description <- extractOneValue(params, "description")() - parent <- extractOneValue(params, "parent")(toRuleCategoryId) - id <- extractOneValue(params, "id")(toRuleCategoryId) - } yield { - RestRuleCategory(name, description, parent, id) - } - } - - def extractGroup(params: Map[String, List[String]]): Box[RestGroup] = { - for { - id <- extractOneValue(params, "id")() - name <- extractOneValue(params, "displayName")(toMinimalSizeString(3)) - description <- extractOneValue(params, "description")() - enabled <- extractOneValue(params, "enabled")(toBoolean) - dynamic <- extractOneValue(params, "dynamic")(toBoolean) - query <- extractOneValue(params, "query")(toQuery) - _ <- if (query.map(_.criteria.size > 0).getOrElse(true)) Full("Query has at least one criteria") - else Failure("Query should containt at least one criteria") - category <- extractOneValue(params, "category")(toGroupCategoryId) - properties <- extractGroupProperties(params) - } yield { - RestGroup(id, name, description, properties, query, dynamic, enabled, category) - } - } - def extractGroupCategory(params: Map[String, List[String]]): Box[RestGroupCategory] = { for { @@ -575,108 +125,9 @@ final case class RestExtractorService( } } - def extractParameter(params: Map[String, List[String]]): Box[RestParameter] = { - for { - description <- extractOneValue(params, "description")() - value <- extractOneValue(params, "value")(s => GenericProperty.parseValue(s).toBox) - } yield { - RestParameter(value, description) - } - } - - /* - * Looking for parameter: "properties=foo=bar" - * ==> set foo to bar; delete baz, set plop to plop. - * With that syntaxe, you can't choose override mode - */ - def extractNodeProperties(params: Map[String, List[String]]): Box[Option[List[NodeProperty]]] = { - // properties coming from the API are always provider=rudder / mode=read-write - extractProperties(params, (k, v) => NodeProperty.parse(k, v, None, None)) - } - def extractGroupProperties(params: Map[String, List[String]]): Box[Option[List[GroupProperty]]] = { - // properties coming from the API are always provider=rudder / mode=read-write - // TODO: parse revision correctly - extractProperties(params, (k, v) => GroupProperty.parse(k, GitVersion.DEFAULT_REV, v, None, None)) - } - - def extractProperties[A](params: Map[String, List[String]], make: (String, String) => PureResult[A]): Box[Option[List[A]]] = { - import cats.implicits.* - import com.normation.box.* - - extractList(params, "properties") { props => - (props.traverse { prop => - val parts = prop.split('=') - - for { - name <- PropertyParser.validPropertyName(parts(0)) - prop <- if (parts.size == 1) make(name, "") - else make(name, parts(1)) - } yield { - prop - } - }).toBox - } - } - - /* - * expecting json: - * { "properties": [ - * {"name":"foo" , "value":"bar" } - * , {"name":"baz" , "value": "" } - * , {"name":"plop", "value":"plop" } - * ] } - */ - - def extractNodeProperty(json: JValue): Box[NodeProperty] = { - ((json \ "name"), (json \ "value")) match { - case (JString(nameValue), value) => - val provider = (json \ "provider") match { - case JString(string) => Some(PropertyProvider(string)) - // if not defined of not a string, use default - case _ => None - } - val inheritMode = (json \ "inheritMode") match { - case JString(string) => InheritMode.parseString(string).toOption - // if not defined of not a string, use default - case _ => None - } - (for { - _ <- PropertyParser.validPropertyName(nameValue) - } yield { - NodeProperty(nameValue, GenericProperty.fromJsonValue(value), inheritMode, provider) - }).toBox - - case (a, b) => - Failure(s"""Error when trying to parse new property: '${compactRender( - json - )}'. The awaited format is: {"name": string, "value": json}""") - } - } - - def extractNodePropertiesrFromJSON(json: JValue): Box[RestNodeProperties] = { - import com.normation.utils.Control.traverse - for { - props <- json \ "properties" match { - case JArray(props) => Full(props) - case x => Failure(s"""Error: the given parameter is not a JSON object with a 'properties' key""") - } - seq <- traverse(props)(extractNodeProperty) - } yield { - RestNodeProperties(Some(seq)) - } - } - - def extractNodePropertiesFromJSON(json: JValue): Box[Option[List[NodeProperty]]] = { - import com.normation.utils.Control.traverse - json \ "properties" match { - case JArray(props) => traverse(props)(extractNodeProperty).map(x => Some(x.toList)) - case JNothing => Full(None) - case x => Failure(s"""Error: the given parameter is not a JSON object with a 'properties' key""") - } - } - /* * Looking for parameter: "level=2" + * Still used in compliance APIs */ def extractComplianceLevel(params: Map[String, List[String]]): Box[Option[Int]] = { params.get("level") match { @@ -706,191 +157,6 @@ final case class RestExtractorService( } } - def extractRule(req: Req): Box[RestRule] = { - req.json match { - case Full(json) => extractRuleFromJSON(json) - case _ => extractRule(req.params) - } - } - - def extractDirective(req: Req): Box[RestDirective] = { - req.json match { - case Full(json) => extractDirectiveFromJSON(json) - case _ => extractDirective(req.params) - } - } - - def extractDirective(params: Map[String, List[String]]): Box[RestDirective] = { - for { - name <- ( - extractOneValue(params, "name")(toMinimalSizeString(3)), - extractOneValue(params, "displayName")(toMinimalSizeString(3)) - ) match { - case (res @ Full(Some(name)), _) => res - case (_, res @ Full(Some(name))) => res - case (Full(None), Full(None)) => Full(None) - case (eb: EmptyBox, _) => eb - case (_, eb: EmptyBox) => eb - } - shortDescription <- extractOneValue(params, "shortDescription")() - longDescription <- extractOneValue(params, "longDescription")() - enabled <- extractOneValue(params, "enabled")(toBoolean) - priority <- extractOneValue(params, "priority")(toInt) - parameters <- extractOneValue(params, "parameters")(toDirectiveParam) - techniqueName <- extractOneValue(params, "techniqueName")(x => Full(TechniqueName(x))) - techniqueVersion <- extractOneValue(params, "techniqueVersion")(x => TechniqueVersion.parse(x).toBox) - policyMode <- extractOneValue(params, "policyMode")(PolicyMode.parseDefault(_).toBox) - tagsList <- extractList(params, "tags")(traverse(_)(toTag)) - tags = tagsList.map(t => Tags(t.toSet)) - } yield { - RestDirective( - name, - shortDescription, - longDescription, - enabled, - parameters, - priority, - techniqueName, - techniqueVersion, - policyMode, - tags - ) - } - } - - def toTagJson(json: JValue): Box[Tag] = { - import Tag.* - json match { - case JObject(JField(name, JString(value)) :: Nil) => Full(Tag(name, value)) - case _ => Failure("Not valid format for tags") - } - } - - def extractRuleFromJSON(json: JValue): Box[RestRule] = { - for { - name <- extractJsonString(json, "displayName", toMinimalSizeString(3)) - category <- extractJsonString(json, "category", toRuleCategoryId) - shortDescription <- extractJsonString(json, "shortDescription") - longDescription <- extractJsonString(json, "longDescription") - directives <- extractJsonListString(json, "directives", convertListToDirectiveId) - target <- toRuleTarget(json, "targets") - enabled <- extractJsonBoolean(json, "enabled") - tags <- extractTagsFromJson(json \ "tags") ?~! "Error when extracting Rule tags" - } yield { - RestRule(name, category, shortDescription, longDescription, directives, target.map(Set(_)), enabled, tags) - } - } - - def extractRuleCategory(json: JValue): Box[RestRuleCategory] = { - for { - name <- extractJsonString(json, "name", toMinimalSizeString(3)) - description <- extractJsonString(json, "description") - parent <- extractJsonString(json, "parent", toRuleCategoryId) - id <- extractJsonString(json, "id", toRuleCategoryId) - } yield { - RestRuleCategory(name, description, parent, id) - } - } - - // this extractTagsFromJson is exclusively used when updating TAG in the POST API request. We want to extract tags as a List - // of {key1,value1 ... keyN,valueN} - - private def extractTagsFromJson(value: JValue): Box[Option[Tags]] = { - implicit val formats = DefaultFormats - if (value == JNothing) Full(None) // missing tag in json means user doesn't want to update them - else { - for { - jobjects <- Box(value.extractOpt[List[JObject]]) ?~! s"Invalid JSON serialization for Tags ${value}" - // be careful, we need to use JObject.obj to get the list even if there is duplicated keys, - // which would be removed with JObject.values - pairs <- Control.traverse(jobjects) { o => - Control.traverse(o.obj) { - case JField(key, v) => - v match { - case JString(s) if (s.nonEmpty) => Full((key, s)) - case _ => Failure(s"Cannot parse value '${v}' as a valid tag value for tag with name '${key}'") - - } - } - } - } yield { - val tags = pairs.flatten - Some(Tags(tags.map { case (k, v) => Tag(TagName(k), TagValue(v)) }.toSet)) - } - } - } - - def extractDirectiveFromJSON(json: JValue): Box[RestDirective] = { - for { - name <- ( - extractJsonString(json, "name", toMinimalSizeString(3)), - extractJsonString(json, "displayName", toMinimalSizeString(3)) - ) match { - case (res @ Full(Some(name)), _) => res - case (_, res @ Full(Some(name))) => res - case (Full(None), Full(None)) => Full(None) - case (eb: EmptyBox, _) => eb - case (_, eb: EmptyBox) => eb - } - shortDescription <- extractJsonString(json, "shortDescription") - longDescription <- extractJsonString(json, "longDescription") - enabled <- extractJsonBoolean(json, "enabled") - priority <- extractJsonInt(json, "priority") - parameters <- extractJsonDirectiveParam(json) - techniqueName <- extractJsonString(json, "techniqueName", x => Full(TechniqueName(x))) - techniqueVersion <- extractJsonString(json, "techniqueVersion", x => TechniqueVersion.parse(x).toBox) - policyMode <- extractJsonString(json, "policyMode", PolicyMode.parseDefault(_).toBox) - tags <- extractTagsFromJson(json \ "tags") ?~! "Error when extracting Directive tags" - } yield { - RestDirective( - name, - shortDescription, - longDescription, - enabled, - parameters, - priority, - techniqueName, - techniqueVersion, - policyMode, - tags - ) - } - } - - def extractGroupPropertiesFromJSON(json: JValue): Box[Option[Seq[GroupProperty]]] = { - import com.normation.utils.Control.traverse - json \ "properties" match { - case JArray(props) => - traverse(props)(p => GroupProperty.unserializeLdapGroupProperty(GenericProperty.serializeJson(p)).toBox).map(Some(_)) - case JNothing => Full(None) - case x => Failure(s"""Error: the given parameter is not a JSON object with a 'properties' key""") - } - } - - def extractGroupFromJSON(json: JValue): Box[RestGroup] = { - for { - id <- extractJsonString(json, "id") - name <- extractJsonString(json, "displayName", toMinimalSizeString(3)) - description <- extractJsonString(json, "description") - properties <- extractGroupPropertiesFromJSON(json) - enabled <- extractJsonBoolean(json, "enabled") - dynamic <- extractJsonBoolean(json, "dynamic") - query <- json \ "query" match { - case JNothing => Full(None) - case stringQuery => - for { - st <- queryParser.jsonParse(stringQuery) - q <- queryParser.parse(st) - _ <- if (q.criteria.size > 0) Full("Query has at least one criteria") - else Failure("Query should containt at least one criteria") - } yield Some(q) - } - category <- extractJsonString(json, "category", toGroupCategoryId) - } yield { - RestGroup(id, name, description, properties.map(_.toList), query, dynamic, enabled, category) - } - } - def extractGroupCategory(json: JValue): Box[RestGroupCategory] = { for { id <- extractJsonString(json, "id", toNodeGroupCategoryId) @@ -902,31 +168,6 @@ final case class RestExtractorService( } } - def extractParameterNameFromJSON(json: JValue): Box[String] = { - extractJsonString(json, "id", toParameterName) match { - case Full(None) => Failure("Parameter id should not be empty") - case Full(Some(value)) => Full(value) - case eb: EmptyBox => eb ?~ "Error while fetch parameter Name" - } - } - - def extractParameterFromJSON(json: JValue): Box[RestParameter] = { - for { - description <- extractJsonString(json, "description") - value <- (json \ "value") match { - case JNothing => Full(None) - case x => Full(Some(GenericProperty.fromJsonValue(x))) - } - inheritMode <- (json \ "inheritMode") match { - case JString(s) => InheritMode.parseString(s).map(x => Some(x)).toBox - case JNothing => Full(None) - case x => Failure("Can not parse inherit mode: " + x) - } - } yield { - RestParameter(value, description, inheritMode) - } - } - /* * The ACL list which is exchange between */ @@ -981,41 +222,9 @@ final case class RestExtractorService( } } - def extractNodeIdsFromJson(json: JValue): Box[Option[List[NodeId]]] = { - extractJsonListString(json, "nodeId", convertListToNodeId) - } - - def extractQuery(params: Map[String, List[String]]): Box[Option[Query]] = { - extractOneValue(params, "query")(toQuery) match { - case Full(None) => - extractOneValue(params, CRITERIA)(toQueryCriterion) match { - case Full(None) => Full(None) - case Full(Some(Nil)) => Failure("Query should at least contain one criteria") - case Full(Some(criterion)) => - for { - // Target defaults to NodeReturnType - optType <- extractOneValue(params, TARGET)(toQueryReturnType) - returnType = optType.getOrElse(NodeReturnType) - - // Composition defaults to None/And - optComposition <- extractOneValue(params, COMPOSITION)(toQueryComposition) - composition = optComposition.getOrElse(None) - transform <- extractOneValue(params, TRANSFORM)(toQueryTransform) - - // Query may fail when parsing - stringQuery = StringQuery(returnType, composition, transform.flatten, criterion.toList) - query <- queryParser.parse(stringQuery) - } yield { - Some(query) - } - case eb: EmptyBox => eb ?~! "error with query" - } - case Full(query) => - Full(query) - case eb: EmptyBox => eb ?~! "error with query" - } - } - + /* + * Still used in technique API + */ def extractString[T](key: String)(req: Req)(fun: String => Box[T]): Box[Option[T]] = { req.json match { case Full(json) => @@ -1033,31 +242,9 @@ final case class RestExtractorService( } } - def extractInt[T](key: String)(req: Req)(fun: BigInt => Box[T]): Box[Option[T]] = { - req.json match { - case Full(json) => - json \ key match { - case JInt(value) => fun(value).map(Some(_)) - case JNothing => Full(None) - case x => Failure(s"Not a valid value for '${key}' parameter, current value is : ${x}") - } - case _ => - req.params.get(key) match { - case None => Full(None) - case Some(head :: Nil) => - try { - fun(head.toLong).map(Some(_)) - } catch { - case e: Throwable => - Failure( - s"Parsing request parameter '${key}' as an integer failed, current value is '${head}'. Error message is: '${e.getMessage}'." - ) - } - case Some(list) => Failure(s"${list.size} values defined for 'id' parameter, only one needs to be defined") - } - } - } - + /* + * Still used in technique/eventlog/apiaccounts API + */ def extractBoolean[T](key: String)(req: Req)(fun: Boolean => T): Box[Option[T]] = { req.json match { case Full(json) => @@ -1083,82 +270,6 @@ final case class RestExtractorService( } } - def extractMap[T, U](key: String)(req: Req)( - keyFun: String => T, - jsonValueFun: JValue => U, - paramValueFun: String => U, - paramMapSepartor: String - ): Box[Option[Map[T, U]]] = { - req.json match { - case Full(json) => - json \ key match { - case JObject(fields) => - val map: Map[T, U] = fields.map { case JField(fieldName, value) => (keyFun(fieldName), jsonValueFun(value)) }.toMap - Full(Some(map)) - case JNothing => Full(None) - case x => Failure(s"Not a valid value for '${key}' parameter, current value is : ${x}") - } - case _ => - req.params.get(key) match { - case None => Full(None) - case Some(keyValues) => - (bestEffort(keyValues) { - case keyValue => - val splitted = keyValue.split(paramMapSepartor, 1).toList - splitted match { - case key :: value :: Nil => Full((keyFun(key), paramValueFun(value))) - case _ => Failure("Could not split value") - } - }).map(values => Some(values.toMap)) - } - } - } - - def extractObj[T](key: String)(req: Req)(jsonValueFun: JObject => Box[T]): Box[Option[T]] = { - req.json match { - case Full(json) => extractJsonObj(json, key, jsonValueFun) - case _ => - req.params.get(key) match { - case None => Full(None) - case Some(value :: Nil) => - parseOpt(value) match { - case Some(obj: JObject) => jsonValueFun(obj).map(Some(_)) - case _ => Failure(s"Not a valid value for '${key}' parameter, current value is : ${value}") - } - case Some(list) => Failure(s"${list.size} values defined for '${key}' parameter, only one needs to be defined") - } - } - } - - def extractList[T](key: String)(req: Req)(fun: String => Box[T]): Box[List[T]] = { - req.json match { - case Full(json) => - json \ key match { - case JString(value) => fun(value).map(_ :: Nil) - case JArray(values) => - com.normation.utils.Control - .bestEffort(values) { value => - value match { - case JString(value) => fun(value) - case x => - Failure(s"Not a valid value for '${key}' parameter, current value is : ${x}") - } - - } - .map(_.toList) - case JNothing => Full(Nil) - case x => Failure(s"Not a valid value for '${key}' parameter, current value is : ${x}") - } - case _ => - req.params.get(key) match { - case None => Full(Nil) - case Some(list) => bestEffort(list)(fun(_)).map(_.toList) - } - } - } - - def extractId[T](req: Req)(fun: String => Box[T]): Box[Option[T]] = extractString("id")(req)(fun) - def extractComplianceFormat(params: Map[String, List[String]]): Box[ComplianceFormat] = { params.get("format") match { case None | Some(Nil) | Some("" :: Nil) => diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/RestUtils.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/RestUtils.scala index 6f7c55126d1..a2261bc3c6d 100644 --- a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/RestUtils.scala +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/RestUtils.scala @@ -37,10 +37,13 @@ package com.normation.rudder.rest +import com.normation.box.* import com.normation.eventlog.EventActor import com.normation.eventlog.ModificationId import com.normation.rudder.api.ApiVersion +import com.normation.rudder.config.ReasonBehavior import com.normation.rudder.domain.logger.ApiLogger +import com.normation.rudder.rest.RudderJsonRequest.* import com.normation.rudder.users.UserService import com.normation.utils.StringUuidGenerator import net.liftweb.common.Box @@ -219,8 +222,7 @@ object RestUtils { restExtractor: RestExtractorService, dataName: String, id: Option[String] - )(function: Box[JValue], req: Req, errorMessage: String)(implicit action: String): LiftResponse = { - implicit val prettify = restExtractor.extractPrettify(req.params) + )(function: Box[JValue], req: Req, errorMessage: String)(implicit action: String, prettify: Boolean): LiftResponse = { function match { case Full(category: JValue) => toJsonResponse(id, (dataName -> category)) @@ -237,18 +239,22 @@ object RestUtils { function: Box[ActionType], req: Req, errorMessage: String - )(implicit action: String, userService: UserService): LiftResponse = { - actionResponse2(restExtractor, dataName, uuidGen, id)(function, req, errorMessage)(action, RestUtils.getActor(req)) + )(implicit action: String, prettify: Boolean, reasonBehavior: ReasonBehavior, userService: UserService): LiftResponse = { + actionResponse2(restExtractor, dataName, uuidGen, id)(function, req, errorMessage)( + action, + prettify, + reasonBehavior, + RestUtils.getActor(req) + ) } def actionResponse2(restExtractor: RestExtractorService, dataName: String, uuidGen: StringUuidGenerator, id: Option[String])( function: Box[ActionType], req: Req, errorMessage: String - )(implicit action: String, actor: EventActor): LiftResponse = { - implicit val prettify = restExtractor.extractPrettify(req.params) + )(implicit action: String, prettify: Boolean, reasonBehavior: ReasonBehavior, actor: EventActor): LiftResponse = { (for { - reason <- restExtractor.extractReason(req) + reason <- extractReason(req).toBox modId = ModificationId(uuidGen.newUuid) result <- function.flatMap(_(actor, modId, reason)) } yield { @@ -262,42 +268,6 @@ object RestUtils { } } - type WorkflowType = (EventActor, Option[String], String, String) => Box[JValue] - def workflowResponse(restExtractor: RestExtractorService, dataName: String, uuidGen: StringUuidGenerator, id: Option[String])( - function: Box[WorkflowType], - req: Req, - errorMessage: String, - defaultName: String - )(implicit action: String, userService: UserService): LiftResponse = { - workflowResponse2(restExtractor, dataName, uuidGen, id)(function, req, errorMessage, defaultName)( - action, - RestUtils.getActor(req) - ) - } - def workflowResponse2(restExtractor: RestExtractorService, dataName: String, uuidGen: StringUuidGenerator, id: Option[String])( - function: Box[WorkflowType], - req: Req, - errorMessage: String, - defaultName: String - )(implicit action: String, actor: EventActor): LiftResponse = { - implicit val prettify = restExtractor.extractPrettify(req.params) - - (for { - reason <- restExtractor.extractReason(req) - crName <- restExtractor.extractChangeRequestName(req).map(_.getOrElse(defaultName)) - crDesc = restExtractor.extractChangeRequestDescription(req) - result <- function.flatMap(_(actor, reason, crName, crDesc)) - } yield { - result - }) match { - case Full(result: JValue) => - toJsonResponse(id, (dataName -> result)) - case eb: EmptyBox => - val message = (eb ?~! errorMessage).messageChain - toJsonError(id, message) - } - } - def notFoundResponse(id: Option[String], message: JValue)(implicit action: String, prettify: Boolean): LiftResponse = { effectiveResponse(id, message, NotFoundError, action, prettify) } diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/RudderJsonRequest.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/RudderJsonRequest.scala index c1c22f442cd..4c2443de70a 100644 --- a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/RudderJsonRequest.scala +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/RudderJsonRequest.scala @@ -37,8 +37,14 @@ package com.normation.rudder.rest +import cats.syntax.either.* +import cats.syntax.monadError.* +import com.normation.errors.Chained +import com.normation.errors.Inconsistency import com.normation.errors.PureResult import com.normation.rudder.apidata.ZioJsonExtractor +import com.normation.rudder.config.ReasonBehavior +import com.normation.rudder.config.ReasonBehavior.* import net.liftweb.http.Req import zio.json.* @@ -56,4 +62,19 @@ object RudderJsonRequest { } } + + def extractReason(req: Req)(implicit reasonBehavior: ReasonBehavior): PureResult[Option[String]] = { + def reason = req.params.get("reason").flatMap(_.headOption) + (reasonBehavior match { + case Disabled => Right(None) + case Mandatory => + reason + .toRight[String]("Reason field is mandatory and should be at least 5 characters long") + .reject { + case s if s.lengthIs < 5 => "Reason field should be at least 5 characters long" + } + .map(Some(_)) + case Optional => Right(reason) + }).leftMap(err => Chained("There was an error while extracting reason message", Inconsistency(err))) + } } diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/DirectiveApi.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/DirectiveApi.scala index 21ecb5f69a0..702614b9c97 100644 --- a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/DirectiveApi.scala +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/DirectiveApi.scala @@ -73,8 +73,6 @@ import com.normation.rudder.rest.* import com.normation.rudder.rest.ApiPath import com.normation.rudder.rest.AuthzToken import com.normation.rudder.rest.DirectiveApi as API -import com.normation.rudder.rest.RestExtractorService -import com.normation.rudder.rest.RestUtils import com.normation.rudder.rest.data.* import com.normation.rudder.rest.implicits.* import com.normation.rudder.services.workflows.ChangeRequestService @@ -94,38 +92,13 @@ import zio.* import zio.syntax.* class DirectiveApi( - restExtractorService: RestExtractorService, - zioJsonExtractor: ZioJsonExtractor, - uuidGen: StringUuidGenerator, - service: DirectiveApiService14 + zioJsonExtractor: ZioJsonExtractor, + uuidGen: StringUuidGenerator, + service: DirectiveApiService14 ) extends LiftApiModuleProvider[API] { - private val dataName = "directives" - def schemas: ApiModuleProvider[API] = API - type ActionType = RestUtils.ActionType - def actionResponse(function: Box[ActionType], req: Req, errorMessage: String, id: Option[String], actor: EventActor)(implicit - action: String - ): LiftResponse = { - RestUtils.actionResponse2(restExtractorService, dataName, uuidGen, id)(function, req, errorMessage)(action, actor) - } - - type WorkflowType = RestUtils.WorkflowType - def workflowResponse( - function: Box[WorkflowType], - req: Req, - errorMessage: String, - id: Option[String], - defaultName: String, - actor: EventActor - )(implicit action: String): LiftResponse = { - RestUtils.workflowResponse2(restExtractorService, dataName, uuidGen, id)(function, req, errorMessage, defaultName)( - action, - actor - ) - } - def getLiftEndpoints(): List[LiftApiModule] = { API.endpoints.map { case API.DirectiveTree => DirectiveTree @@ -149,7 +122,7 @@ class DirectiveApi( val schema: API.DirectiveTree.type = API.DirectiveTree def process0(version: ApiVersion, path: ApiPath, req: Req, params: DefaultParams, authzToken: AuthzToken): LiftResponse = { (for { - includeSystem <- restExtractorService.extractBoolean("includeSystem")(req)(identity).toIO + includeSystem <- zioJsonExtractor.extractIncludeSystem(req).toIO res <- service.directiveTree(includeSystem.getOrElse(false)) } yield { res diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/GroupsApi.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/GroupsApi.scala index 72c0b433b78..565b09a3108 100644 --- a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/GroupsApi.scala +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/GroupsApi.scala @@ -52,6 +52,8 @@ import com.normation.rudder.apidata.ZioJsonExtractor import com.normation.rudder.apidata.implicits.* import com.normation.rudder.batch.AsyncDeploymentActor import com.normation.rudder.batch.AutomaticStartDeployment +import com.normation.rudder.config.ReasonBehavior +import com.normation.rudder.config.UserPropertyService import com.normation.rudder.domain.nodes.* import com.normation.rudder.domain.properties.FailedNodePropertyHierarchy import com.normation.rudder.domain.properties.SuccessNodePropertyHierarchy @@ -87,9 +89,12 @@ class GroupsApi( restExtractorService: RestExtractorService, zioJsonExtractor: ZioJsonExtractor, uuidGen: StringUuidGenerator, + userPropertyService: UserPropertyService, service: GroupApiService14 ) extends LiftApiModuleProvider[API] { + def reasonBehavior: ReasonBehavior = userPropertyService.reasonsFieldBehavior + def schemas: ApiModuleProvider[API] = API /* @@ -118,15 +123,22 @@ class GroupsApi( import net.liftweb.json.* def response(function: Box[JValue], req: Req, errorMessage: String, id: Option[String])(implicit - action: String + action: String, + prettify: Boolean ): LiftResponse = { RestUtils.response(restExtractorService, "groupCategories", id)(function, req, errorMessage) } def actionResponse(function: Box[ActionType], req: Req, errorMessage: String, id: Option[String], actor: EventActor)(implicit - action: String + action: String, + prettify: Boolean ): LiftResponse = { - RestUtils.actionResponse2(restExtractorService, "groupCategories", uuidGen, id)(function, req, errorMessage)(action, actor) + RestUtils.actionResponse2(restExtractorService, "groupCategories", uuidGen, id)(function, req, errorMessage)( + action, + prettify, + reasonBehavior, + actor + ) } // group categories @@ -281,6 +293,7 @@ class GroupsApi( val restExtractor = restExtractorService def process0(version: ApiVersion, path: ApiPath, req: Req, params: DefaultParams, authzToken: AuthzToken): LiftResponse = { implicit val action = schema.name + implicit val prettify: Boolean = params.prettify response( service.getCategoryTree(version), req, @@ -301,6 +314,7 @@ class GroupsApi( authzToken: AuthzToken ): LiftResponse = { implicit val action = schema.name + implicit val prettify: Boolean = params.prettify response( service.getCategoryDetails(NodeGroupCategoryId(id), version), req, @@ -321,6 +335,7 @@ class GroupsApi( authzToken: AuthzToken ): LiftResponse = { implicit val action = schema.name + implicit val prettify: Boolean = params.prettify actionResponse( Full(service.deleteCategory(NodeGroupCategoryId(id), version)), req, @@ -342,7 +357,8 @@ class GroupsApi( authzToken: AuthzToken ): LiftResponse = { implicit val action = schema.name - val x = for { + implicit val prettify: Boolean = params.prettify + val x = for { restCategory <- { if (req.json_?) { for { @@ -372,8 +388,9 @@ class GroupsApi( val restExtractor = restExtractorService def process0(version: ApiVersion, path: ApiPath, req: Req, params: DefaultParams, authzToken: AuthzToken): LiftResponse = { implicit val action = schema.name - val id = () => NodeGroupCategoryId(uuidGen.newUuid) - val x = for { + implicit val prettify: Boolean = params.prettify + val id = () => NodeGroupCategoryId(uuidGen.newUuid) + val x = for { restCategory <- { if (req.json_?) { for { diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/NodeApi.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/NodeApi.scala index b22c10cc4e5..fe4891e51bd 100644 --- a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/NodeApi.scala +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/NodeApi.scala @@ -57,6 +57,8 @@ import com.normation.rudder.apidata.RenderInheritedProperties import com.normation.rudder.apidata.RestDataSerializer import com.normation.rudder.apidata.ZioJsonExtractor import com.normation.rudder.apidata.implicits.* +import com.normation.rudder.config.ReasonBehavior +import com.normation.rudder.config.UserPropertyService import com.normation.rudder.domain.NodeDit import com.normation.rudder.domain.logger.NodeLogger import com.normation.rudder.domain.logger.NodeLoggerPure @@ -86,14 +88,13 @@ import com.normation.rudder.properties.PropertiesRepository import com.normation.rudder.reports.ReportingConfiguration import com.normation.rudder.reports.execution.AgentRunWithNodeConfig import com.normation.rudder.reports.execution.RoReportsExecutionRepository -import com.normation.rudder.repository.json.DataExtractor.OptionnalJson import com.normation.rudder.repository.ldap.LDAPEntityMapper import com.normation.rudder.rest.ApiModuleProvider import com.normation.rudder.rest.ApiPath import com.normation.rudder.rest.AuthzToken import com.normation.rudder.rest.NodeApi as API import com.normation.rudder.rest.OneParam -import com.normation.rudder.rest.RestExtractorService +import com.normation.rudder.rest.RudderJsonRequest.* import com.normation.rudder.rest.RudderJsonResponse import com.normation.rudder.rest.data.* import com.normation.rudder.rest.data.Creation.CreationError @@ -118,8 +119,6 @@ import com.normation.rudder.services.reports.ReportingService import com.normation.rudder.services.servers.DeleteMode import com.normation.rudder.services.servers.NewNodeManager import com.normation.rudder.services.servers.RemoveNodeService -import com.normation.rudder.web.services.ReasonBehavior -import com.normation.rudder.web.services.UserPropertyService import com.normation.utils.StringUuidGenerator import com.normation.zio.* import io.scalaland.chimney.syntax.* @@ -165,6 +164,8 @@ class NodeApi( deleteDefaultMode: DeleteMode ) extends LiftApiModuleProvider[API] { + implicit def reasonBehavior: ReasonBehavior = userPropertyService.reasonsFieldBehavior + def schemas: ApiModuleProvider[API] = API def getLiftEndpoints(): List[LiftApiModule] = { @@ -387,7 +388,7 @@ class NodeApi( (for { restNode <- restExtractor.extractUpdateNode(req).toIO - reason <- extractReason(req) + reason <- extractReason(req).toIO result <- nodeApiService.updateRestNode(NodeId(id), restNode) // await all properties update to guarantee that properties are resolved after node modification _ <- nodePropertiesService.updateAll() @@ -708,7 +709,7 @@ class NodeApi( implicit val qc = authzToken.qc val result = (for { - inheritedProperty <- req.json.flatMap(j => OptionnalJson.extractJsonBoolean(j, "inherited")).toIO + inheritedProperty <- zioJsonExtractor.extractNodeInherited(req).toIO response <- nodeApiService.property(req, property, inheritedProperty.getOrElse(false)) } yield { response @@ -731,25 +732,6 @@ class NodeApi( DeriveJsonEncoder.gen[JRNodeDetailLevel] } - // TODO: known to be duplicated in change-validation. Some day we will need to factor this out in zio (moving prop service to rudder-core) - private def extractReason(req: Req): IOResult[Option[String]] = { - import ReasonBehavior.* - (userPropertyService.reasonsFieldBehavior match { - case Disabled => ZIO.none - case mode => - val reason = req.params.get("reason").flatMap(_.headOption) - (mode: @unchecked) match { - case Mandatory => - reason - .notOptional("Reason field is mandatory and should be at least 5 characters long") - .reject { - case s if s.lengthIs < 5 => Inconsistency("Reason field should be at least 5 characters long") - } - .map(Some(_)) - case Optionnal => reason.succeed - } - }).chainError("There was an error while extracting reason message") - } } class NodeApiInheritedProperties( @@ -806,7 +788,7 @@ class NodeApiService( acceptedDit: InventoryDit, newNodeManager: NewNodeManager, removeNodeService: RemoveNodeService, - restExtractor: RestExtractorService, + restExtractor: ZioJsonExtractor, reportingService: ReportingService, acceptedNodeQueryProcessor: QueryProcessor, pendingNodeQueryProcessor: QueryChecker, @@ -1086,7 +1068,7 @@ class NodeApiService( def software(req: Req, software: String)(implicit qc: QueryContext): IOResult[Map[String, Version]] = { for { - optNodeIds <- req.json.flatMap(restExtractor.extractNodeIdsFromJson).toIO + optNodeIds <- restExtractor.extractNodeIdChunk(req).toIO nodes <- optNodeIds match { case None => nodeFactRepository.getAll() case Some(nodeIds) => nodeFactRepository.getAll().map(_.filterKeys(id => nodeIds.contains(id.value))) @@ -1102,7 +1084,7 @@ class NodeApiService( ): IOResult[Map[String, JRProperty]] = { for { - optNodeIds <- req.json.flatMap(restExtractor.extractNodeIdsFromJson).toIO + optNodeIds <- restExtractor.extractNodeIdChunk(req).toIO nodes <- optNodeIds match { case None => nodeFactRepository.getAll() case Some(nodeIds) => nodeFactRepository.getAll().map(_.filterKeys(id => nodeIds.contains(id.value))) diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/RuleApi.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/RuleApi.scala index 02603d98f4e..7702eafc55d 100644 --- a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/RuleApi.scala +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/RuleApi.scala @@ -59,8 +59,6 @@ import com.normation.rudder.repository.* import com.normation.rudder.rest.* import com.normation.rudder.rest.ApiPath import com.normation.rudder.rest.AuthzToken -import com.normation.rudder.rest.RestExtractorService -import com.normation.rudder.rest.RestUtils import com.normation.rudder.rest.RuleApi as API import com.normation.rudder.rest.implicits.* import com.normation.rudder.rule.category.* @@ -69,7 +67,6 @@ import com.normation.rudder.services.policies.RuleApplicationStatusService import com.normation.rudder.services.workflows.* import com.normation.rudder.web.services.ComputePolicyMode import com.normation.utils.StringUuidGenerator -import net.liftweb.common.Box import net.liftweb.http.LiftResponse import net.liftweb.http.Req import org.joda.time.DateTime @@ -78,25 +75,11 @@ import zio.* import zio.syntax.* class RuleApi( - restExtractorService: RestExtractorService, - zioJsonExtractor: ZioJsonExtractor, - service: RuleApiService14, - uuidGen: StringUuidGenerator + zioJsonExtractor: ZioJsonExtractor, + service: RuleApiService14, + uuidGen: StringUuidGenerator ) extends LiftApiModuleProvider[API] { - import RestUtils.* - - def actionResponse( - function: Box[ActionType], - req: Req, - errorMessage: String, - id: Option[String], - actor: EventActor, - dataName: String - )(implicit action: String): LiftResponse = { - RestUtils.actionResponse2(restExtractorService, dataName, uuidGen, id)(function, req, errorMessage)(action, actor) - } - def schemas: ApiModuleProvider[API] = API def getLiftEndpoints(): List[LiftApiModule] = { diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/SettingsApi.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/SettingsApi.scala index a5127068435..c0af5844c3f 100644 --- a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/SettingsApi.scala +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/SettingsApi.scala @@ -184,7 +184,8 @@ class SettingsApi( // sort settings alphanum RestUtils.response(restExtractorService, "settings", None)(Full(data.sortBy(_.name)), req, s"Could not settings")( - "getAllSettings" + "getAllSettings", + params.prettify ) } } @@ -202,7 +203,10 @@ class SettingsApi( JField(setting.key, value) } startNewPolicyGeneration(authzToken.qc.actor) - RestUtils.response(restExtractorService, "settings", None)(Full(data), req, s"Could not modfiy settings")("modifySettings") + RestUtils.response(restExtractorService, "settings", None)(Full(data), req, s"Could not modfiy settings")( + "modifySettings", + params.prettify + ) } } @@ -224,7 +228,8 @@ class SettingsApi( (key -> value) } RestUtils.response(restExtractorService, "settings", Some(key))(data, req, s"Could not get parameter '${key}'")( - "getSetting" + "getSetting", + params.prettify ) } } @@ -247,7 +252,8 @@ class SettingsApi( (key -> value) } RestUtils.response(restExtractorService, "settings", Some(key))(data, req, s"Could not modify parameter '${key}'")( - "modifySetting" + "modifySetting", + params.prettify ) } } @@ -693,7 +699,8 @@ class SettingsApi( } def response(function: Box[JValue], req: Req, errorMessage: String, id: Option[String])(implicit - action: String + action: String, + prettify: Boolean ): LiftResponse = { RestUtils.response(restExtractorService, kind, id)(function, req, errorMessage) } @@ -847,6 +854,7 @@ class SettingsApi( val restExtractor = restExtractorService def process0(version: ApiVersion, path: ApiPath, req: Req, params: DefaultParams, authzToken: AuthzToken): LiftResponse = { implicit val action = "getAllAllowedNetworks" + implicit val prettify: Boolean = params.prettify RestUtils.response(restExtractorService, getRootNameForVersion(version), None)( getAllowedNetworks()(authzToken.qc).toBox, req, @@ -868,7 +876,8 @@ class SettingsApi( ): LiftResponse = { implicit val action = "getAllowedNetworks" - val result = for { + implicit val prettify: Boolean = params.prettify + val result = for { nodeInfo <- nodeFactRepo.get(NodeId(id))(authzToken.qc) isServer <- nodeInfo match { case Some(info) if info.rudderSettings.isPolicyServer => @@ -909,6 +918,7 @@ class SettingsApi( ): LiftResponse = { implicit val action = "modifyAllowedNetworks" + implicit val prettify: Boolean = params.prettify def checkAllowedNetwork(v: String) = { val netWithoutSpaces = v.replaceAll("""\s""", "") @@ -992,6 +1002,7 @@ class SettingsApi( ): LiftResponse = { implicit val action = "modifyDiffAllowedNetworks" + implicit val prettify: Boolean = params.prettify def checkAllowedNetwork(v: String) = { val netWithoutSpaces = v.replaceAll("""\s""", "") diff --git a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/TechniqueApi.scala b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/TechniqueApi.scala index 19add6066a3..a8b553408bb 100644 --- a/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/TechniqueApi.scala +++ b/webapp/sources/rudder/rudder-rest/src/main/scala/com/normation/rudder/rest/lift/TechniqueApi.scala @@ -42,12 +42,13 @@ import com.normation.box.* import com.normation.cfclerk.domain.* import com.normation.cfclerk.services.TechniqueRepository import com.normation.errors.* -import com.normation.eventlog.EventActor import com.normation.eventlog.ModificationId import com.normation.rudder.api.ApiVersion import com.normation.rudder.apidata.JsonResponseObjects.* import com.normation.rudder.apidata.RestDataSerializer import com.normation.rudder.apidata.implicits.* +import com.normation.rudder.config.ReasonBehavior +import com.normation.rudder.config.UserPropertyService import com.normation.rudder.domain.logger.ApiLoggerPure import com.normation.rudder.domain.policies.Directive import com.normation.rudder.ncf.* @@ -56,7 +57,6 @@ import com.normation.rudder.ncf.yaml.YamlTechniqueSerializer import com.normation.rudder.repository.RoDirectiveRepository import com.normation.rudder.repository.xml.TechniqueRevisionRepository import com.normation.rudder.rest.{TechniqueApi as API, *} -import com.normation.rudder.rest.RestUtils.ActionType import com.normation.rudder.rest.RestUtils.response import com.normation.rudder.rest.implicits.* import com.normation.rudder.rest.lift.TechniqueApi.QueryFormat @@ -97,6 +97,7 @@ class TechniqueApi( techniqueRepository: TechniqueRepository, techniqueSerializer: TechniqueSerializer, uuidGen: StringUuidGenerator, + userPropertyService: UserPropertyService, resourceFileService: ResourceFileService, configRepoPath: String ) extends LiftApiModuleProvider[API] { @@ -104,38 +105,16 @@ class TechniqueApi( import TechniqueApi.* import zio.json.* import zio.json.yaml.* + + implicit def reasonBehavior: ReasonBehavior = userPropertyService.reasonsFieldBehavior + def schemas: ApiModuleProvider[API] = API val dataName = "techniques" - def resp(function: Box[JValue], req: Req, errorMessage: String)(action: String)(implicit dataName: String): LiftResponse = { - response(restExtractorService, dataName, None)(function, req, errorMessage) - } - - def actionResp(function: Box[ActionType], req: Req, errorMessage: String, actor: EventActor)(implicit + def resp(function: Box[JValue], req: Req, errorMessage: String)( action: String - ): LiftResponse = { - // implementation copied from RestUtils#actionResponse2 - // but changed to never fail on reason message extraction - implicit val prettify = restExtractorService.extractPrettify(req.params) - import net.liftweb.json.JsonDSL.* - import net.liftweb.common.EmptyBox - - ( - for { - reason <- restExtractorService.extractReason(req).or(Full(Some("Technique updated via technique editor API"))) - modId = ModificationId(uuidGen.newUuid) - result <- function.flatMap(_(actor, modId, reason)) - } yield { - result - } - ) match { - case Full(result: JValue) => - RestUtils.toJsonResponse(None, (dataName -> result)) - case eb: EmptyBox => - val message = (eb ?~! errorMessage).messageChain - RestUtils.toJsonError(None, message) - } - + )(implicit dataName: String, prettify: Boolean): LiftResponse = { + response(restExtractorService, dataName, None)(function, req, errorMessage) } def getLiftEndpoints(): List[LiftApiModule] = { @@ -178,6 +157,8 @@ class TechniqueApi( import net.liftweb.json.JsonDSL.* import zio.syntax.* + implicit val prettify: Boolean = params.prettify + def serializeResourceWithState(resource: ResourceFile) = { (("path" -> resource.path) ~ ("state" -> resource.state.value)) } @@ -245,6 +226,7 @@ class TechniqueApi( params: DefaultParams, authzToken: AuthzToken ): LiftResponse = { + implicit val prettify: Boolean = params.prettify val modId = ModificationId(uuidGen.newUuid) @@ -314,6 +296,7 @@ class TechniqueApi( implicit val dataName: String = "methods" def process0(version: ApiVersion, path: ApiPath, req: Req, params: DefaultParams, authzToken: AuthzToken): LiftResponse = { + implicit val prettify: Boolean = params.prettify val response = for { methods <- techniqueReader.getMethodsMetadata sorted = methods.toList.sortBy(_._1.value) @@ -332,6 +315,7 @@ class TechniqueApi( implicit val dataName: String = "methods" def process0(version: ApiVersion, path: ApiPath, req: Req, params: DefaultParams, authzToken: AuthzToken): LiftResponse = { + implicit val prettify: Boolean = params.prettify val response = for { _ <- techniqueReader.updateMethodsMetadataFile methods <- techniqueReader.getMethodsMetadata @@ -379,6 +363,7 @@ class TechniqueApi( implicit val dataName: String = "techniqueCategories" def process0(version: ApiVersion, path: ApiPath, req: Req, params: DefaultParams, authzToken: AuthzToken): LiftResponse = { + implicit val prettify: Boolean = params.prettify val response = { val categories = techniqueRepository.getAllCategories def serializeTechniqueCategory(t: TechniqueCategory): JObject = { diff --git a/webapp/sources/rudder/rudder-rest/src/test/scala/com/normation/rudder/rest/RestTestSetUp.scala b/webapp/sources/rudder/rudder-rest/src/test/scala/com/normation/rudder/rest/RestTestSetUp.scala index fb039332cc2..21cc651badb 100644 --- a/webapp/sources/rudder/rudder-rest/src/test/scala/com/normation/rudder/rest/RestTestSetUp.scala +++ b/webapp/sources/rudder/rudder-rest/src/test/scala/com/normation/rudder/rest/RestTestSetUp.scala @@ -59,6 +59,7 @@ import com.normation.rudder.apidata.ZioJsonExtractor import com.normation.rudder.batch.* import com.normation.rudder.batch.PolicyGenerationTrigger.AllGeneration import com.normation.rudder.campaigns.CampaignSerializer +import com.normation.rudder.config.StatelessUserPropertyService import com.normation.rudder.domain.appconfig.FeatureSwitch import com.normation.rudder.domain.nodes.NodeGroup import com.normation.rudder.domain.nodes.NodeGroupId @@ -147,7 +148,6 @@ import com.normation.rudder.web.model.LinkUtil import com.normation.rudder.web.services.DirectiveEditorServiceImpl import com.normation.rudder.web.services.DirectiveFieldFactory import com.normation.rudder.web.services.Section2FieldService -import com.normation.rudder.web.services.StatelessUserPropertyService import com.normation.rudder.web.services.Translator import com.normation.utils.StringUuidGeneratorImpl import com.normation.zio.* @@ -757,7 +757,7 @@ class RestTestSetUp { null, mockNodes.newNodeManager, mockNodes.removeNodeService, - restExtractorService, + zioJsonExtractor, mockCompliance.reportingService(Map.empty), mockNodes.queryProcessor, null, @@ -894,16 +894,16 @@ class RestTestSetUp { techniqueRepository, techniqueSerializer, uuidGen, + userPropertyService, resourceFileService, mockGitRepo.configurationRepositoryRoot.pathAsString ), new DirectiveApi( - restExtractorService, zioJsonExtractor, uuidGen, directiveApiService14 ), - new RuleApi(restExtractorService, zioJsonExtractor, ruleApiService14, uuidGen), + new RuleApi(zioJsonExtractor, ruleApiService14, uuidGen), new RulesInternalApi(ruleInternalApiService, ruleApiService14), new GroupsInternalApi(groupInternalApiService), new NodeApi( @@ -921,6 +921,7 @@ class RestTestSetUp { restExtractorService, zioJsonExtractor, uuidGen, + userPropertyService, groupService14 ), new SettingsApi( 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 1a8f250a361..31dd6f00b18 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 @@ -61,6 +61,8 @@ import com.normation.rudder.api.* import com.normation.rudder.apidata.* import com.normation.rudder.batch.* import com.normation.rudder.campaigns.* +import com.normation.rudder.config.StatelessUserPropertyService +import com.normation.rudder.config.UserPropertyService import com.normation.rudder.configuration.* import com.normation.rudder.db.Doobie import com.normation.rudder.domain.* @@ -1754,7 +1756,7 @@ object RudderConfigInit { acceptedNodesDit, newNodeManagerImpl, removeNodeServiceImpl, - restExtractorService, + zioJsonExtractor, reportingService, queryProcessor, inventoryQueryChecker, @@ -2107,10 +2109,10 @@ object RudderConfigInit { restExtractorService, zioJsonExtractor, stringUuidGenerator, + userPropertyService, groupApiService14 ), new DirectiveApi( - restExtractorService, zioJsonExtractor, stringUuidGenerator, directiveApiService14 @@ -2142,11 +2144,11 @@ object RudderConfigInit { techniqueRepository, techniqueSerializer, stringUuidGenerator, + userPropertyService, resourceFileService, RUDDER_GIT_ROOT_CONFIG_REPO ), new RuleApi( - restExtractorService, zioJsonExtractor, ruleApiService13, stringUuidGenerator @@ -2420,7 +2422,7 @@ object RudderConfigInit { configService.rudder_ui_changeMessage_mandatory _, configService.rudder_ui_changeMessage_explanation _ ) - lazy val userPropertyService = userPropertyServiceImpl + lazy val userPropertyService: UserPropertyService = userPropertyServiceImpl //////////////////////////////////// // non success services diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/TechniqueEditForm.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/TechniqueEditForm.scala index f814bc85802..5da0ab9527b 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/TechniqueEditForm.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/TechniqueEditForm.scala @@ -260,29 +260,29 @@ class TechniqueEditForm( } private val crReasons = { - import com.normation.rudder.web.services.ReasonBehavior.* - (userPropertyService.reasonsFieldBehavior: @unchecked) match { + import com.normation.rudder.config.ReasonBehavior.* + userPropertyService.reasonsFieldBehavior match { case Disabled => None case Mandatory => Some(buildReasonField(true, "px-1")) - case Optionnal => Some(buildReasonField(false, "px-1")) + case Optional => Some(buildReasonField(false, "px-1")) } } private val crReasonsRemovePopup = { - import com.normation.rudder.web.services.ReasonBehavior.* - (userPropertyService.reasonsFieldBehavior: @unchecked) match { + import com.normation.rudder.config.ReasonBehavior.* + userPropertyService.reasonsFieldBehavior match { case Disabled => None case Mandatory => Some(buildReasonField(true, "px-1")) - case Optionnal => Some(buildReasonField(false, "px-1")) + case Optional => Some(buildReasonField(false, "px-1")) } } private val crReasonsDisablePopup = { - import com.normation.rudder.web.services.ReasonBehavior.* - (userPropertyService.reasonsFieldBehavior: @unchecked) match { + import com.normation.rudder.config.ReasonBehavior.* + userPropertyService.reasonsFieldBehavior match { case Disabled => None case Mandatory => Some(buildReasonField(true, "px-1")) - case Optionnal => Some(buildReasonField(false, "px-1")) + case Optional => Some(buildReasonField(false, "px-1")) } } diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/CreateCategoryOrGroupPopup.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/CreateCategoryOrGroupPopup.scala index 1f21fc61c84..d95048cc3c4 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/CreateCategoryOrGroupPopup.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/CreateCategoryOrGroupPopup.scala @@ -313,11 +313,11 @@ class CreateCategoryOrGroupPopup( } private val piReasons = { - import com.normation.rudder.web.services.ReasonBehavior.* - (userPropertyService.reasonsFieldBehavior: @unchecked) match { + import com.normation.rudder.config.ReasonBehavior.* + userPropertyService.reasonsFieldBehavior match { case Disabled => None case Mandatory => Some(buildReasonField(true, "px-1")) - case Optionnal => Some(buildReasonField(false, "px-1")) + case Optional => Some(buildReasonField(false, "px-1")) } } diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/CreateCloneDirectivePopup.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/CreateCloneDirectivePopup.scala index d599f5809e8..74ca8fb4ded 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/CreateCloneDirectivePopup.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/CreateCloneDirectivePopup.scala @@ -125,11 +125,11 @@ class CreateCloneDirectivePopup( ///////////// fields for category settings /////////////////// private val reasons = { - import com.normation.rudder.web.services.ReasonBehavior.* - (userPropertyService.reasonsFieldBehavior: @unchecked) match { + import com.normation.rudder.config.ReasonBehavior.* + userPropertyService.reasonsFieldBehavior match { case Disabled => None case Mandatory => Some(buildReasonField(true, "px-1")) - case Optionnal => Some(buildReasonField(false, "px-1")) + case Optional => Some(buildReasonField(false, "px-1")) } } diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/CreateCloneGroupPopup.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/CreateCloneGroupPopup.scala index cb28598ecd9..b84ed29ee3c 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/CreateCloneGroupPopup.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/CreateCloneGroupPopup.scala @@ -208,11 +208,11 @@ class CreateCloneGroupPopup( ///////////// fields for category settings /////////////////// private val groupReasons = { - import com.normation.rudder.web.services.ReasonBehavior.* - (userPropertyService.reasonsFieldBehavior: @unchecked) match { + import com.normation.rudder.config.ReasonBehavior.* + userPropertyService.reasonsFieldBehavior match { case Disabled => None case Mandatory => Some(buildReasonField(true, "px-1")) - case Optionnal => Some(buildReasonField(false, "px-1")) + case Optional => Some(buildReasonField(false, "px-1")) } } diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/CreateOrUpdateGlobalParameterPopup.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/CreateOrUpdateGlobalParameterPopup.scala index 588e6af5c27..ba15e4a1ece 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/CreateOrUpdateGlobalParameterPopup.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/CreateOrUpdateGlobalParameterPopup.scala @@ -330,11 +330,11 @@ class CreateOrUpdateGlobalParameterPopup( val parameterOverridable = true private val paramReasons = { - import com.normation.rudder.web.services.ReasonBehavior.* - (userPropertyService.reasonsFieldBehavior: @unchecked) match { + import com.normation.rudder.config.ReasonBehavior.* + userPropertyService.reasonsFieldBehavior match { case Disabled => None case Mandatory => Some(buildReasonField(true, "px-1")) - case Optionnal => Some(buildReasonField(false, "px-1")) + case Optional => Some(buildReasonField(false, "px-1")) } } diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/CreateRulePopup.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/CreateRulePopup.scala index 3352bacb859..ed9d002df2e 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/CreateRulePopup.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/CreateRulePopup.scala @@ -133,11 +133,11 @@ class CreateOrCloneRulePopup( ///////////// fields for category settings /////////////////// private val reason = { - import com.normation.rudder.web.services.ReasonBehavior.* - (userPropertyService.reasonsFieldBehavior: @unchecked) match { + import com.normation.rudder.config.ReasonBehavior.* + userPropertyService.reasonsFieldBehavior match { case Disabled => None case Mandatory => Some(buildReasonField(true, "px-1")) - case Optionnal => Some(buildReasonField(false, "px-1")) + case Optional => Some(buildReasonField(false, "px-1")) } } diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/GiveReasonPopup.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/GiveReasonPopup.scala index 73fc4220cd3..75d2f972d56 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/GiveReasonPopup.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/GiveReasonPopup.scala @@ -41,6 +41,7 @@ import bootstrap.liftweb.RudderConfig import com.normation.box.* import com.normation.cfclerk.domain.TechniqueName import com.normation.eventlog.ModificationId +import com.normation.rudder.config.ReasonBehavior.* import com.normation.rudder.domain.policies.ActiveTechniqueCategoryId import com.normation.rudder.domain.policies.ActiveTechniqueId import com.normation.rudder.domain.policies.PolicyTypes @@ -48,7 +49,6 @@ import com.normation.rudder.users.CurrentUser import com.normation.rudder.web.ChooseTemplate import com.normation.rudder.web.model.FormTracker import com.normation.rudder.web.model.WBTextAreaField -import com.normation.rudder.web.services.ReasonBehavior.* import net.liftweb.common.* import net.liftweb.http.DispatchSnippet import net.liftweb.http.SHtml @@ -104,10 +104,10 @@ class GiveReasonPopup( ///////////// fields for category settings /////////////////// private val crReasons = { - (userPropertyService.reasonsFieldBehavior: @unchecked) match { + userPropertyService.reasonsFieldBehavior match { case Disabled => None case Mandatory => Some(buildReasonField(true, "px-1")) - case Optionnal => Some(buildReasonField(false, "px-1")) + case Optional => Some(buildReasonField(false, "px-1")) } } diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/ModificationValidationPopup.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/ModificationValidationPopup.scala index fd7e0f171b4..d93ece03f5b 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/ModificationValidationPopup.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/ModificationValidationPopup.scala @@ -258,11 +258,11 @@ class ModificationValidationPopup( // must be here because used in val popupWarningMessages private val crReasons = { - import com.normation.rudder.web.services.ReasonBehavior.* - (userPropertyService.reasonsFieldBehavior: @unchecked) match { + import com.normation.rudder.config.ReasonBehavior.* + userPropertyService.reasonsFieldBehavior match { case Disabled => None case Mandatory => Some(buildReasonField(true, "px-1")) - case Optionnal => Some(buildReasonField(false, "px-1")) + case Optional => Some(buildReasonField(false, "px-1")) } } diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/RuleModificationValidationPopup.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/RuleModificationValidationPopup.scala index cb0df939d55..1e36db75f0d 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/RuleModificationValidationPopup.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/components/popup/RuleModificationValidationPopup.scala @@ -168,11 +168,11 @@ class RuleModificationValidationPopup( ///////////// fields for category settings /////////////////// private val crReasons = { - import com.normation.rudder.web.services.ReasonBehavior.* - (userPropertyService.reasonsFieldBehavior: @unchecked) match { + import com.normation.rudder.config.ReasonBehavior.* + userPropertyService.reasonsFieldBehavior match { case Disabled => None case Mandatory => Some(buildReasonField(true, "px-1")) - case Optionnal => Some(buildReasonField(false, "px-1")) + case Optional => Some(buildReasonField(false, "px-1")) } } diff --git a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/administration/TechniqueLibraryManagement.scala b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/administration/TechniqueLibraryManagement.scala index e94060517ff..96f5d49d950 100644 --- a/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/administration/TechniqueLibraryManagement.scala +++ b/webapp/sources/rudder/rudder-web/src/main/scala/com/normation/rudder/web/snippet/administration/TechniqueLibraryManagement.scala @@ -42,6 +42,7 @@ import com.normation.box.* import com.normation.cfclerk.domain.* import com.normation.eventlog.ModificationId import com.normation.rudder.AuthorizationType +import com.normation.rudder.config.ReasonBehavior.* import com.normation.rudder.domain.eventlog.RudderEventActor import com.normation.rudder.domain.policies.* import com.normation.rudder.users.CurrentUser @@ -50,7 +51,6 @@ import com.normation.rudder.web.components.popup.CreateActiveTechniqueCategoryPo import com.normation.rudder.web.components.popup.GiveReasonPopup import com.normation.rudder.web.model.JsTreeNode import com.normation.rudder.web.services.AgentCompat -import com.normation.rudder.web.services.ReasonBehavior.* import net.liftweb.common.* import net.liftweb.common.Box.box2Option import net.liftweb.common.Box.option2Box