Skip to content

Commit 1c37112

Browse files
committed
Apply unix style permission management
1 parent 68cb28a commit 1c37112

File tree

7 files changed

+374
-129
lines changed

7 files changed

+374
-129
lines changed

common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskAction.scala

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,7 @@ case class WhiskActionPut(exec: Option[Exec] = None,
5656
limits: Option[ActionLimitsOption] = None,
5757
version: Option[SemVer] = None,
5858
publish: Option[Boolean] = None,
59-
annotations: Option[Parameters] = None,
60-
unlock: Option[Boolean] = None) {
59+
annotations: Option[Parameters] = None) {
6160

6261
protected[core] def replace(exec: Exec) = {
6362
WhiskActionPut(Some(exec), parameters, limits, version, publish, annotations)
@@ -76,6 +75,16 @@ case class WhiskActionPut(exec: Option[Exec] = None,
7675
case _ => this
7776
} getOrElse this
7877
}
78+
79+
protected[core] def getPermissions(): Option[String] = {
80+
annotations match {
81+
case Some(value) =>
82+
value
83+
.get(WhiskAction.permissionsFieldName)
84+
.map(value => value.convertTo[String])
85+
case None => None
86+
}
87+
}
7988
}
8089

8190
abstract class WhiskActionLike(override val name: EntityName) extends WhiskEntity(name, "action") {
@@ -350,7 +359,31 @@ object WhiskAction extends DocumentFactory[WhiskAction] with WhiskEntityQueries[
350359
import WhiskActivation.instantSerdes
351360
val execFieldName = "exec"
352361
val requireWhiskAuthHeader = "x-require-whisk-auth"
353-
val lockFieldName = "lock"
362+
363+
// annotation permission key name
364+
val permissionsFieldName = "permissions"
365+
366+
val defaultPermissions = "rwxr-x"
367+
368+
// notes on users, just have 2 type users,
369+
// 1. the action's owner
370+
// 2. the user (not the owner) who used the shared action directly(e.g. get, invoke)
371+
//
372+
// Notes on permission control
373+
// 1. the action's read permission should open forever, because under invoke action or update action and so on,
374+
// need to use `fetch` api to get the action to judge it whether exist.
375+
// 2. the user(not the owner) can't update/delete the action forever.
376+
// 3. the owner's permission can affect other user's permission, e.g
377+
// if the owner is not given execute permission, the user(not the owner) can't have execute permission as well.
378+
//
379+
// Notes on permission values, include below permission value
380+
// 1. permission code:rwxr-x: owner:read(yes)/write(yes)/execute(yes)|the shared action's user:read(yes)/write(no)/execute(yes), this is default
381+
// 2. permission code:rwxr--: owner:read(yes)/write(yes)/execute(yes)|the shared action's user:read(yes)/write(no)/execute(no)
382+
// 3. permission code:r-xr-x: owner:read(yes)/write(no)/execute(yes)|the shared action's user:read(yes)/write(no)/execute(yes)
383+
// 4. permission code:r-xr--: owner:read(yes)/write(no)/execute(yes)|the shared action's user:read(yes)/write(no)/execute(no)
384+
// 5. permission code:r--r--: owner:read(yes)/write(no)/execute(no)|the shared action's user:read(yes)/write(no)/execute(no)
385+
// 6. permission code:rw-r--: owner:read(yes)/write(yes)/execute(no)|the shared action's user:read(yes)/write(no)/execute(no)
386+
val permissionList = List(defaultPermissions, "rwxr--", "r-xr-x", "r-xr--", "r--r--", "rw-r--")
354387

355388
override val collectionName = "actions"
356389
override val cacheEnabled = true
@@ -645,5 +678,5 @@ object ActionLimitsOption extends DefaultJsonProtocol {
645678
}
646679

647680
object WhiskActionPut extends DefaultJsonProtocol {
648-
implicit val serdes = jsonFormat7(WhiskActionPut.apply)
681+
implicit val serdes = jsonFormat6(WhiskActionPut.apply)
649682
}

common/scala/src/main/scala/org/apache/openwhisk/http/ErrorResponse.scala

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,16 @@ package org.apache.openwhisk.http
2020
import scala.concurrent.duration.Duration
2121
import scala.concurrent.duration.FiniteDuration
2222
import scala.util.Try
23-
2423
import akka.http.scaladsl.model.StatusCode
2524
import akka.http.scaladsl.model.StatusCodes.Forbidden
2625
import akka.http.scaladsl.model.StatusCodes.NotFound
2726
import akka.http.scaladsl.model.MediaType
2827
import akka.http.scaladsl.server.Directives
2928
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport.sprayJsonMarshaller
3029
import akka.http.scaladsl.server.StandardRoute
31-
3230
import spray.json._
33-
3431
import org.apache.openwhisk.common.TransactionId
35-
import org.apache.openwhisk.core.entity.SizeError
36-
import org.apache.openwhisk.core.entity.ByteSize
37-
import org.apache.openwhisk.core.entity.Exec
38-
import org.apache.openwhisk.core.entity.ExecMetaDataBase
39-
import org.apache.openwhisk.core.entity.ActivationId
32+
import org.apache.openwhisk.core.entity.{ActivationId, ByteSize, Exec, ExecMetaDataBase, SizeError}
4033

4134
object Messages {
4235

core/controller/src/main/scala/org/apache/openwhisk/core/controller/Actions.scala

Lines changed: 59 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -209,13 +209,25 @@ trait WhiskActionsApi extends WhiskCollectionAPI with PostActionActivation with
209209
val checkAdditionalPrivileges = entitleReferencedEntities(user, Privilege.READ, request.exec).flatMap {
210210
case _ => entitlementProvider.check(user, content.exec)
211211
}
212-
val unlock = content.unlock.getOrElse(false)
213212

214213
onComplete(checkAdditionalPrivileges) {
215214
case Success(_) =>
216-
putEntity(WhiskAction, entityStore, entityName.toDocId, overwrite, update(user, request) _, () => {
217-
make(user, entityName, request)
218-
}, unlock = unlock)
215+
val operation = if (overwrite) "update" else "create"
216+
onComplete(
217+
entitlementProvider
218+
.checkActionPermissions(
219+
operation,
220+
user,
221+
entityStore,
222+
entityName,
223+
WhiskAction.get,
224+
content.getPermissions())) {
225+
case Success(_) =>
226+
putEntity(WhiskAction, entityStore, entityName.toDocId, overwrite, update(user, request) _, () => {
227+
make(user, entityName, request)
228+
})
229+
case Failure(f) => super.handleEntitlementFailure(f)
230+
}
219231
case Failure(f) =>
220232
super.handleEntitlementFailure(f)
221233
}
@@ -236,37 +248,43 @@ trait WhiskActionsApi extends WhiskCollectionAPI with PostActionActivation with
236248
*/
237249
override def activate(user: Identity, entityName: FullyQualifiedEntityName, env: Option[Parameters])(
238250
implicit transid: TransactionId) = {
239-
parameter(
240-
'blocking ? false,
241-
'result ? false,
242-
'timeout.as[FiniteDuration] ? controllerActivationConfig.maxWaitForBlockingActivation) {
243-
(blocking, result, waitOverride) =>
244-
entity(as[Option[JsObject]]) { payload =>
245-
getEntity(WhiskActionMetaData.resolveActionAndMergeParameters(entityStore, entityName), Some {
246-
act: WhiskActionMetaData =>
247-
// resolve the action --- special case for sequences that may contain components with '_' as default package
248-
val action = act.resolve(user.namespace)
249-
onComplete(entitleReferencedEntitiesMetaData(user, Privilege.ACTIVATE, Some(action.exec))) {
250-
case Success(_) =>
251-
val actionWithMergedParams = env.map(action.inherit(_)) getOrElse action
252-
253-
// incoming parameters may not override final parameters (i.e., parameters with already defined values)
254-
// on an action once its parameters are resolved across package and binding
255-
val allowInvoke = payload
256-
.map(_.fields.keySet.forall(key => !actionWithMergedParams.immutableParameters.contains(key)))
257-
.getOrElse(true)
258-
259-
if (allowInvoke) {
260-
doInvoke(user, actionWithMergedParams, payload, blocking, waitOverride, result)
261-
} else {
262-
terminate(BadRequest, Messages.parametersNotAllowed)
263-
}
251+
onComplete(
252+
entitlementProvider
253+
.checkActionPermissions("invoke", user, entityStore, entityName, WhiskAction.get)) {
254+
case Success(_) =>
255+
parameter(
256+
'blocking ? false,
257+
'result ? false,
258+
'timeout.as[FiniteDuration] ? controllerActivationConfig.maxWaitForBlockingActivation) {
259+
(blocking, result, waitOverride) =>
260+
entity(as[Option[JsObject]]) { payload =>
261+
getEntity(WhiskActionMetaData.resolveActionAndMergeParameters(entityStore, entityName), Some {
262+
act: WhiskActionMetaData =>
263+
// resolve the action --- special case for sequences that may contain components with '_' as default package
264+
val action = act.resolve(user.namespace)
265+
onComplete(entitleReferencedEntitiesMetaData(user, Privilege.ACTIVATE, Some(action.exec))) {
266+
case Success(_) =>
267+
val actionWithMergedParams = env.map(action.inherit(_)) getOrElse action
268+
269+
// incoming parameters may not override final parameters (i.e., parameters with already defined values)
270+
// on an action once its parameters are resolved across package and binding
271+
val allowInvoke = payload
272+
.map(_.fields.keySet.forall(key => !actionWithMergedParams.immutableParameters.contains(key)))
273+
.getOrElse(true)
274+
275+
if (allowInvoke) {
276+
doInvoke(user, actionWithMergedParams, payload, blocking, waitOverride, result)
277+
} else {
278+
terminate(BadRequest, Messages.parametersNotAllowed)
279+
}
264280

265-
case Failure(f) =>
266-
super.handleEntitlementFailure(f)
267-
}
268-
})
281+
case Failure(f) =>
282+
super.handleEntitlementFailure(f)
283+
}
284+
})
285+
}
269286
}
287+
case Failure(f) => super.handleEntitlementFailure(f)
270288
}
271289
}
272290

@@ -328,7 +346,13 @@ trait WhiskActionsApi extends WhiskCollectionAPI with PostActionActivation with
328346
* - 500 Internal Server Error
329347
*/
330348
override def remove(user: Identity, entityName: FullyQualifiedEntityName)(implicit transid: TransactionId) = {
331-
deleteEntity(WhiskAction, entityStore, entityName.toDocId, (a: WhiskAction) => Future.successful({}))
349+
onComplete(
350+
entitlementProvider
351+
.checkActionPermissions("remove", user, entityStore, entityName, WhiskAction.get)) {
352+
case Success(_) =>
353+
deleteEntity(WhiskAction, entityStore, entityName.toDocId, (a: WhiskAction) => Future.successful({}))
354+
case Failure(f) => super.handleEntitlementFailure(f)
355+
}
332356
}
333357

334358
/**
@@ -554,9 +578,7 @@ trait WhiskActionsApi extends WhiskCollectionAPI with PostActionActivation with
554578
content.version getOrElse action.version.upPatch,
555579
content.publish getOrElse action.publish,
556580
WhiskActionsApi
557-
.amendAnnotations(content.annotations getOrElse action.annotations, exec, create = false) ++ content.unlock
558-
.map(u => Parameters(WhiskAction.lockFieldName, JsBoolean(!u)))
559-
.getOrElse(Parameters()))
581+
.amendAnnotations(content.annotations getOrElse action.annotations, exec, create = false))
560582
.revision[WhiskAction](action.docinfo.rev)
561583
}
562584

core/controller/src/main/scala/org/apache/openwhisk/core/controller/ApiUtils.scala

Lines changed: 12 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,18 @@ import scala.util.Failure
2525
import scala.util.Success
2626
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._
2727
import akka.http.scaladsl.model.StatusCode
28-
import akka.http.scaladsl.model.StatusCodes._
28+
import akka.http.scaladsl.model.StatusCodes.Conflict
29+
import akka.http.scaladsl.model.StatusCodes.InternalServerError
30+
import akka.http.scaladsl.model.StatusCodes.NotFound
31+
import akka.http.scaladsl.model.StatusCodes.OK
2932
import akka.http.scaladsl.server.{Directives, RequestContext, RouteResult}
3033
import spray.json.DefaultJsonProtocol._
3134
import spray.json.{JsObject, JsValue, RootJsonFormat}
3235
import org.apache.openwhisk.common.Logging
3336
import org.apache.openwhisk.common.TransactionId
3437
import org.apache.openwhisk.core.controller.PostProcess.PostProcessEntity
3538
import org.apache.openwhisk.core.database._
36-
import org.apache.openwhisk.core.entity.{
37-
ActivationId,
38-
ActivationLogs,
39-
DocId,
40-
WhiskAction,
41-
WhiskActivation,
42-
WhiskDocument
43-
}
39+
import org.apache.openwhisk.core.entity.{ActivationId, ActivationLogs, DocId, WhiskActivation, WhiskDocument}
4440
import org.apache.openwhisk.http.ErrorResponse
4541
import org.apache.openwhisk.http.ErrorResponse.terminate
4642
import org.apache.openwhisk.http.Messages._
@@ -314,8 +310,7 @@ trait WriteOps extends Directives {
314310
update: A => Future[A],
315311
create: () => Future[A],
316312
treatExistsAsConflict: Boolean = true,
317-
postProcess: Option[PostProcessEntity[A]] = None,
318-
unlock: Boolean = false)(
313+
postProcess: Option[PostProcessEntity[A]] = None)(
319314
implicit transid: TransactionId,
320315
format: RootJsonFormat[A],
321316
notifier: Option[CacheChangeNotification],
@@ -340,24 +335,8 @@ trait WriteOps extends Directives {
340335
} flatMap {
341336
case (old, a) =>
342337
logging.debug(this, s"[PUT] entity created/updated, writing back to datastore")
343-
if (overwrite && !unlock && old.getOrElse(None).isInstanceOf[WhiskAction]) {
344-
val oldWhiskAction = old.getOrElse(None).asInstanceOf[WhiskAction]
345-
oldWhiskAction.annotations.get(WhiskAction.lockFieldName) match {
346-
case Some(value) if (value.convertTo[Boolean]) => {
347-
Future failed RejectRequest(
348-
MethodNotAllowed,
349-
s"this action can't be updated until ${WhiskAction.lockFieldName} annotation is updated to false")
350-
}
351-
case _ => {
352-
factory.put(datastore, a, old) map { _ =>
353-
a
354-
}
355-
}
356-
}
357-
} else {
358-
factory.put(datastore, a, old) map { _ =>
359-
a
360-
}
338+
factory.put(datastore, a, old) map { _ =>
339+
a
361340
}
362341
}) {
363342
case Success(entity) =>
@@ -411,30 +390,11 @@ trait WriteOps extends Directives {
411390
notifier: Option[CacheChangeNotification],
412391
ma: Manifest[A]) = {
413392
onComplete(factory.get(datastore, docid) flatMap { entity =>
414-
if (entity.isInstanceOf[WhiskAction]) {
415-
val whiskAction = entity.asInstanceOf[WhiskAction]
416-
whiskAction.annotations.get(WhiskAction.lockFieldName) match {
417-
case Some(value) if (value.convertTo[Boolean]) => {
418-
Future failed RejectRequest(
419-
MethodNotAllowed,
420-
s"this action can't be deleted until ${WhiskAction.lockFieldName} annotation is updated to false")
393+
confirm(entity) flatMap {
394+
case _ =>
395+
factory.del(datastore, entity.docinfo) map { _ =>
396+
entity
421397
}
422-
case _ => {
423-
confirm(entity) flatMap {
424-
case _ =>
425-
factory.del(datastore, entity.docinfo) map { _ =>
426-
entity
427-
}
428-
}
429-
}
430-
}
431-
} else {
432-
confirm(entity) flatMap {
433-
case _ =>
434-
factory.del(datastore, entity.docinfo) map { _ =>
435-
entity
436-
}
437-
}
438398
}
439399
}) {
440400
case Success(entity) =>

0 commit comments

Comments
 (0)