diff --git a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskAction.scala b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskAction.scala index b4807b7b68c..0d0416bdb80 100644 --- a/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskAction.scala +++ b/common/scala/src/main/scala/org/apache/openwhisk/core/entity/WhiskAction.scala @@ -56,8 +56,7 @@ case class WhiskActionPut(exec: Option[Exec] = None, limits: Option[ActionLimitsOption] = None, version: Option[SemVer] = None, publish: Option[Boolean] = None, - annotations: Option[Parameters] = None, - unlock: Option[Boolean] = None) { + annotations: Option[Parameters] = None) { protected[core] def replace(exec: Exec) = { WhiskActionPut(Some(exec), parameters, limits, version, publish, annotations) @@ -76,6 +75,17 @@ case class WhiskActionPut(exec: Option[Exec] = None, case _ => this } getOrElse this } + + protected[core] def getPermissions(): String = { + annotations match { + case Some(value) => + value + .get(WhiskAction.permissionsFieldName) + .map(value => value.convertTo[String]) + .getOrElse(WhiskAction.defaultPermissions) + case None => WhiskAction.defaultPermissions + } + } } abstract class WhiskActionLike(override val name: EntityName) extends WhiskEntity(name, "action") { @@ -352,6 +362,24 @@ object WhiskAction extends DocumentFactory[WhiskAction] with WhiskEntityQueries[ val requireWhiskAuthHeader = "x-require-whisk-auth" val lockFieldName = "lock" + // annotation permission key name + val permissionsFieldName = "permissions" + + // action is used by two type user, one is its owner, the owner has read(yes)/write(yes)/execute(yes) permissions by default, + // another user uses the shared action who has read(yes)/write(no)/execute(yes) permissions permissions by default + // only the owner can modify the permissions + val defaultPermissions = "rwxr-x" + + // includes below permission codes only for action. + // permission code:rwxr-x or 75: owner:read(yes)/write(yes)/execute(yes)|the shared action's user:read(yes)/write(no)/execute(yes), this is default + // permission code:r-xr-x or 55: owner:read(yes)/write(no)/execute(yes)|the shared action's user:read(yes)/write(no)/execute(yes) + // permission code:r--r-- or 44: owner:read(yes)/write(no)/execute(no)|the shared action's user:read(yes)/write(no)/execute(no) + // permission code:rx-r-- or 64: owner:read(yes)/write(yes)/execute(no)|the shared action's user:read(yes)/write(no)/execute(no) + // permission code:-wx--x or 31: owner:read(no)/write(yes)/execute(yes)|the shared action's user:read(no)/write(no)/execute(yes) + // permission code:--x--x or 11: owner:read(no)/write(no)/execute(yes)|the shared action's user:read(no)/write(no)/execute(yes) + // permission code:------ or 00: owner:read(no)/write(no)/execute(no)|the shared action's user:read(no)/write(no)/execute(no) + val permissionList = List(defaultPermissions, "r-xr-x", "r--r--", "rx-r--", "-wx--x", "--x--x", "------") + override val collectionName = "actions" override val cacheEnabled = true @@ -645,5 +673,5 @@ object ActionLimitsOption extends DefaultJsonProtocol { } object WhiskActionPut extends DefaultJsonProtocol { - implicit val serdes = jsonFormat7(WhiskActionPut.apply) + implicit val serdes = jsonFormat6(WhiskActionPut.apply) } diff --git a/common/scala/src/main/scala/org/apache/openwhisk/http/ErrorResponse.scala b/common/scala/src/main/scala/org/apache/openwhisk/http/ErrorResponse.scala index 1198cdc9141..148735fc953 100644 --- a/common/scala/src/main/scala/org/apache/openwhisk/http/ErrorResponse.scala +++ b/common/scala/src/main/scala/org/apache/openwhisk/http/ErrorResponse.scala @@ -20,7 +20,6 @@ package org.apache.openwhisk.http import scala.concurrent.duration.Duration import scala.concurrent.duration.FiniteDuration import scala.util.Try - import akka.http.scaladsl.model.StatusCode import akka.http.scaladsl.model.StatusCodes.Forbidden import akka.http.scaladsl.model.StatusCodes.NotFound @@ -28,15 +27,9 @@ import akka.http.scaladsl.model.MediaType import akka.http.scaladsl.server.Directives import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport.sprayJsonMarshaller import akka.http.scaladsl.server.StandardRoute - import spray.json._ - import org.apache.openwhisk.common.TransactionId -import org.apache.openwhisk.core.entity.SizeError -import org.apache.openwhisk.core.entity.ByteSize -import org.apache.openwhisk.core.entity.Exec -import org.apache.openwhisk.core.entity.ExecMetaDataBase -import org.apache.openwhisk.core.entity.ActivationId +import org.apache.openwhisk.core.entity.{ActivationId, ByteSize, Exec, ExecMetaDataBase, SizeError, WhiskAction} object Messages { @@ -83,6 +76,8 @@ object Messages { s"The supplied authentication is not authorized to access '$value'." def notAuthorizedtoActionKind(value: String) = s"The supplied authentication is not authorized to access actions of kind '$value'." + def notAuthorizedtoActionPermission(value: String) = + s"The supplied authentication is not authorized to give permission code: '$value', available permission is in ${WhiskAction.permissionList}" /** Standard error message for malformed fully qualified entity names. */ val malformedFullyQualifiedEntityName = diff --git a/core/controller/src/main/scala/org/apache/openwhisk/core/controller/Actions.scala b/core/controller/src/main/scala/org/apache/openwhisk/core/controller/Actions.scala index 7e08643e6dd..8b07ed57bfc 100644 --- a/core/controller/src/main/scala/org/apache/openwhisk/core/controller/Actions.scala +++ b/core/controller/src/main/scala/org/apache/openwhisk/core/controller/Actions.scala @@ -209,13 +209,16 @@ trait WhiskActionsApi extends WhiskCollectionAPI with PostActionActivation with val checkAdditionalPrivileges = entitleReferencedEntities(user, Privilege.READ, request.exec).flatMap { case _ => entitlementProvider.check(user, content.exec) } - val unlock = content.unlock.getOrElse(false) onComplete(checkAdditionalPrivileges) { case Success(_) => - putEntity(WhiskAction, entityStore, entityName.toDocId, overwrite, update(user, request) _, () => { - make(user, entityName, request) - }, unlock = unlock) + onComplete(entitlementProvider.checkActionPermissions(content.getPermissions())) { + case Success(_) => + putEntity(WhiskAction, entityStore, entityName.toDocId, overwrite, update(user, request) _, () => { + make(user, entityName, request) + }) + case Failure(f) => super.handleEntitlementFailure(f) + } case Failure(f) => super.handleEntitlementFailure(f) } @@ -554,9 +557,7 @@ trait WhiskActionsApi extends WhiskCollectionAPI with PostActionActivation with content.version getOrElse action.version.upPatch, content.publish getOrElse action.publish, WhiskActionsApi - .amendAnnotations(content.annotations getOrElse action.annotations, exec, create = false) ++ content.unlock - .map(u => Parameters(WhiskAction.lockFieldName, JsBoolean(!u))) - .getOrElse(Parameters())) + .amendAnnotations(content.annotations getOrElse action.annotations, exec, create = false)) .revision[WhiskAction](action.docinfo.rev) } diff --git a/core/controller/src/main/scala/org/apache/openwhisk/core/controller/ApiUtils.scala b/core/controller/src/main/scala/org/apache/openwhisk/core/controller/ApiUtils.scala index 9d43c674f8d..42b39cda268 100644 --- a/core/controller/src/main/scala/org/apache/openwhisk/core/controller/ApiUtils.scala +++ b/core/controller/src/main/scala/org/apache/openwhisk/core/controller/ApiUtils.scala @@ -25,7 +25,10 @@ import scala.util.Failure import scala.util.Success import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ import akka.http.scaladsl.model.StatusCode -import akka.http.scaladsl.model.StatusCodes._ +import akka.http.scaladsl.model.StatusCodes.Conflict +import akka.http.scaladsl.model.StatusCodes.InternalServerError +import akka.http.scaladsl.model.StatusCodes.NotFound +import akka.http.scaladsl.model.StatusCodes.OK import akka.http.scaladsl.server.{Directives, RequestContext, RouteResult} import spray.json.DefaultJsonProtocol._ import spray.json.{JsObject, JsValue, RootJsonFormat} @@ -33,14 +36,7 @@ import org.apache.openwhisk.common.Logging import org.apache.openwhisk.common.TransactionId import org.apache.openwhisk.core.controller.PostProcess.PostProcessEntity import org.apache.openwhisk.core.database._ -import org.apache.openwhisk.core.entity.{ - ActivationId, - ActivationLogs, - DocId, - WhiskAction, - WhiskActivation, - WhiskDocument -} +import org.apache.openwhisk.core.entity.{ActivationId, ActivationLogs, DocId, WhiskActivation, WhiskDocument} import org.apache.openwhisk.http.ErrorResponse import org.apache.openwhisk.http.ErrorResponse.terminate import org.apache.openwhisk.http.Messages._ @@ -314,8 +310,7 @@ trait WriteOps extends Directives { update: A => Future[A], create: () => Future[A], treatExistsAsConflict: Boolean = true, - postProcess: Option[PostProcessEntity[A]] = None, - unlock: Boolean = false)( + postProcess: Option[PostProcessEntity[A]] = None)( implicit transid: TransactionId, format: RootJsonFormat[A], notifier: Option[CacheChangeNotification], @@ -340,24 +335,8 @@ trait WriteOps extends Directives { } flatMap { case (old, a) => logging.debug(this, s"[PUT] entity created/updated, writing back to datastore") - if (overwrite && !unlock && old.getOrElse(None).isInstanceOf[WhiskAction]) { - val oldWhiskAction = old.getOrElse(None).asInstanceOf[WhiskAction] - oldWhiskAction.annotations.get(WhiskAction.lockFieldName) match { - case Some(value) if (value.convertTo[Boolean]) => { - Future failed RejectRequest( - MethodNotAllowed, - s"this action can't be updated until ${WhiskAction.lockFieldName} annotation is updated to false") - } - case _ => { - factory.put(datastore, a, old) map { _ => - a - } - } - } - } else { - factory.put(datastore, a, old) map { _ => - a - } + factory.put(datastore, a, old) map { _ => + a } }) { case Success(entity) => @@ -411,30 +390,11 @@ trait WriteOps extends Directives { notifier: Option[CacheChangeNotification], ma: Manifest[A]) = { onComplete(factory.get(datastore, docid) flatMap { entity => - if (entity.isInstanceOf[WhiskAction]) { - val whiskAction = entity.asInstanceOf[WhiskAction] - whiskAction.annotations.get(WhiskAction.lockFieldName) match { - case Some(value) if (value.convertTo[Boolean]) => { - Future failed RejectRequest( - MethodNotAllowed, - s"this action can't be deleted until ${WhiskAction.lockFieldName} annotation is updated to false") + confirm(entity) flatMap { + case _ => + factory.del(datastore, entity.docinfo) map { _ => + entity } - case _ => { - confirm(entity) flatMap { - case _ => - factory.del(datastore, entity.docinfo) map { _ => - entity - } - } - } - } - } else { - confirm(entity) flatMap { - case _ => - factory.del(datastore, entity.docinfo) map { _ => - entity - } - } } }) { case Success(entity) => diff --git a/core/controller/src/main/scala/org/apache/openwhisk/core/entitlement/Entitlement.scala b/core/controller/src/main/scala/org/apache/openwhisk/core/entitlement/Entitlement.scala index 4b33a4b37bb..f1dd48e6415 100644 --- a/core/controller/src/main/scala/org/apache/openwhisk/core/entitlement/Entitlement.scala +++ b/core/controller/src/main/scala/org/apache/openwhisk/core/entitlement/Entitlement.scala @@ -231,6 +231,21 @@ protected[core] abstract class EntitlementProvider( .getOrElse(Future.successful(())) } + /** + * Checks if an action permission code is whether right + * + * @param permissions the permission code + * @return a promise that completes with success iff the permission code is right + */ + protected[core] def checkActionPermissions(permissions: String)(implicit transid: TransactionId): Future[Unit] = { + if (WhiskAction.permissionList.contains(permissions)) { + Future.successful(()) + } else { + Future.failed( + RejectRequest(Forbidden, Some(ErrorResponse(Messages.notAuthorizedtoActionPermission(permissions), transid)))) + } + } + /** * Checks if a subject has the right to access a specific resource. The entitlement may be implicit, * that is, inferred based on namespaces that a subject belongs to and the namespace of the