Skip to content

Commit

Permalink
move code checking if ds is accessible via orga switching to separate…
Browse files Browse the repository at this point in the history
… service
  • Loading branch information
Michael Büßemeyer authored and Michael Büßemeyer committed Dec 10, 2024
1 parent f31575f commit 4a56a33
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 104 deletions.
93 changes: 6 additions & 87 deletions app/controllers/AuthenticationController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import play.api.i18n.{Messages, MessagesProvider}
import play.api.libs.json._
import play.api.mvc.{Action, AnyContent, Cookie, PlayBodyParsers, Request, Result}
import security.{
AuthenticationService,
CombinedAuthenticator,
OpenIdConnectClient,
OpenIdConnectUserInfo,
Expand All @@ -50,6 +51,7 @@ class AuthenticationController @Inject()(
passwordHasher: PasswordHasher,
userService: UserService,
annotationProvider: AnnotationInformationProvider,
authenticationService: AuthenticationService,
organizationService: OrganizationService,
inviteService: InviteService,
inviteDAO: InviteDAO,
Expand Down Expand Up @@ -228,103 +230,20 @@ class AuthenticationController @Inject()(
result <- combinedAuthenticatorService.embed(cookie, Redirect("/dashboard")) //to login the new user
} yield result

/*
superadmin - can definitely switch, find organization via global access context
not superadmin - fetch all identities, construct access context, try until one works
*/

def accessibleBySwitching(datasetId: Option[String],
annotationId: Option[String],
workflowHash: Option[String]): Action[AnyContent] = sil.SecuredAction.async {
implicit request =>
for {
datasetIdValidated <- Fox.runOptional(datasetId)(ObjectId.fromString(_))
isSuperUser <- multiUserDAO.findOne(request.identity._multiUser).map(_.isSuperUser)
selectedOrganization <- if (isSuperUser)
accessibleBySwitchingForSuperUser(datasetIdValidated, annotationId, workflowHash)
else
accessibleBySwitchingForMultiUser(request.identity._multiUser, datasetIdValidated, annotationId, workflowHash)
_ <- bool2Fox(selectedOrganization._id != request.identity._organization) // User is already in correct orga, but still could not see dataset. Assume this had a reason.
selectedOrganization <- authenticationService.getOrganizationToSwitchTo(request.identity,
datasetIdValidated,
annotationId,
workflowHash)
selectedOrganizationJs <- organizationService.publicWrites(selectedOrganization)
} yield Ok(selectedOrganizationJs)
}

private def accessibleBySwitchingForSuperUser(datasetIdOpt: Option[ObjectId],
annotationIdOpt: Option[String],
workflowHashOpt: Option[String]): Fox[Organization] = {
implicit val ctx: DBAccessContext = GlobalAccessContext
(datasetIdOpt, annotationIdOpt, workflowHashOpt) match {
case (Some(datasetId), None, None) =>
for {
dataset <- datasetDAO.findOne(datasetId)
organization <- organizationDAO.findOne(dataset._organization)
} yield organization
case (None, Some(annotationId), None) =>
for {
annotationObjectId <- ObjectId.fromString(annotationId)
annotation <- annotationDAO.findOne(annotationObjectId) // Note: this does not work for compound annotations.
user <- userDAO.findOne(annotation._user)
organization <- organizationDAO.findOne(user._organization)
} yield organization
case (None, None, Some(workflowHash)) =>
for {
workflow <- voxelyticsDAO.findWorkflowByHash(workflowHash)
organization <- organizationDAO.findOne(workflow._organization)
} yield organization
case _ => Fox.failure("Can either test access for dataset or annotation or workflow, not a combination")
}
}

private def accessibleBySwitchingForMultiUser(multiUserId: ObjectId,
datasetIdOpt: Option[ObjectId],
annotationIdOpt: Option[String],
workflowHashOpt: Option[String]): Fox[Organization] =
for {
identities <- userDAO.findAllByMultiUser(multiUserId)
selectedIdentity <- Fox.find(identities)(identity =>
canAccessDatasetOrAnnotationOrWorkflow(identity, datasetIdOpt, annotationIdOpt, workflowHashOpt))
selectedOrganization <- organizationDAO.findOne(selectedIdentity._organization)(GlobalAccessContext)
} yield selectedOrganization

private def canAccessDatasetOrAnnotationOrWorkflow(user: User,
datasetIdOpt: Option[ObjectId],
annotationIdOpt: Option[String],
workflowHashOpt: Option[String]): Fox[Boolean] = {
val ctx = AuthorizedAccessContext(user)
(datasetIdOpt, annotationIdOpt, workflowHashOpt) match {
case (Some(datasetId), None, None) =>
canAccessDataset(ctx, datasetId)
case (None, Some(annotationId), None) =>
canAccessAnnotation(user, ctx, annotationId)
case (None, None, Some(workflowHash)) =>
canAccessWorkflow(user, workflowHash)
case _ => Fox.failure("Can either test access for dataset or annotation or workflow, not a combination")
}
}

private def canAccessDataset(ctx: DBAccessContext, datasetId: ObjectId): Fox[Boolean] = {
val foundFox = datasetDAO.findOne(datasetId)(ctx)
foundFox.futureBox.map(_.isDefined)
}

private def canAccessAnnotation(user: User, ctx: DBAccessContext, annotationId: String): Fox[Boolean] = {
val foundFox = for {
annotationIdParsed <- ObjectId.fromString(annotationId)
annotation <- annotationDAO.findOne(annotationIdParsed)(GlobalAccessContext)
_ <- bool2Fox(annotation.state != Cancelled)
restrictions <- annotationProvider.restrictionsFor(AnnotationIdentifier(annotation.typ, annotationIdParsed))(ctx)
_ <- restrictions.allowAccess(user)
} yield ()
foundFox.futureBox.map(_.isDefined)
}

private def canAccessWorkflow(user: User, workflowHash: String): Fox[Boolean] = {
val foundFox = for {
_ <- voxelyticsDAO.findWorkflowByHashAndOrganization(user._organization, workflowHash)
} yield ()
foundFox.futureBox.map(_.isDefined)
}

def joinOrganization(inviteToken: String): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
for {
invite <- inviteDAO.findOneByTokenValue(inviteToken) ?~> "invite.invalidToken"
Expand Down
29 changes: 12 additions & 17 deletions app/controllers/DatasetController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ import models.folder.FolderService
import models.organization.OrganizationDAO
import models.team.{TeamDAO, TeamService}
import models.user.{User, UserDAO, UserService}
import net.liftweb.common.{Failure, Full, Empty}
import net.liftweb.common.{Empty, Failure, Full}
import play.api.i18n.{Messages, MessagesProvider}
import play.api.libs.functional.syntax._
import play.api.libs.json._
import play.api.mvc.{Action, AnyContent, PlayBodyParsers}
import play.silhouette.api.Silhouette
import security.{URLSharing, WkEnv}
import security.{AuthenticationService, URLSharing, WkEnv}
import utils.{MetadataAssertions, WkConf}

import javax.inject.Inject
Expand Down Expand Up @@ -85,7 +85,7 @@ class DatasetController @Inject()(userService: UserService,
thumbnailService: ThumbnailService,
thumbnailCachingService: ThumbnailCachingService,
conf: WkConf,
authenticationController: AuthenticationController,
authenticationService: AuthenticationService,
analyticsService: AnalyticsService,
mailchimpClient: MailchimpClient,
wkExploreRemoteLayerService: WKExploreRemoteLayerService,
Expand Down Expand Up @@ -418,21 +418,16 @@ class DatasetController @Inject()(userService: UserService,
"directoryName" -> dataset.directoryName)))
case Empty =>
for {
user <- request.identity.toFox ~> Unauthorized
dataset <- datasetDAO.findOneByNameAndOrganization(datasetName, organizationId)(GlobalAccessContext)
isAccessibleResult <- authenticationController.accessibleBySwitching(Some(dataset._id.toString),
None,
None)(request)
result <- isAccessibleResult.header.status match {
case 200 =>
Fox.successful(
Ok(
Json.obj("id" -> dataset._id,
"name" -> dataset.name,
"organization" -> dataset._organization,
"directoryName" -> dataset.directoryName)))
case _ => Fox.failure(notFoundMessage(datasetName))
}
} yield result
// Just checking if the user can switch to an organization to access the dataset.
_ <- authenticationService.getOrganizationToSwitchTo(user, Some(dataset._id), None, None)
} yield
Ok(
Json.obj("id" -> dataset._id,
"name" -> dataset.name,
"organization" -> dataset._organization,
"directoryName" -> dataset.directoryName))
case _ => Fox.failure(notFoundMessage(datasetName))
}) ?~> notFoundMessage(datasetName) ~> NOT_FOUND
} yield result
Expand Down
123 changes: 123 additions & 0 deletions app/security/AuthenticationService.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package security

import com.scalableminds.util.accesscontext.{AuthorizedAccessContext, DBAccessContext, GlobalAccessContext}
import com.scalableminds.util.objectid.ObjectId
import com.scalableminds.util.tools.Fox
import com.scalableminds.util.tools.Fox.bool2Fox
import models.annotation.AnnotationState.Cancelled
import models.annotation.{AnnotationDAO, AnnotationIdentifier, AnnotationInformationProvider}
import models.dataset.DatasetDAO
import models.organization.{Organization, OrganizationDAO}
import models.user.{MultiUserDAO, User, UserDAO}
import models.voxelytics.VoxelyticsDAO
import utils.sql.SqlEscaping

import javax.inject.Inject
import scala.concurrent.ExecutionContext

class AuthenticationService @Inject()(
userDAO: UserDAO,
multiUserDAO: MultiUserDAO,
annotationDAO: AnnotationDAO,
organizationDAO: OrganizationDAO,
datasetDAO: DatasetDAO,
annotationProvider: AnnotationInformationProvider,
voxelyticsDAO: VoxelyticsDAO,
)(implicit ec: ExecutionContext)
extends SqlEscaping {

/*
superadmin - can definitely switch, find organization via global access context
not superadmin - fetch all identities, construct access context, try until one works
*/

def getOrganizationToSwitchTo(user: User,
datasetId: Option[ObjectId],
annotationId: Option[String],
workflowHash: Option[String])(implicit ctx: DBAccessContext): Fox[Organization] =
for {
isSuperUser <- multiUserDAO.findOne(user._multiUser).map(_.isSuperUser)
selectedOrganization <- if (isSuperUser)
accessibleBySwitchingForSuperUser(datasetId, annotationId, workflowHash)
else
accessibleBySwitchingForMultiUser(user._multiUser, datasetId, annotationId, workflowHash)
_ <- bool2Fox(selectedOrganization._id != user._organization) // User is already in correct orga, but still could not see dataset. Assume this had a reason.
} yield selectedOrganization

private def accessibleBySwitchingForSuperUser(datasetIdOpt: Option[ObjectId],
annotationIdOpt: Option[String],
workflowHashOpt: Option[String]): Fox[Organization] = {
implicit val ctx: DBAccessContext = GlobalAccessContext
(datasetIdOpt, annotationIdOpt, workflowHashOpt) match {
case (Some(datasetId), None, None) =>
for {
dataset <- datasetDAO.findOne(datasetId)
organization <- organizationDAO.findOne(dataset._organization)
} yield organization
case (None, Some(annotationId), None) =>
for {
annotationObjectId <- ObjectId.fromString(annotationId)
annotation <- annotationDAO.findOne(annotationObjectId) // Note: this does not work for compound annotations.
user <- userDAO.findOne(annotation._user)
organization <- organizationDAO.findOne(user._organization)
} yield organization
case (None, None, Some(workflowHash)) =>
for {
workflow <- voxelyticsDAO.findWorkflowByHash(workflowHash)
organization <- organizationDAO.findOne(workflow._organization)
} yield organization
case _ => Fox.failure("Can either test access for dataset or annotation or workflow, not a combination")
}
}

private def accessibleBySwitchingForMultiUser(multiUserId: ObjectId,
datasetIdOpt: Option[ObjectId],
annotationIdOpt: Option[String],
workflowHashOpt: Option[String]): Fox[Organization] =
for {
identities <- userDAO.findAllByMultiUser(multiUserId)
selectedIdentity <- Fox.find(identities)(identity =>
canAccessDatasetOrAnnotationOrWorkflow(identity, datasetIdOpt, annotationIdOpt, workflowHashOpt))
selectedOrganization <- organizationDAO.findOne(selectedIdentity._organization)(GlobalAccessContext)
} yield selectedOrganization

private def canAccessDatasetOrAnnotationOrWorkflow(user: User,
datasetIdOpt: Option[ObjectId],
annotationIdOpt: Option[String],
workflowHashOpt: Option[String]): Fox[Boolean] = {
val ctx = AuthorizedAccessContext(user)
(datasetIdOpt, annotationIdOpt, workflowHashOpt) match {
case (Some(datasetId), None, None) =>
canAccessDataset(ctx, datasetId)
case (None, Some(annotationId), None) =>
canAccessAnnotation(user, ctx, annotationId)
case (None, None, Some(workflowHash)) =>
canAccessWorkflow(user, workflowHash)
case _ => Fox.failure("Can either test access for dataset or annotation or workflow, not a combination")
}
}

private def canAccessDataset(ctx: DBAccessContext, datasetId: ObjectId): Fox[Boolean] = {
val foundFox = datasetDAO.findOne(datasetId)(ctx)
foundFox.futureBox.map(_.isDefined)
}

private def canAccessAnnotation(user: User, ctx: DBAccessContext, annotationId: String): Fox[Boolean] = {
val foundFox = for {
annotationIdParsed <- ObjectId.fromString(annotationId)
annotation <- annotationDAO.findOne(annotationIdParsed)(GlobalAccessContext)
_ <- bool2Fox(annotation.state != Cancelled)
restrictions <- annotationProvider.restrictionsFor(AnnotationIdentifier(annotation.typ, annotationIdParsed))(ctx)
_ <- restrictions.allowAccess(user)
} yield ()
foundFox.futureBox.map(_.isDefined)
}

private def canAccessWorkflow(user: User, workflowHash: String): Fox[Boolean] = {
val foundFox = for {
_ <- voxelyticsDAO.findWorkflowByHashAndOrganization(user._organization, workflowHash)
} yield ()
foundFox.futureBox.map(_.isDefined)
}

}

0 comments on commit 4a56a33

Please sign in to comment.