Skip to content

Commit

Permalink
Work in progress
Browse files Browse the repository at this point in the history
  • Loading branch information
VinceMacBuche committed Feb 16, 2017
1 parent 83e74bd commit 0dc531b
Show file tree
Hide file tree
Showing 14 changed files with 532 additions and 255 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ import com.normation.rudder.domain.policies.TagName
import com.normation.rudder.domain.policies.TagValue
import com.normation.rudder.repository.json.DataExtractor.CompleteJson
import scala.util.control.NonFatal
import net.liftweb.common.Loggable

/**
* Correctly quote a token
Expand All @@ -82,7 +83,7 @@ object QSPattern {
* of everything else.
*/

object QSDirectiveBackend {
object QSDirectiveBackend extends Loggable {
import com.normation.rudder.repository.FullActiveTechnique
import com.normation.rudder.domain.policies.Directive
import com.normation.rudder.domain.policies.DirectiveId
Expand Down Expand Up @@ -114,7 +115,34 @@ object QSDirectiveBackend {
if(!at.isSystem && !dir.isSystem)
attribute <- attributes
} yield {
attribute.find(at, dir, query.userToken)

val res = attribute.findAll(at, dir, query.userToken)
logger.info(res)
res
}).flatten.toSet
}
} else {
Full(Set())
}
}

def searchAll(query: Query)(implicit repo: RoDirectiveRepository): Box[Set[QuickSearchResult]] = {

// only search if query is on Directives and attributes contains
// DirectiveId, DirectiveVarName, DirectiveVarValue, TechniqueName, TechniqueVersion

val attributes: Set[QSAttribute] = query.attributes.intersect(QSObject.Directive.attributes)

if(query.objectClass.contains(QSDirective) && attributes.nonEmpty) {
for {
directiveLib <- repo.getFullDirectiveLibrary
} yield {
(for {
(at, dir) <- directiveLib.allDirectives.values
if(!at.isSystem && !dir.isSystem)
attribute <- attributes
} yield {
attribute.findAll(at, dir, query.userToken)
}).flatten.toSet
}
} else {
Expand All @@ -123,7 +151,8 @@ object QSDirectiveBackend {
}

implicit class QSAttributeFilter(a: QSAttribute) {
def find(at: FullActiveTechnique, dir: Directive, token: String): Option[QuickSearchResult] = {

private[this] def toMatch(at: FullActiveTechnique, dir: Directive): Option[Set[(String,String)]] = {

val enableToken = Set("true" , "enable" , "enabled" , "isenable" , "isenabled" )
val disableToken = Set("false", "disable", "disabled", "isdisable", "isdisabled")
Expand All @@ -135,7 +164,7 @@ object QSDirectiveBackend {
/*
* A set of value to check against / value to return to the user
*/
val toMatch: Option[Set[(String,String)]] = a match {
a match {
case QSDirectiveId => Some(Set((dir.id.value,dir.id.value)))
case DirectiveVarName => Some(dir.parameters.flatMap(param => param._2.map(value => (param._1, param._1+":"+ value))).toSet)
case DirectiveVarValue => Some(dir.parameters.flatMap(param => param._2.map(value => (value, param._1+":"+ value))).toSet)
Expand All @@ -147,6 +176,8 @@ object QSDirectiveBackend {
case Name => Some(Set((dir.name,dir.name)))
case IsEnabled => Some(isEnabled)
case Tags => Some(dir.tags.map{ case Tag(TagName(k),TagValue(v)) => (s"$k=$v", s"$k=$v") }.toSet )
case TagKeys => Some(dir.tags.map{ case Tag(TagName(k),_) => (k, k) }.toSet )
case TagValues => Some(dir.tags.map{ case Tag(_,TagValue(v)) => (v, v) }.toSet )
case NodeId => None
case Fqdn => None
case OsType => None
Expand All @@ -169,15 +200,30 @@ object QSDirectiveBackend {
case DirectiveIds => None
case Targets => None
}
}

def find(at: FullActiveTechnique, dir: Directive, token: String): Option[QuickSearchResult] = {
toMatch(at,dir).flatMap { set => set.find{case (s,value) =>logger.warn(s); logger.warn(value); logger.warn(token); QSPattern(token).matcher(s).matches } }.map{ case (_, value) =>
QuickSearchResult(
QRDirectiveId(dir.id.value)
, dir.name
, Some(a)
, value
)
}
}

toMatch.flatMap { set => set.find{case (s,value) => QSPattern(token).matcher(s).matches } }.map{ case (_, value) =>
QuickSearchResult(
def findAll(at: FullActiveTechnique, dir: Directive, token: String): Set[QuickSearchResult] = {
(toMatch(at,dir).map { set => set.collect{
case (s,value) if QSPattern(token).matcher(s).matches =>
logger.warn(s); logger.warn(value); logger.warn(token);
QuickSearchResult(
QRDirectiveId(dir.id.value)
, dir.name
, Some(a)
, value
)
}
}}).getOrElse(Set())
}
}

Expand Down Expand Up @@ -231,7 +277,7 @@ object QSLdapBackend {

// transformat LDAPEntries to quicksearch results, keeping only the attribute
// that matches the query on the result and no system entries but nodes.
(others ++ merged).flatMap( _.toResult(query.userToken))
(others ++ merged).flatMap( _.toResult(query))
}
}
}
Expand Down Expand Up @@ -273,6 +319,8 @@ object QSLdapBackend {
, DirectiveIds -> A_DIRECTIVE_UUID
, Targets -> A_RULE_TARGET
, Tags -> A_SERIALIZED_TAGS
, TagKeys -> A_SERIALIZED_TAGS
, TagValues -> A_SERIALIZED_TAGS
)

if(m.size != QSAttribute.all.size) {
Expand Down Expand Up @@ -385,6 +433,8 @@ object QSLdapBackend {
case DirectiveIds => sub(a, token)
case Targets => sub(a, token)
case Tags => sub(a, token)
case TagKeys => sub(a, token)
case TagValues => sub(a, token)
}
}
}
Expand All @@ -394,32 +444,44 @@ object QSLdapBackend {
* an attribute.
* The transformation may fail (option = none).
*/
implicit class LdapAttributeValueTransform(attrName: String) {
implicit class LdapAttributeValueTransform( a: QSAttribute) {
import QSAttributeLdapFilter._

def transform(pattern: Pattern, value: String): Option[String] = {
attrName match {
case A_SERIALIZED_TAGS =>
import net.liftweb.json.parse

def parseTag(value : String, matcher : Tag => Boolean, transform :Tag => String )= {
import net.liftweb.json.parse
try {
val json = parse(value)
CompleteJson.extractTags(json) match {
case Full(tags) => tags.tags.find { t =>
pattern.matcher(t.tagName.name).matches || pattern.matcher(t.tagValue.value).matches
}.map( t => s"${t.tagName.name}=${t.tagValue.value}")

case Full(tags) => tags.tags.collectFirst { case t if matcher(t) => transform(t) }
case _ => None
}
} catch {
case NonFatal(ex) => None
}
}

a match {
case Tags =>
def matcher(t : Tag) = pattern.matcher(t.tagName.name).matches || pattern.matcher(t.tagValue.value).matches
def transform(tag : Tag) = s"${tag.tagName.name}=${tag.tagValue.value}"
parseTag(value,matcher,transform)
case TagKeys =>
def matcher(t : Tag) = pattern.matcher(t.tagName.name).matches
def transform(tag : Tag) = tag.tagName.name
parseTag(value,matcher,transform)
case TagValues =>
def matcher(t : Tag) = pattern.matcher(t.tagValue.value).matches
def transform(tag : Tag) = tag.tagValue.value
parseTag(value,matcher,transform)

//main case: no more transformation
case _ => Some(value)
}
}
}


/**
* Build LDAP filter for a QSObject
*/
Expand All @@ -443,7 +505,7 @@ object QSLdapBackend {
import QuickSearchResultId._
import QSAttributeLdapFilter._

def toResult(token: String): Option[QuickSearchResult] = {
def toResult(query : Query): Option[QuickSearchResult] = {
def getId(e: LDAPEntry): Option[QuickSearchResultId] = {
if (e.isA(OC_NODE )) { e(A_NODE_UUID ).map( QRNodeId )
} else if(e.isA(OC_RUDDER_NODE )) { e(A_NODE_UUID ).map( QRNodeId )
Expand All @@ -455,20 +517,22 @@ object QSLdapBackend {
}
}

val pattern = QSPattern(query.userToken)

/*
* Depending of the attribute type, we must use a different
* match methods.
* Returns Option[(attribute name, matching value)]
*/
def matchValue(a: Attribute, token: String, pattern: Pattern): Option[(String, String)] = {
def matchValue(a: Attribute): Option[(String, String)] = {

//test a matcher against values
def findValue(values: Array[String], test: String => Boolean): Option[(String, String)] = {
values.find(test).map(v => (a.getName, v))
}
//test a list of pattern matcher against values
def testAndFindValue(testers: List[String => Boolean]): Option[(String, String)] = {
testers.find(f => f(token)).flatMap(findValue(a.getValues, _))
testers.find(f => f(query.userToken)).flatMap(findValue(a.getValues, _))
}

if(a.getName == A_OC) {
Expand Down Expand Up @@ -500,24 +564,25 @@ object QSLdapBackend {
// get the attribute value matching patterns
// if several, take only one at random. If none, that's strange, reject entry
// also, don't look in objectClass to find the pattern
val pattern = QSPattern(token)
for {
(attr, desc) <- (Option.empty[(String, String)] /: e.attributes) { (current, next) => (current, next ) match {
(attr, desc) <- (Option.empty[(QSAttribute, String)] /: e.attributes) { (current, next) => (current, next ) match {
case (Some(x), _) => Some(x)
case (None , a) =>
for {
(attr, value) <- matchValue(a, token, pattern)
trans <- attr.transform(pattern, value)
(attr, value) <- matchValue(a)

attribute <- query.attributes.find { attributeNameMapping(_) == attr }
trans <- attribute.transform(pattern, value)
} yield {
(attr, trans)
(attribute, trans)
}
} }
id <- getId(e)
if(isNodeOrNotSystem(e))
} yield {
//prefer hostname for nodes
val name = e(A_HOSTNAME).orElse(e(A_NAME)).getOrElse(id.value)
QuickSearchResult(id, name, ldapNameMapping.get(attr), desc)
QuickSearchResult(id, name,Some(attr) , desc)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,12 @@ object QSAttribute {
final case object Tags extends QSAttribute {
override val name = "tags"
}
final case object TagKeys extends QSAttribute {
override val name = "tagKeys"
}
final case object TagValues extends QSAttribute {
override val name = "tagValues"
}

//Parameters
final case object ParameterName extends QSAttribute {
Expand Down Expand Up @@ -198,6 +204,8 @@ sealed trait QSObject { def name: String; def attributes: Set[QSAttribute] }
object QSObject {
import QSAttribute._

val tagsAttribute = Set(Tags, TagKeys, TagValues)

final case object Common extends QSObject { override val name = "common"
override val attributes : Set[QSAttribute] = Set(Name, Description, LongDescription, IsEnabled)
}
Expand All @@ -211,14 +219,14 @@ object QSObject {
override val attributes : Set[QSAttribute] = Common.attributes ++ Set(GroupId, IsDynamic)
}
final case object Directive extends QSObject { override val name = "directive"
override val attributes : Set[QSAttribute] = Common.attributes ++ Set(DirectiveId, DirectiveVarName
, DirectiveVarValue, TechniqueName, TechniqueId, TechniqueVersion, Tags)
override val attributes : Set[QSAttribute] = Common.attributes ++ tagsAttribute ++ Set(DirectiveId, DirectiveVarName
, DirectiveVarValue, TechniqueName, TechniqueId, TechniqueVersion)
}
final case object Parameter extends QSObject { override val name = "parameter"
override val attributes : Set[QSAttribute] = Common.attributes ++ Set(ParameterName, ParameterValue)
}
final case object Rule extends QSObject { override val name = "rule"
override val attributes : Set[QSAttribute] = Common.attributes ++ Set(RuleId, DirectiveIds, Targets, Tags)
override val attributes : Set[QSAttribute] = Common.attributes ++ tagsAttribute ++ Set(RuleId, DirectiveIds, Targets)
}

final val all: Set[QSObject] = sealerate.values[QSObject]
Expand Down Expand Up @@ -297,6 +305,7 @@ final object QSMapping {
case DirectiveIds => (a, Set(DirectiveIds.name, "directiveids", "id", "ids") )
case Targets => (a, Set(Targets.name, "target", "group", "groups") )
case Tags => (a, Set(Tags.name, "tag") )
case default => (a, Set(default.name) )
} }
}.toMap

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@

package com.normation.rudder.services.quicksearch


import scala.util.parsing.combinator.RegexParsers
import net.liftweb.common.{ Failure => FailedBox, _ }
import com.normation.utils.Control.sequence
Expand Down Expand Up @@ -70,7 +69,7 @@ object QSRegexQueryParser extends RegexParsers {
*/
def parse(value: String): Box[Query] = {
if(value.trim.isEmpty()) {
FailedBox("You can search with an empty or whitespace only query")
FailedBox("You can't search with an empty or whitespace only query")
} else {
parseAll(all, value) match {
case NoSuccess(msg , remaining) => FailedBox(s"""Error when parsing query "${value}", error message is: ${msg}""")
Expand All @@ -86,9 +85,33 @@ object QSRegexQueryParser extends RegexParsers {
def interprete(parsed: (QueryString, List[Filter])): Box[Query] = {

parsed match {
case (EmptyQuery, _) =>
FailedBox("No query string was found (the query is only composed of whitespaces and filters)")
case (EmptyQuery, filters) =>
// get all keys, and sort them between objets/attributes/ERRORS
val names = filters.map( _.keys).flatten.toSet

val is = filters.collect { case FilterType(set) => set }.flatten
val in = filters.collect { case FilterAttr(set) => set }.flatten

(for {
(objs , oKeys) <- getObjects(is.toSet) ?~! "Check 'is' filters"
(attrs, aKeys) <- getAttributes(in.toSet) ?~! "Check 'in' filters"
} yield {
/*
* we need to add all attributes and objects if
* sets are empty - the user just didn't provided any filters.
*/
Query(
""
, if( objs.isEmpty) { QSObject.all } else { objs }
, if(attrs.isEmpty) { QSAttribute.all } else { attrs }
)
}) ?~! {
val allNames = (
QSMapping.objectNameMapping.keys.map( _.capitalize)
++ QSMapping.attributeNameMapping.keys
).toSeq.sorted.mkString("', '")
s"Query containts unknown filter. Please choose among '${allNames}'"
}
case (CharSeq(query), filters) =>
// get all keys, and sort them between objets/attributes/ERRORS
val names = filters.map( _.keys).flatten.toSet
Expand Down Expand Up @@ -149,7 +172,6 @@ object QSRegexQueryParser extends RegexParsers {

//// does not work on empty/whitespace only string, forbid them before ////


/*
* We want to parse a string that:
* - starts or/and ends with 0 or more filter
Expand Down Expand Up @@ -200,7 +222,6 @@ object QSRegexQueryParser extends RegexParsers {
private[this] def queryInMiddle : Parser[QueryString] = """(?iums)(.+?(?=((in:)|(is:))))""".r ^^ { x => CharSeq(x.trim) }
private[this] def queryAtEnd : Parser[QueryString] = """(?iums)(.+)""".r ^^ { x => CharSeq(x.trim) }


/////
///// utility methods
/////
Expand Down Expand Up @@ -269,4 +290,3 @@ object QSRegexQueryParser extends RegexParsers {
}
}
}

Loading

0 comments on commit 0dc531b

Please sign in to comment.