Skip to content

Commit

Permalink
Fixes #23305: When custom role permission list is empty, reload lead …
Browse files Browse the repository at this point in the history
…to stack trace
  • Loading branch information
fanf committed Aug 18, 2023
1 parent c1c9baa commit ba3e72a
Show file tree
Hide file tree
Showing 7 changed files with 43 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -291,19 +291,10 @@ object AuthorizationType {
* a new AuthorizationType which melt each AuthorizationType
* that composed it.
*/
class Rights(_authorizationTypes: AuthorizationType*) {

require(
null != _authorizationTypes && _authorizationTypes.nonEmpty,
"At least one AuthorizationType must be include in a Rights object"
)

val authorizationTypes = _authorizationTypes.toSet
case class Rights private (authorizationTypes: Set[AuthorizationType]) {

def displayAuthorizations = authorizationTypes.map(_.id).toList.sorted.mkString(", ")

override lazy val hashCode = 23 * authorizationTypes.hashCode

override def equals(other: Any) = other match {
case that: Rights => this.authorizationTypes == that.authorizationTypes
case _ => false
Expand All @@ -314,6 +305,23 @@ class Rights(_authorizationTypes: AuthorizationType*) {
}
}

object Rights {

val NoRights = Rights.forAuthzs(AuthorizationType.NoRights)

val AnyRights = Rights.forAuthzs(AuthorizationType.AnyRights)

def apply(authorizationTypes: Iterable[AuthorizationType]): Rights = {
if (authorizationTypes.isEmpty) {
NoRights
} else {
new Rights(authorizationTypes.toSet)
}
}

def forAuthzs(authorizationTypes: AuthorizationType*) = apply(authorizationTypes.toSeq)
}

/*
* Rudder "Role" which are kind of an aggregate of rights which somehow
* make sense from a rudder usage point of view.
Expand All @@ -325,57 +333,55 @@ sealed trait Role {
}
object Role {
import com.normation.rudder.{AuthorizationType => A}
implicit private class ToRights[T <: AuthorizationType](authorizations: Set[T]) {
def toRights: Rights = new Rights(authorizations.toSeq: _*)
}

def allRead = A.allKind.collect { case x: ActionType.Read => x }

// for now, all account type also have the "user account" rights
val ua = A.UserAccount.values

// a special account, with all rights, present and future, even if declared at runtime.
final case object Administrator extends Role {
val name = "administrator"; def rights = new Rights(AuthorizationType.AnyRights)
val name = "administrator"; def rights = Rights.AnyRights
}

// other standard predefined roles
final case object User extends Role { val name = "user"; def rights = (ua ++ A.nodeKind ++ A.configurationKind).toRights }
final case object User extends Role { val name = "user"; def rights = Rights(ua ++ A.nodeKind ++ A.configurationKind) }
final case object AdminOnly extends Role {
val name = "administration_only"; def rights = (ua ++ A.Administration.values.map(identity)).toRights
val name = "administration_only"; def rights = Rights(ua ++ A.Administration.values.map(identity))
}
final case object Workflow extends Role {
val name = "workflow"; def rights = (ua ++ A.workflowKind ++ A.complianceKind).toRights
val name = "workflow"; def rights = Rights(ua ++ A.workflowKind ++ A.complianceKind)
}
final case object Deployer extends Role {
val name = "deployer"; def rights = (ua ++ A.Deployer.values ++ A.complianceKind).toRights
val name = "deployer"; def rights = Rights(ua ++ A.Deployer.values ++ A.complianceKind)
}
final case object Validator extends Role {
val name = "validator"; def rights = (ua ++ A.Validator.values ++ A.complianceKind).toRights
val name = "validator"; def rights = Rights(ua ++ A.Validator.values ++ A.complianceKind)
}
case object Configuration extends Role {
val name = "configuration"; val rights = (ua ++ A.configurationKind.map(identity)).toRights
val name = "configuration"; val rights = Rights(ua ++ A.configurationKind.map(identity))
}
final case object ReadOnly extends Role { val name = "read_only"; def rights = (ua ++ allRead).toRights }
final case object Compliance extends Role { val name = "compliance"; def rights = (ua ++ A.complianceKind).toRights }
final case object Inventory extends Role { val name = "inventory"; def rights = (ua ++ Set(A.Node.Read)).toRights }
final case object ReadOnly extends Role { val name = "read_only"; def rights = Rights(ua ++ allRead) }
final case object Compliance extends Role { val name = "compliance"; def rights = Rights(ua ++ A.complianceKind) }
final case object Inventory extends Role { val name = "inventory"; def rights = Rights(ua ++ Set(A.Node.Read)) }
final case object RuleOnly extends Role {
val name = "rule_only"; def rights = (ua ++ Set(A.Configuration.Read, A.Rule.Read)).toRights
val name = "rule_only"; def rights = Rights(ua ++ Set(A.Configuration.Read, A.Rule.Read))
}

// a special Role that means that a user has no rights at all. That role must super-seed any other right given by other roles
final case object NoRights extends Role { val name = "no_rights"; def rights = (Set(AuthorizationType.NoRights)).toRights }
final case object NoRights extends Role { val name = "no_rights"; def rights = Rights(Set(AuthorizationType.NoRights)) }

// this is the anonymous custom roles, the one computed on fly for user who have several roles in their attribute
final case class Custom(rights: Rights) extends Role {
val name = "custom"
override def debugString = s"authz[${rights.displayAuthorizations}]"
}
def forRight(right: AuthorizationType) = Custom(new Rights(right))
def forRights(rights: Set[AuthorizationType]) = Custom(new Rights(rights.toSeq: _*))
def forRight(right: AuthorizationType) = Custom(Rights.forAuthzs(right))
def forRights(rights: Set[AuthorizationType]) = Custom(Rights(rights))

// this is the named custom roles defined in <custom-roles> tag
final case class NamedCustom(name: String, permissions: Seq[Role]) extends Role {
def rights = new Rights(permissions.flatMap(_.rights.authorizationTypes): _*)
def rights = Rights(permissions.flatMap(_.rights.authorizationTypes))
override def debugString = s"customRole[${permissions.map(_.debugString).mkString(",")}]"
}

Expand Down Expand Up @@ -444,7 +450,7 @@ object RudderRoles {
AuthorizationType.parseRight(role) match {
case Left(err) =>
Inconsistency(s"Role '${role}' can not be resolved to a named role nor it matches a valid authorization").fail
case Right(x) => Role.Custom(new Rights(x.toSeq: _*)).succeed
case Right(x) => Role.Custom(Rights(x)).succeed
}
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ class RoleApiMapping(mapper: AuthorizationApiMapping) {
}

def getApiAclFromRoles(roles: Seq[Role]): List[ApiAclElement] = {
getApiAclFromRights(new Rights(roles.flatMap(_.rights.authorizationTypes): _*))
getApiAclFromRights(Rights(roles.flatMap(_.rights.authorizationTypes)))
}

// a merge function that groups action for identical path
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ object CurrentUser extends SessionVar[Option[RudderUserDetail]]({

def getRights: Rights = this.get match {
case Some(u) => u.authz
case None => new Rights(AuthorizationType.NoRights)
case None => Rights.forAuthzs(AuthorizationType.NoRights)
}

def account: RudderAccount = this.get match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@
*/
package com.normation.rudder.web.services

import com.normation.rudder.AuthorizationType
import com.normation.rudder.Rights
import com.normation.rudder.Role
import com.normation.rudder.RudderAccount
Expand Down Expand Up @@ -74,7 +73,7 @@ object RudderAuthType {
case object Api extends RudderAuthType {
override val grantedAuthorities = buildAuthority("ROLE_REMOTE")

val apiRudderRights = new Rights(AuthorizationType.NoRights)
val apiRudderRights = Rights.NoRights
val apiRudderRole: Set[Role] = Set(Role.NoRights)
}
}
Expand All @@ -91,9 +90,8 @@ case class RudderUserDetail(
apiAuthz: ApiAuthorization
) extends UserDetails {
// merge roles rights
val authz = new Rights(
(if (roles.nonEmpty) roles.flatMap(_.rights.authorizationTypes).toSeq else Seq(AuthorizationType.NoRights)): _*
)
val authz = Rights(roles.flatMap(_.rights.authorizationTypes))

override val (getUsername, getPassword, getAuthorities) = account match {
case RudderAccount.User(login, password) => (login, password, RudderAuthType.User.grantedAuthorities)
case RudderAccount.Api(api) => (api.name.value, api.token.value, RudderAuthType.Api.grantedAuthorities)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ object RudderAuthType {
case object Api extends RudderAuthType {
override val grantedAuthorities = buildAuthority("ROLE_REMOTE")

val apiRudderRights = new Rights(AuthorizationType.NoRights)
val apiRudderRights = Rights.NoRights
val apiRudderRole: Set[Role] = Set(Role.NoRights)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@

package com.normation.rudder.web.snippet

import bootstrap.liftweb.StaticResourceRewrite
import net.liftweb.http._
import net.liftweb.http.js.JE._
import net.liftweb.http.js.JsCmd
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,8 @@ class RudderUserDetailsTest extends Specification {
(userDetailList.isCaseSensitive must beTrue) and
(userDetailList.users.size must beEqualTo(3)) and
(userDetailList.customRoles must containTheSameElementsAs(parsedRoles)) and
(userDetailList.users("user_a1").roles must containTheSameElementsAs(List(roleA1)))
(userDetailList.users("user_a1").roles must containTheSameElementsAs(List(roleA1))) and
(roleC2.rights must beEqualTo(Role.NoRights.rights))
}

/// role specific unit tests
Expand Down

0 comments on commit ba3e72a

Please sign in to comment.