Skip to content

Commit

Permalink
Fixes #12808: Rudder 4.3 missing important features for plugins, espe…
Browse files Browse the repository at this point in the history
…cially ACL one
  • Loading branch information
fanf committed Jun 26, 2018
1 parent 9bf8649 commit ba41803
Show file tree
Hide file tree
Showing 14 changed files with 332 additions and 150 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,5 @@ final case class ModifyApiAccountDiff(
, modTokenGenerationDate : Option[SimpleDiff[DateTime]] = None
, modExpirationDate : Option[SimpleDiff[Option[DateTime]]] = None
, modAccountKind : Option[SimpleDiff[String]] = None
, modAccountAcl : Option[SimpleDiff[List[ApiAclElement]]] = None
) extends ApiAccountDiff with HashcodeCaching
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ final object AclPath {
final case class FullPath(segments: NonEmptyList[AclPathSegment]) extends AclPath {
def parts = segments
}
// only the root is given, and the path ends with "**". It can even be onlyl "**"
// only the root is given, and the path ends with "**". It can even be only "**"
final case class Root(segments: List[AclPathSegment]) extends AclPath {
def parts = NonEmptyList.ofInitLast(segments, AclPathSegment.DoubleWildcard)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,22 @@ class LDAPDiffMapper(
kind.kind.name
}
Full(diff.copy(modAccountKind = Some(SimpleDiff(oldAuthType, mod.getAttribute().getValue()))))
case A_API_ACL =>
val oldAcl = oldAccount.kind match {
case PublicApi(ApiAuthorization.ACL(acl), _) =>
acl
case kind =>
Nil
}

for {
acl <- mapper.unserApiAcl(mod.getAttribute().getValue) match {
case Left(error) => Failure(error)
case Right(acl) => Full(acl)
}
} yield {
diff.copy(modAccountAcl = Some(SimpleDiff(oldAcl, acl)))
}

case x => Failure("Unknown diff attribute: " + x)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -792,7 +792,7 @@ class LDAPEntityMapper(
))
write[JsonApiAcl](toSerialize)
}
def unserApiAuthz(s: String): Either[String, List[ApiAclElement]] = {
def unserApiAcl(s: String): Either[String, List[ApiAclElement]] = {
import cats.implicits._
import net.liftweb.json._
implicit val formats = net.liftweb.json.DefaultFormats
Expand Down Expand Up @@ -844,7 +844,7 @@ class LDAPEntityMapper(
case None =>
logger.debug(s"API authorizations level kind for token '${name.value}' with id '${id.value}' is 'ACL' but it doesn't have any ACLs conigured")
Full(ApiAuthorization.None) // for Rudder < 4.3, it should have been migrated. So here, we just don't gave any access.
case Some(s) => unserApiAuthz(s) match {
case Some(s) => unserApiAcl(s) match {
case Right(x) => Full(ApiAuthorization.ACL(x))
case Left(msg) => Failure(msg)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,12 @@ import net.liftweb.common.Box._
import net.liftweb.util.Helpers.tryo
import org.joda.time.format.ISODateTimeFormat
import org.eclipse.jgit.lib.PersonIdent

import com.normation.cfclerk.domain.TechniqueVersion
import com.normation.cfclerk.domain.TechniqueName
import com.normation.cfclerk.domain.TechniqueId
import com.normation.inventory.domain.NodeId
import com.normation.utils.Control.sequence
import com.normation.eventlog.ModificationId

import com.normation.rudder.api._
import com.normation.rudder.batch.CurrentDeploymentStatus
import com.normation.rudder.domain.policies._
Expand All @@ -71,6 +69,7 @@ import com.normation.rudder.services.queries.CmdbQueryParser
import com.normation.rudder.services.marshalling._
import com.normation.rudder.services.marshalling.TestFileFormat
import net.liftweb.json.JsonAST.JString
import org.joda.time.DateTime

/**
* A service that helps mapping event log details to there structured data model.
Expand Down Expand Up @@ -826,6 +825,8 @@ class EventLogDetailsServiceImpl(
}

def getApiAccountModifyDetails(xml:NodeSeq) : Box[ModifyApiAccountDiff] = {
import cats.implicits._

for {
entry <- getEntryContent(xml)
apiAccount <- (entry \ XML_TAG_API_ACCOUNT).headOption ?~!
Expand All @@ -835,7 +836,25 @@ class EventLogDetailsServiceImpl(
modToken <- getFromToString((apiAccount \ "token").headOption)
modDescription <- getFromToString((apiAccount \ "description").headOption)
modIsEnabled <- getFromTo[Boolean]((apiAccount \ "enabled").headOption,
{ s => tryo { s.text.toBoolean } } )
{ s => tryo { s.text.toBoolean } } )
modTokenGenDate <- getFromTo[DateTime]((apiAccount \ "tokenGenerationDate").headOption,
{ s => tryo { ISODateTimeFormat.dateTimeParser().parseDateTime(s.text) }} )
modExpirationDate <- getFromTo[Option[DateTime]]((apiAccount \ "expirationDate").headOption,
{ s => Full(tryo { ISODateTimeFormat.dateTimeParser().parseDateTime(s.text) }.toOption) } )
modAccountKind <- getFromToString((apiAccount \ "accountKind").headOption)
modAcls <- getFromTo[List[ApiAclElement]]((apiAccount \ "acls").headOption,
{ s => ((s \ "acl").toList.traverse{x =>
for {
path <- AclPath.parse((x \ "@path").head.text)
actions <- (x \ "@actions").head.text.split(",").toList.traverse(HttpAction.parse _)
} yield {
ApiAclElement(path, actions.toSet)
}
}) match {
case Left(e) => Failure(e)
case Right(x) => Full(x)
}
} )
fileFormatOk <- TestFileFormat(apiAccount)
} yield {
ModifyApiAccountDiff(
Expand All @@ -844,6 +863,10 @@ class EventLogDetailsServiceImpl(
, modToken = modToken
, modDescription = modDescription
, modIsEnabled = modIsEnabled
, modTokenGenerationDate = modTokenGenDate
, modExpirationDate = modExpirationDate
, modAccountKind = modAccountKind
, modAccountAcl = modAcls
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,7 @@ package com.normation.rudder.services.eventlog

import scala.xml._
import scala.xml.Text

import org.joda.time.DateTime

import com.normation.cfclerk.domain.SectionSpec
import com.normation.cfclerk.domain.TechniqueVersion
import com.normation.eventlog._
Expand All @@ -56,8 +54,8 @@ import com.normation.rudder.domain.policies._
import com.normation.rudder.domain.queries.Query
import com.normation.rudder.domain.workflows.WorkflowStepChange
import com.normation.rudder.services.marshalling._

import net.liftweb.util.Helpers._
import org.joda.time.format.ISODateTimeFormat

trait EventLogFactory {

Expand Down Expand Up @@ -780,7 +778,20 @@ class EventLogFactoryImpl(
diff.modName.map(x => SimpleDiff.stringToXml(<name/>, x) ) ++
diff.modToken.map(x => SimpleDiff.stringToXml(<token/>, x) ) ++
diff.modDescription.map(x => SimpleDiff.stringToXml(<description/>, x ) ) ++
diff.modIsEnabled.map(x => SimpleDiff.booleanToXml(<enabled/>, x ) )
diff.modIsEnabled.map(x => SimpleDiff.booleanToXml(<enabled/>, x ) ) ++
diff.modTokenGenerationDate.map(x => SimpleDiff.toXml[DateTime](<tokenGenerationDate/>, x){ x =>
Text(x.toString(ISODateTimeFormat.dateTime()))
})++
diff.modExpirationDate.map(x => SimpleDiff.toXml[Option[DateTime]](<expirationDate/>, x){ x =>
x match {
case None => NodeSeq.Empty
case Some(d) => Text(d.toString(ISODateTimeFormat.dateTime()))
}
}) ++
diff.modAccountKind.map(x => SimpleDiff.stringToXml(<accountKind/>, x)) ++
diff.modAccountAcl.map(x => SimpleDiff.toXml[List[ApiAclElement]](<acls/>, x){x =>
x.map(acl => <acl path={acl.path.value} actions={acl.actions.map(_.name).mkString(",")} />)
})
}
</apiAccount>)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -561,23 +561,34 @@ case class RestDataSerializerImpl (

}

/*
* ACL
* Between front and backend, we exchange a JsonAcl list, where JsonAcl are *just*
* one path and one verb. The grouping is done in extractor
*/
final case class JsonApiAcl(path: String, verb: String)

object ApiAccountSerialisation {

implicit val formats = Serialization.formats(NoTypeHints)


implicit class Json(account: ApiAccount) {

val (expirationDate, authzType, aclList) : (Option[String],Option[String],Option[List[String]]) = {
val (expirationDate, authzType, acl) : (Option[String],Option[String],Option[List[JsonApiAcl]]) = {
account.kind match {
case User | System => (None,None,None)
case PublicApiAccount(authz,expirationDate) =>
val aclList = authz match {
val acl = authz match {
case NoAccess | RO | RW => None
case ACL(acls) => Some(acls.map(_.path.value))
case ACL(acls) => Some(acls.flatMap(x => x.actions.map(a => JsonApiAcl(x.path.value, a.name))))
}
( expirationDate.map(DateFormaterService.getFormatedDate)
, Some(authz.kind.name)
, aclList )
, acl )
}
}

def toJson(): JObject = {
("id" -> account.id.value) ~
("name" -> account.name.value) ~
Expand All @@ -590,8 +601,7 @@ object ApiAccountSerialisation {
("expirationDate" -> expirationDate) ~
("expirationDateDefined" -> expirationDate.isDefined ) ~
("authorizationType" -> authzType ) ~
("aclList" -> aclList)
("acl" -> acl.map(x => Extraction.decompose(x)))
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -828,19 +828,16 @@ case class RestExtractorService (
}


def extractApiACLFromJSON (json : JValue) : Box[ApiAclElement] = {
/*
* The ACL list which is exchange between
*/
def extractApiACLFromJSON (json : JValue) : Box[(AclPath, HttpAction)] = {
import com.normation.rudder.utils.Utils.eitherToBox
def extractAPIVerb(json : JValue) : Box[HttpAction] = {
json match {
case JString(v) => HttpAction.parse(v)
case s => Failure(s"Not a valid value for an http verb, expected a String, got ${compactRender(s)}")
}
}
for {
path <- CompleteJson.extractJsonString(json, "path", AclPath.parse)
actions <- CompleteJson.extractJsonArray(json \ "verbs")(extractAPIVerb)
path <- CompleteJson.extractJsonString(json, "path", AclPath.parse)
action <- CompleteJson.extractJsonString(json, "verb", HttpAction.parse)
} yield {
ApiAclElement(path, actions.toSet)
(path, action)
}
}

Expand All @@ -856,7 +853,7 @@ case class RestExtractorService (
expirationValue <- extractJsonString(json, "expirationDate", DateFormaterService.parseDate)
authType <- extractJsonString(json, "authorizationType", ApiAuthorizationKind.parse)

aclList <- extractJsonArray(json \ "aclList")(
acl <- extractJsonArray(json \ "acl")(
// I need an option here, but this function does not need to return an option
// AndThern it to wrap the result in a option, then unwrap it with a getOrElse after it ran
(extractApiACLFromJSON _ ).andThen(_.map(Some(_)))
Expand All @@ -868,7 +865,13 @@ case class RestExtractorService (
case Some(ApiAuthorizationKind.None) => Some(ApiAuthz.None)
case Some(ApiAuthorizationKind.RO) => Some(ApiAuthz.RO)
case Some(ApiAuthorizationKind.RW) => Some(ApiAuthz.RW)
case Some(ApiAuthorizationKind.ACL) => Some(ApiAuthz.ACL(aclList))
case Some(ApiAuthorizationKind.ACL) => {
//group by path to get ApiAclElements
val acls = acl.groupBy(_._1).map { case (p, seq) =>
ApiAclElement(p, seq.map(_._2).toSet)
}.toList
Some(ApiAuthz.ACL(acls))
}
}
val expiration = expirationDefined match {
case None => None
Expand Down
59 changes: 57 additions & 2 deletions rudder-web/src/main/scala/bootstrap/liftweb/Boot.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,18 @@ import com.normation.rudder.AuthorizationType
import com.normation.rudder.domain.logger.ApplicationLogger
import com.normation.eventlog.ModificationId
import java.util.Locale

import net.liftweb.http.rest.RestHelper
import org.joda.time.DateTime
import com.normation.rudder.web.snippet.WithCachedResource
import java.net.URLConnection

import com.normation.plugins.PluginName
import com.normation.rudder.domain.logger.PluginLogger
import com.normation.rudder.rest.EndpointSchema
import com.normation.rudder.rest.{InfoApi => InfoApiDef}
import com.normation.rudder.rest.lift.InfoApi
import com.normation.rudder.rest.lift.LiftApiModuleProvider

/*
* Utilities about rights
Expand All @@ -73,6 +79,43 @@ object Boot {
}
}

/*
* Configuration about plugins. Information will only be available
* after full boot, and it's why that object is not in RudderConfig.
* You can't use information from here in RudderConfig services without
* a system of callback or something like that.
*
* Plugins are stored by their plugin-fullname.
*/
object PluginsInfo {

private[this] var _plugins = Map[PluginName, RudderPluginDef]()

def registerPlugin(plugin: RudderPluginDef) = {
_plugins = _plugins + (plugin.name -> plugin)
}

def plugins = _plugins

def pluginApisDef: List[EndpointSchema] = {

def recApi(apis: List[EndpointSchema], plugins: List[RudderPluginDef]): List[EndpointSchema] = {
plugins match {
case Nil => apis
case plugin :: tail => plugin.apis match {
case None => recApi(apis, tail)
case Some(x) => x.schemas match {
case s: EndpointSchema => recApi(s :: apis, tail)
case _ => recApi(apis, tail)
}
}
}
}
recApi(Nil, _plugins.values.toList)
}
}


////////// rewrites rules to remove the version from resources urls //////////
//////////
object StaticResourceRewrite extends RestHelper {
Expand All @@ -86,7 +129,7 @@ object StaticResourceRewrite extends RestHelper {
}

//the resource directory we want to server that way
val resources = Set("javascript", "style", "images")
val resources = Set("javascript", "style", "images", "toserve")
serve {
case Get(prefix :: resource :: tail, req) if(resources.contains(resource)) =>
val resourcePath = req.uri.replaceFirst(prefix+"/", "")
Expand Down Expand Up @@ -439,10 +482,22 @@ class Boot extends Loggable {
val pluginDefs = LiftSpringApplicationContext.springContext.getBeansOfType(classOf[RudderPluginDef]).values.asScala.toList

pluginDefs.foreach { plugin =>
ApplicationLogger.info("Initializing plugin '%s' [%s]".format(plugin.name.value, plugin.id))
PluginLogger.info(s"Initializing plugin '${plugin.name.value}': ${plugin.version}")

// resources in src/main/resources/toserve/${plugin short-name} must be allowed for each plugin
ResourceServer.allow{
case base :: _ if(base == plugin.shortName) => true
}
plugin.init

//add APIs
plugin.apis.foreach { (api:LiftApiModuleProvider[_]) =>
RudderConfig.rudderApi.addModules(api.getLiftEndpoints())
}

//add the plugin packages to Lift package to look for packages
LiftRules.addToPackages(plugin.basePackage)
PluginsInfo.registerPlugin(plugin)
}

pluginDefs
Expand Down
Loading

0 comments on commit ba41803

Please sign in to comment.