Skip to content

Commit a7d3fe7

Browse files
markusthoemmesrabbah
authored andcommitted
Refactored EntitlementProvider to not contain mutability. (#3068)
Using mutable datastructures, especially closing over them in Futures, is dangerous and non-deterministic. This aims to further terse down the logic used as well.
1 parent 82f5d87 commit a7d3fe7

1 file changed

Lines changed: 32 additions & 42 deletions

File tree

core/controller/src/main/scala/whisk/core/entitlement/Entitlement.scala

Lines changed: 32 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package whisk.core.entitlement
1919

2020
import scala.collection.concurrent.TrieMap
2121
import scala.collection.immutable.Set
22-
import scala.collection.mutable.ListBuffer
2322
import scala.concurrent.Future
2423
import scala.util.Failure
2524
import scala.util.Success
@@ -49,7 +48,7 @@ package object types {
4948
* Resource is a type that encapsulates details relevant to identify a specific resource.
5049
* It may be an entire collection, or an element in a collection.
5150
*
52-
* @param ns the namespace the resource resides in
51+
* @param namespace the namespace the resource resides in
5352
* @param collection the collection (e.g., actions, triggers) identifying a resource
5453
* @param entity an optional entity name that identifies a specific item in the collection
5554
* @param env an optional environment to bind to the resource during an activation
@@ -59,8 +58,8 @@ protected[core] case class Resource(namespace: EntityPath,
5958
entity: Option[String],
6059
env: Option[Parameters] = None) {
6160
def parent = collection.path + EntityPath.PATHSEP + namespace
62-
def id = parent + (entity map { EntityPath.PATHSEP + _ } getOrElse (""))
63-
def fqname = namespace.asString + (entity map { EntityPath.PATHSEP + _ } getOrElse (""))
61+
def id = parent + entity.map(EntityPath.PATHSEP + _).getOrElse("")
62+
def fqname = namespace.asString + entity.map(EntityPath.PATHSEP + _).getOrElse("")
6463
override def toString = id
6564
}
6665

@@ -186,6 +185,21 @@ protected[core] abstract class EntitlementProvider(config: WhiskConfig, loadBala
186185
protected[core] def check(user: Identity, right: Privilege, resource: Resource)(
187186
implicit transid: TransactionId): Future[Unit] = check(user, right, Set(resource))
188187

188+
/**
189+
* Constructs a RejectRequest containing the forbidden resources.
190+
*
191+
* @param resources resources forbidden to access
192+
* @return a RejectRequest with the appropriate message
193+
*/
194+
private def unauthorizedOn(resources: Set[Resource])(implicit transid: TransactionId) = {
195+
RejectRequest(
196+
Forbidden,
197+
Some(
198+
ErrorResponse(
199+
Messages.notAuthorizedtoAccessResource(resources.map(_.fqname).toSeq.sorted.toSet.mkString(", ")),
200+
transid)))
201+
}
202+
189203
/**
190204
* Checks if a subject has the right to access a set of resources. The entitlement may be implicit,
191205
* that is, inferred based on namespaces that a subject belongs to and the namespace of the
@@ -200,58 +214,36 @@ protected[core] abstract class EntitlementProvider(config: WhiskConfig, loadBala
200214
protected[core] def check(user: Identity, right: Privilege, resources: Set[Resource])(
201215
implicit transid: TransactionId): Future[Unit] = {
202216
val subject = user.subject
203-
val inaccessibleResources = ListBuffer[Resource]()
204217

205-
val entitlementCheck: Future[Boolean] = if (user.rights.contains(right)) {
218+
val entitlementCheck: Future[Unit] = if (user.rights.contains(right)) {
206219
if (resources.nonEmpty) {
207220
logging.info(this, s"checking user '$subject' has privilege '$right' for '${resources.mkString(", ")}'")
208221
checkSystemOverload(right)
209222
.flatMap(_ => checkUserThrottle(user, right, resources))
210223
.flatMap(_ => checkConcurrentUserThrottle(user, right, resources))
211224
.flatMap(_ => checkPrivilege(user, right, resources))
212-
.flatMap(resourcePrivSet => {
213-
resourcePrivSet.map { resourcePriv =>
214-
if (!resourcePriv._2) inaccessibleResources += resourcePriv._1
215-
}
216-
Future.successful(inaccessibleResources.length == 0)
225+
.flatMap(checkedResources => {
226+
val failedResources = checkedResources.filterNot(_._2)
227+
if (failedResources.isEmpty) Future.successful(())
228+
else Future.failed(unauthorizedOn(failedResources.map(_._1)))
217229
})
218-
} else Future.successful(true)
230+
} else Future.successful(())
219231
} else if (right != REJECT) {
220-
resources.map(r => inaccessibleResources += r)
221232
logging.info(
222233
this,
223234
s"supplied authkey for user '$subject' does not have privilege '$right' for '${resources.mkString(", ")}'")
224-
Future.failed(
225-
RejectRequest(
226-
Forbidden,
227-
Some(
228-
ErrorResponse(
229-
Messages.notAuthorizedtoAccessResource(
230-
inaccessibleResources.map(r => r.fqname).sorted.toSet.mkString(", ")),
231-
transid))))
235+
Future.failed(unauthorizedOn(resources))
232236
} else {
233-
resources.map(r => inaccessibleResources += r)
234-
Future.successful(false)
237+
Future.failed(unauthorizedOn(resources))
235238
}
239+
236240
entitlementCheck andThen {
237-
case Success(r) if resources.nonEmpty =>
238-
logging.info(this, if (r) "authorized" else "not authorized")
241+
case Success(rs) =>
242+
logging.info(this, "authorized")
239243
case Failure(r: RejectRequest) =>
240244
logging.info(this, s"not authorized: $r")
241245
case Failure(t) =>
242246
logging.error(this, s"failed while checking entitlement: ${t.getMessage}")
243-
} flatMap { isAuthorized =>
244-
if (isAuthorized) Future.successful({})
245-
else {
246-
Future.failed(
247-
RejectRequest(
248-
Forbidden,
249-
Some(
250-
ErrorResponse(
251-
Messages.notAuthorizedtoAccessResource(
252-
inaccessibleResources.map(r => r.fqname).sorted.toSet.mkString(", ")),
253-
transid))))
254-
}
255247
}
256248
}
257249

@@ -273,9 +265,7 @@ protected[core] abstract class EntitlementProvider(config: WhiskConfig, loadBala
273265
case true => Future.successful(resource -> true)
274266
case false =>
275267
logging.info(this, "checking explicit grants")
276-
entitled(user.subject, right, resource) flatMap {
277-
case b => Future.successful(resource -> b)
278-
}
268+
entitled(user.subject, right, resource).flatMap(b => Future.successful(resource -> b))
279269
}
280270
}
281271
}
@@ -305,7 +295,7 @@ protected[core] abstract class EntitlementProvider(config: WhiskConfig, loadBala
305295
*
306296
* @param user the subject identity to check rights for
307297
* @param right the privilege, if ACTIVATE then check quota else return None
308-
* @param resource the set of resources must contain at least one resource that can be activated else return None
298+
* @param resources the set of resources must contain at least one resource that can be activated else return None
309299
* @return future completing successfully if user is below limits else failing with a rejection
310300
*/
311301
private def checkUserThrottle(user: Identity, right: Privilege, resources: Set[Resource])(
@@ -327,7 +317,7 @@ protected[core] abstract class EntitlementProvider(config: WhiskConfig, loadBala
327317
*
328318
* @param user the subject identity to check rights for
329319
* @param right the privilege, if ACTIVATE then check quota else return None
330-
* @param resource the set of resources must contain at least one resource that can be activated else return None
320+
* @param resources the set of resources must contain at least one resource that can be activated else return None
331321
* @return future completing successfully if user is below limits else failing with a rejection
332322
*/
333323
private def checkConcurrentUserThrottle(user: Identity, right: Privilege, resources: Set[Resource])(

0 commit comments

Comments
 (0)