From c3fd73e4d958569e176816e88753a967892f6aa4 Mon Sep 17 00:00:00 2001 From: "Francois @fanf42 Armand" Date: Thu, 21 Jun 2018 17:22:44 +0200 Subject: [PATCH] Fixes #12808: Rudder API for ACL is buggy --- .../normation/rudder/api/ApiAccountDiff.scala | 1 + .../normation/rudder/api/DataStructures.scala | 2 +- .../repository/ldap/LDAPDiffMapper.scala | 15 ++ .../repository/ldap/LDAPEntityMapper.scala | 4 +- .../eventlog/EventLogDetailsService.scala | 29 ++- .../services/eventlog/EventLogFactory.scala | 19 +- .../rudder/hooks/RunNuCommandTest.scala | 2 +- .../rudder/rest/RestDataSerializer.scala | 18 +- .../rudder/rest/RestExtractorService.scala | 25 +- .../web/services/EventListDisplayer.scala | 47 +++- .../javascript/rudder/angular/apiAccount.js | 216 +++++++++++------- 11 files changed, 261 insertions(+), 117 deletions(-) diff --git a/rudder-core/src/main/scala/com/normation/rudder/api/ApiAccountDiff.scala b/rudder-core/src/main/scala/com/normation/rudder/api/ApiAccountDiff.scala index 049884c8789..2a0d1de9524 100644 --- a/rudder-core/src/main/scala/com/normation/rudder/api/ApiAccountDiff.scala +++ b/rudder-core/src/main/scala/com/normation/rudder/api/ApiAccountDiff.scala @@ -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 diff --git a/rudder-core/src/main/scala/com/normation/rudder/api/DataStructures.scala b/rudder-core/src/main/scala/com/normation/rudder/api/DataStructures.scala index 79dcf723e84..70492afb7bd 100644 --- a/rudder-core/src/main/scala/com/normation/rudder/api/DataStructures.scala +++ b/rudder-core/src/main/scala/com/normation/rudder/api/DataStructures.scala @@ -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) } diff --git a/rudder-core/src/main/scala/com/normation/rudder/repository/ldap/LDAPDiffMapper.scala b/rudder-core/src/main/scala/com/normation/rudder/repository/ldap/LDAPDiffMapper.scala index d94a7afd3b3..4921dfac791 100644 --- a/rudder-core/src/main/scala/com/normation/rudder/repository/ldap/LDAPDiffMapper.scala +++ b/rudder-core/src/main/scala/com/normation/rudder/repository/ldap/LDAPDiffMapper.scala @@ -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) } diff --git a/rudder-core/src/main/scala/com/normation/rudder/repository/ldap/LDAPEntityMapper.scala b/rudder-core/src/main/scala/com/normation/rudder/repository/ldap/LDAPEntityMapper.scala index ea31b4187dc..b75bf76f84c 100644 --- a/rudder-core/src/main/scala/com/normation/rudder/repository/ldap/LDAPEntityMapper.scala +++ b/rudder-core/src/main/scala/com/normation/rudder/repository/ldap/LDAPEntityMapper.scala @@ -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 @@ -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) } diff --git a/rudder-core/src/main/scala/com/normation/rudder/services/eventlog/EventLogDetailsService.scala b/rudder-core/src/main/scala/com/normation/rudder/services/eventlog/EventLogDetailsService.scala index cad02efa194..8f4689f666c 100644 --- a/rudder-core/src/main/scala/com/normation/rudder/services/eventlog/EventLogDetailsService.scala +++ b/rudder-core/src/main/scala/com/normation/rudder/services/eventlog/EventLogDetailsService.scala @@ -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._ @@ -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. @@ -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 ?~! @@ -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( @@ -844,6 +863,10 @@ class EventLogDetailsServiceImpl( , modToken = modToken , modDescription = modDescription , modIsEnabled = modIsEnabled + , modTokenGenerationDate = modTokenGenDate + , modExpirationDate = modExpirationDate + , modAccountKind = modAccountKind + , modAccountAcl = modAcls ) } } diff --git a/rudder-core/src/main/scala/com/normation/rudder/services/eventlog/EventLogFactory.scala b/rudder-core/src/main/scala/com/normation/rudder/services/eventlog/EventLogFactory.scala index ce66dd49f59..e213738a543 100644 --- a/rudder-core/src/main/scala/com/normation/rudder/services/eventlog/EventLogFactory.scala +++ b/rudder-core/src/main/scala/com/normation/rudder/services/eventlog/EventLogFactory.scala @@ -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._ @@ -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 { @@ -780,7 +778,20 @@ class EventLogFactoryImpl( diff.modName.map(x => SimpleDiff.stringToXml(, x) ) ++ diff.modToken.map(x => SimpleDiff.stringToXml(, x) ) ++ diff.modDescription.map(x => SimpleDiff.stringToXml(, x ) ) ++ - diff.modIsEnabled.map(x => SimpleDiff.booleanToXml(, x ) ) + diff.modIsEnabled.map(x => SimpleDiff.booleanToXml(, x ) ) ++ + diff.modTokenGenerationDate.map(x => SimpleDiff.toXml[DateTime](, x){ x => + Text(x.toString(ISODateTimeFormat.dateTime())) + })++ + diff.modExpirationDate.map(x => SimpleDiff.toXml[Option[DateTime]](, x){ x => + x match { + case None => NodeSeq.Empty + case Some(d) => Text(d.toString(ISODateTimeFormat.dateTime())) + } + }) ++ + diff.modAccountKind.map(x => SimpleDiff.stringToXml(, x)) ++ + diff.modAccountAcl.map(x => SimpleDiff.toXml[List[ApiAclElement]](, x){x => + x.map(acl => ) + }) } ) } diff --git a/rudder-core/src/test/scala/com/normation/rudder/hooks/RunNuCommandTest.scala b/rudder-core/src/test/scala/com/normation/rudder/hooks/RunNuCommandTest.scala index ecae1bf4262..69f3bc6907a 100644 --- a/rudder-core/src/test/scala/com/normation/rudder/hooks/RunNuCommandTest.scala +++ b/rudder-core/src/test/scala/com/normation/rudder/hooks/RunNuCommandTest.scala @@ -50,7 +50,7 @@ import scala.collection.JavaConverters._ */ @RunWith(classOf[JUnitRunner]) -case class RunNuCommandTest(implicit ee: ExecutionEnv) extends Specification { +case class RunNuCommandTest()(implicit ee: ExecutionEnv) extends Specification { "A command" should { diff --git a/rudder-rest/src/main/scala/com/normation/rudder/rest/RestDataSerializer.scala b/rudder-rest/src/main/scala/com/normation/rudder/rest/RestDataSerializer.scala index 7d6a4c6683a..64cd09fd564 100644 --- a/rudder-rest/src/main/scala/com/normation/rudder/rest/RestDataSerializer.scala +++ b/rudder-rest/src/main/scala/com/normation/rudder/rest/RestDataSerializer.scala @@ -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, aclList) : (Option[String],Option[String],Option[List[JsonApiAcl]]) = { account.kind match { case User | System => (None,None,None) case PublicApiAccount(authz,expirationDate) => val aclList = 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 ) } } + def toJson(): JObject = { ("id" -> account.id.value) ~ ("name" -> account.name.value) ~ @@ -590,8 +601,7 @@ object ApiAccountSerialisation { ("expirationDate" -> expirationDate) ~ ("expirationDateDefined" -> expirationDate.isDefined ) ~ ("authorizationType" -> authzType ) ~ - ("aclList" -> aclList) + ("aclList" -> aclList.map(x => Extraction.decompose(x))) } - } } diff --git a/rudder-rest/src/main/scala/com/normation/rudder/rest/RestExtractorService.scala b/rudder-rest/src/main/scala/com/normation/rudder/rest/RestExtractorService.scala index 549cf1698dc..3fccd072da7 100644 --- a/rudder-rest/src/main/scala/com/normation/rudder/rest/RestExtractorService.scala +++ b/rudder-rest/src/main/scala/com/normation/rudder/rest/RestExtractorService.scala @@ -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) } } @@ -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 = aclList.groupBy(_._1).map { case (p, seq) => + ApiAclElement(p, seq.map(_._2).toSet) + }.toList + Some(ApiAuthz.ACL(acls)) + } } val expiration = expirationDefined match { case None => None diff --git a/rudder-web/src/main/scala/com/normation/rudder/web/services/EventListDisplayer.scala b/rudder-web/src/main/scala/com/normation/rudder/web/services/EventListDisplayer.scala index 9623863a95b..8124a45f511 100644 --- a/rudder-web/src/main/scala/com/normation/rudder/web/services/EventListDisplayer.scala +++ b/rudder-web/src/main/scala/com/normation/rudder/web/services/EventListDisplayer.scala @@ -64,6 +64,7 @@ import org.eclipse.jgit.lib.PersonIdent import org.joda.time.DateTime import com.normation.rudder.services.eventlog.RollbackInfo import com.normation.rudder.domain.workflows.ChangeRequestId + import scala.util.Try import scala.util.Success import scala.util.{Failure => Catch} @@ -80,6 +81,7 @@ import com.normation.rudder.rule.category.RuleCategory import com.normation.rudder.rule.category.RoRuleCategoryRepository import org.joda.time.format.DateTimeFormat import com.normation.rudder.web.model.LinkUtil +import org.joda.time.format.ISODateTimeFormat /** * Used to display the event list, in the pending modification (AsyncDeployment), @@ -1027,6 +1029,8 @@ class EventListDisplayer( case mod:ModifyAPIAccountEventLog => "*" #> { logDetailsService.getApiAccountModifyDetails(mod.details) match { case Full(apiAccountDiff) => + + println(("**** " + apiAccountDiff))
{ addRestoreAction } { generatedByChangeRequest } @@ -1038,7 +1042,16 @@ class EventListDisplayer( "#name" #> mapSimpleDiff(apiAccountDiff.modName) & "#token" #> mapSimpleDiff(apiAccountDiff.modToken) & "#description *" #> mapSimpleDiff(apiAccountDiff.modDescription) & - "#isEnabled *" #> mapSimpleDiff(apiAccountDiff.modIsEnabled) + "#isEnabled *" #> mapSimpleDiff(apiAccountDiff.modIsEnabled) & + "#tokenGenerationDate *" #> mapSimpleDiff(apiAccountDiff.modTokenGenerationDate) & + "#expirationDate *" #> mapSimpleDiffT[Option[DateTime]](apiAccountDiff.modExpirationDate, _.fold("")(_.toString(ISODateTimeFormat.dateTime())) + ) & + "#accountKind *" #> mapSimpleDiff(apiAccountDiff.modAccountKind) & + //make list of ACL unsderstandable + "#acls *" #> mapSimpleDiff(apiAccountDiff.modAccountAcl.map(o => { + val f = (l: List[ApiAclElement]) => l.sortBy(_.path.value).map(x => s"[${x.actions.map(_.name.toUpperCase()).mkString(",")}] ${x.path.value}").mkString(" | ") + SimpleDiff(f(o.oldValue), f(o.newValue)) + })) )(apiAccountModDetailsXML) } { reasonHtml } @@ -1319,11 +1332,14 @@ class EventListDisplayer( "#tokenGenerationDate" #> DateFormaterService.getFormatedDate(apiAccount.tokenGenerationDate) )(xml) - private[this] def mapSimpleDiff[T](opt:Option[SimpleDiff[T]]) = opt.map { diff => - ".diffOldValue *" #> diff.oldValue.toString & - ".diffNewValue *" #> diff.newValue.toString + + private[this] def mapSimpleDiffT[T](opt:Option[SimpleDiff[T]], t: T => String) = opt.map { diff => + ".diffOldValue *" #> t(diff.oldValue) & + ".diffNewValue *" #> t(diff.newValue) } + private[this] def mapSimpleDiff[T](opt:Option[SimpleDiff[T]]) = mapSimpleDiffT(opt, (x:T) => x.toString) + private[this] def mapSimpleDiff[T](opt:Option[SimpleDiff[T]], id: DirectiveId) = opt.map { diff => ".diffOldValue *" #> diff.oldValue.toString & ".diffNewValue *" #> diff.newValue.toString & @@ -1439,9 +1455,25 @@ class EventListDisplayer(
  • Enabled: 
  • Creation date: 
  • Token Generation date: 
  • +
  • Token Expiration date: 
  • +
  • Account Kind: 
  • +
  • ACLs: 
  • + private[this] val apiAccountModDetailsXML = + + {liModDetailsXML("name", "Name")} + {liModDetailsXML("token", "Token")} + {liModDetailsXML("description", "Description")} + {liModDetailsXML("isEnabled", "Enabled")} + {liModDetailsXML("tokenGenerationDate", "Token Generation Date")} + {liModDetailsXML("expirationDate", "Token Expiration Date")} + {liModDetailsXML("accountKind", "Account Kind")} + {liModDetailsXML("acls", "ACL list")} + + + private[this] def liModDetailsXML(id:String, name:String) = (
    {name} changed: @@ -1504,13 +1536,6 @@ class EventListDisplayer( {liModDetailsXML("overridable", "Overridable")} - private[this] val apiAccountModDetailsXML = - - {liModDetailsXML("name", "Name")} - {liModDetailsXML("token", "Token")} - {liModDetailsXML("description", "Description")} - {liModDetailsXML("isEnabled", "Enabled")} - private[this] def displayRollbackDetails(rollbackInfo:RollbackInfo,id:Int) = { val rollbackedEvents = rollbackInfo.rollbacked diff --git a/rudder-web/src/main/webapp/javascript/rudder/angular/apiAccount.js b/rudder-web/src/main/webapp/javascript/rudder/angular/apiAccount.js index 150683b3a1b..4f3aa157d0d 100644 --- a/rudder-web/src/main/webapp/javascript/rudder/angular/apiAccount.js +++ b/rudder-web/src/main/webapp/javascript/rudder/angular/apiAccount.js @@ -4,12 +4,12 @@ ************************************************************************************* * * This file is part of Rudder. -* +* * Rudder is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. -* +* * In accordance with the terms of section 7 (7. Additional Terms.) of * the GNU General Public License version 3, the copyright holders add * the following Additional permissions: @@ -22,12 +22,12 @@ * documentation that, without modification of the Source Code, enables * supplementary functions or services in addition to those offered by * the Software. -* +* * Rudder is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. -* +* * You should have received a copy of the GNU General Public License * along with Rudder. If not, see . @@ -54,11 +54,22 @@ accountManagement.directive('validEmpty', function() { } }; } ); - + +/* + * This module expects the following JS variables to be defined: + * - apiPath: context path to use + * - rudderApis: an object with the description of all Rudder apis // only non null if the API authz plugin is present + */ accountManagement.controller('AccountCtrl', function ($scope, $http, DTOptionsBuilder, DTColumnDefBuilder) { $scope.aclPlugin = false; $scope.authFilter = undefined; + // all rudder defined apis + $scope.rudderApis = rudderApis; + + // currently updated api account + $scope.myNewAccount = undefined; + $scope.getAccounts = function() { $http.get(apiPath). then(function (response) { @@ -102,64 +113,65 @@ accountManagement.controller('AccountCtrl', function ($scope, $http, DTOptionsBu return value.authorizationType === $scope.authFilter; } } + $scope.addAccount = function() { - var now = new Date(); - now.setMonth(now.getMonth()+1); - var now = now.getFullYear()+"-"+(now.getMonth()+1)+"-"+now.getDate()+" "+now.getHours()+":"+now.getMinutes(); - var newAccount = { id : "", name : "", token: undefined, enabled : true, description : "", authorizationType : "rw",expirationDateDefined : true, expirationDate : now} - $scope.myNewAccount = newAccount; - $('#newAccountPopup').bsModal('show'); -} - - -$scope.options = - DTOptionsBuilder.newOptions(). - withPaginationType('full_numbers'). - withDOM('<"dataTables_wrapper_top newFilter"f>t<"dataTables_wrapper_bottom"lip>'). - withLanguage({ - "searchPlaceholder": 'Filter', - "search": '' - }). - withOption("bLengthChange", true). - withOption( "lengthMenu", [ [10, 25, 50, 100, 500, 1000, -1], [10, 25, 50, 100, 500, 1000, "All"] ]). - withOption("pageLength", 25). - withOption("jQueryUI", true). - withOption("bAutoWidth", false) + var now = new Date(); + now.setMonth(now.getMonth()+1); + var now = now.getFullYear()+"-"+(now.getMonth()+1)+"-"+now.getDate()+" "+now.getHours()+":"+now.getMinutes(); + var newAccount = { id : "", name : "", token: undefined, enabled : true, description : "", authorizationType : "rw",expirationDateDefined : true, expirationDate : now} + $scope.myNewAccount = newAccount; + $('#newAccountPopup').bsModal('show'); + } + + + $scope.options = DTOptionsBuilder.newOptions(). + withPaginationType('full_numbers'). + withDOM('<"dataTables_wrapper_top newFilter"f>t<"dataTables_wrapper_bottom"lip>'). + withLanguage({ + "searchPlaceholder": 'Filter', + "search": '' + }). + withOption("bLengthChange", true). + withOption("lengthMenu", [ [10, 25, 50, 100, 500, 1000, -1], [10, 25, 50, 100, 500, 1000, "All"] ]). + withOption("pageLength", 25). + withOption("jQueryUI", true). + withOption("bAutoWidth", false) $scope.columns = [ - DTColumnDefBuilder.newColumnDef(0).withOption("sWidth",'20%'), - DTColumnDefBuilder.newColumnDef(1).withOption("sWidth",'30%'), - DTColumnDefBuilder.newColumnDef(2).withOption("sWidth",'30%'), - DTColumnDefBuilder.newColumnDef(3).withOption("sWidth",'20%').notSortable() - ]; -$scope.popupCreation = function(account,index) { - $scope.myNewAccount = angular.copy(account); - $scope.myNewAccount.index = index; - $scope.myNewAccount.oldName = account.name; - $("#newAccountName").focus(); - $('#newAccountPopup').bsModal('show'); - - return account; -}; - -$scope.popupDeletion = function(account, action, actionName) { + DTColumnDefBuilder.newColumnDef(0).withOption("sWidth",'20%'), + DTColumnDefBuilder.newColumnDef(1).withOption("sWidth",'30%'), + DTColumnDefBuilder.newColumnDef(2).withOption("sWidth",'30%'), + DTColumnDefBuilder.newColumnDef(3).withOption("sWidth",'20%').notSortable() + ]; + + $scope.popupCreation = function(account,index) { + $scope.myNewAccount = angular.copy(account); + $scope.myNewAccount.index = index; + $scope.myNewAccount.oldName = account.name; + $("#newAccountName").focus(); + $('#newAccountPopup').bsModal('show'); + return account; + }; + + $scope.popupDeletion = function(account, action, actionName) { $scope.myOldAccount = account; $scope.myOldAccount.action = function(a) { return action(account); }; $scope.myOldAccount.actionName = actionName; $('#oldAccountPopup').bsModal('show'); - return account; -}; + return account; + }; - $scope.closePopup = function () { + $scope.closePopup = function () { $scope.myNewAccount = undefined; $scope.errorPopup = undefined; - } + } $scope.checkAndSaveAccount = function (account,index,form) { if (form.$valid) { $scope.saveAccount(account,index,true); } } + $scope.saveAccount = function(account,isPopup) { if (isPopup) { $scope.errorPopup = undefined; @@ -168,44 +180,48 @@ $scope.popupDeletion = function(account, action, actionName) { } if(account.token === undefined ) { $http.put(apiPath,account). - success( function(data, status, headers, config) { - var newAccount = data.data.accounts[0]; - var newLength = $scope.accounts.push(newAccount); - newAccount.index = newLength - 1 ; - $scope.myNewAccount = undefined; - $('#newAccountPopup').bsModal('hide'); - }). - error(function(data, status, headers, config) { - if (isPopup) { - $scope.errorPopup = data; - } else { - $scope.errorTable = data; - } - }); + success( function(data, status, headers, config) { + var newAccount = data.data.accounts[0]; + var newLength = $scope.accounts.push(newAccount); + newAccount.index = newLength - 1 ; + $scope.myNewAccount = undefined; + $('#newAccountPopup').bsModal('hide'); + }). + error(function(data, status, headers, config) { + if (isPopup) { + $scope.errorPopup = data; + } else { + $scope.errorTable = data; + } + }); } else { $http.post(apiPath + '/'+account.token,account). - success(function(data, status, headers, config) { - var newAccount = data.data.accounts[0]; - $scope.accounts[$scope.myNewAccount.index] = newAccount - //$.extend($scope.myNewAccount, newAccount); - $scope.myNewAccount = undefined; - $('#newAccountPopup').bsModal('hide'); - - }). - error(function(data, status, headers, config) { - if (isPopup) { - $scope.errorPopup = data; - } else { - $scope.errorTable = data; - } - }); + success(function(data, status, headers, config) { + var newAccount = data.data.accounts[0]; + $scope.accounts[$scope.myNewAccount.index] = newAccount + console.log("account") + console.log(account) + console.log("newAccount") + console.log(newAccount) + //$.extend($scope.myNewAccount, newAccount); + $scope.myNewAccount = undefined; + $('#newAccountPopup').bsModal('hide'); + + }). + error(function(data, status, headers, config) { + if (isPopup) { + $scope.errorPopup = data; + } else { + $scope.errorTable = data; + } + }); } } $scope.authorisationName = function(authorizationKind) { var result = authorizationKind; switch (authorizationKind) { - case "ro": + case "ro": result = "Read only"; break case "rw": @@ -222,6 +238,46 @@ $scope.popupDeletion = function(account, action, actionName) { } return result } + + /* + * A couple of method to set/unset a given ACL/verb. + */ + $scope.setApiAcl = function(account, path, verb, enabled) { + // console.log("account: " +account + " path: " + path + " verb: " + verb + " enabled: " + enabled) + if(account) { + console.log("account: " +account + " path: " + path + " verb: " + verb + " enabled: " + enabled) + //in account, aclList is: [{"path": "some path", "verbs":["get", "post"..]},...] + //so if we set enable one (path/verb), we need to add the path (if that path not already in the list) + //and add the verb. And when disable, we need to remove the verb for the path (if present) + var apiIndex = account.aclList.findIndex(o => { o.path === path && o.verb === verb}); + if(enabled && apiIndex < 0) { // add api + account.aclList.push({"path":path, "verb":verb}); + } + if(!enabled && apiIndex > 0) { // remove apo + api.verbs.splice(apiIndex, 1) + } + } + } + + $scope.getApiAclStatus = function(account, path, verb) { +// console.log("get status ["+verb+"] "+ path+ " on " + account); + if(account) { + var index = account.aclList.findIndex(function(o) { return o.path === path && o.verb === verb}); + return index >= 0; + } else return false; + } + + //getter/setter for api status translation + $scope.apiAclStatus = function(account, path, verb) { + return function(enabled) { + //console.log("args " + arguments.length + " account: " +account + " path: " + path + " verb: " + verb + " enabled: " + enabled); + if(arguments.length == 0) { + return $scope.getApiAclStatus(account, path, verb); + } else { + return $scope.setApiAcl(account, path, verb, enabled); + } + } } + $scope.formTitle = function() { if ($scope.myNewAccount === undefined || $scope.myNewAccount.token === undefined) { return "Create a new Account"; @@ -229,16 +285,16 @@ $scope.popupDeletion = function(account, action, actionName) { return "Update account '"+$scope.myNewAccount.oldName+"'"; } } - + $scope.getAccounts(); - + $scope.defineActionName = function(){ if($scope.myNewAccount === undefined || $scope.myNewAccount.token === undefined) { return "Create" } else { return "Save" } - } + } } ); $( document ).ready(function() {