Skip to content

Commit

Permalink
Merge branch 'master' of github.com:scalableminds/webknossos into pri…
Browse files Browse the repository at this point in the history
…cing

* 'master' of github.com:scalableminds/webknossos:
  Automatically open (and close) quick select settings when labeling in… (#6706)
  Add Terms of Service Acceptance Concept (#6632)
  Fix crash in publication page and add error boundaries (#6700)
  temporarily disable vx related polling (#6702)
  add protected and private modifiers to DAO hierarchy (#6698)
  • Loading branch information
hotzenklotz committed Dec 20, 2022
2 parents 067cf5a + df08e63 commit b3f37d2
Show file tree
Hide file tree
Showing 68 changed files with 903 additions and 395 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- Voxelytics workflows can now be viewed by anyone with the link who is in the right organization. [#6622](https://github.com/scalableminds/webknossos/pull/6622)
- webKnossos is now able to recover from a lost webGL context. [#6663](https://github.com/scalableminds/webknossos/pull/6663)
- Bulk task creation now needs the taskTypeId, the task type summary will no longer be accepted. [#6640](https://github.com/scalableminds/webknossos/pull/6640)
- Error handling and reporting is more robust now. [#6700](https://github.com/scalableminds/webknossos/pull/6700)
- The Quick-Select settings are opened (and closed) automatically when labeling with the preview mode. That way, bulk labelings with preview mode don't require constantly opening the settings manually. [#6706](https://github.com/scalableminds/webknossos/pull/6706)
- Redesigned organization page to include more infos on organization users, storage, webKnossos plan and provided opportunities to upgrade. [#6602](https://github.com/scalableminds/webknossos/pull/6602)

### Fixed
Expand All @@ -37,6 +39,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released
- Fixed access of remote datasets using the Amazon S3 protocol [#6679](https://github.com/scalableminds/webknossos/pull/6679)
- Fixed a bug in line measurement that would lead to an infinite loop. [#6689](https://github.com/scalableminds/webknossos/pull/6689)
- Fixed a bug where malformed json files could lead to uncaught exceptions.[#6691](https://github.com/scalableminds/webknossos/pull/6691)
- Fixed rare crash in publications page. [#6700](https://github.com/scalableminds/webknossos/pull/6700)
- Respect the config value mail.smtp.auth (used to be ignored, always using true) [#6692](https://github.com/scalableminds/webknossos/pull/6692)

### Removed
Expand Down
1 change: 1 addition & 0 deletions MIGRATIONS.unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ User-facing changes are documented in the [changelog](CHANGELOG.released.md).

- [091-folders.sql](conf/evolutions/091-folders.sql)
- [092-oidc.sql](conf/evolutions/092-oidc.sql)
- [093-terms-of-service.sql](conf/evolutions/093-terms-of-service.sql)
1 change: 0 additions & 1 deletion app/WebKnossosModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ class WebKnossosModule extends AbstractModule {
bind(classOf[UserService]).asEagerSingleton()
bind(classOf[TaskService]).asEagerSingleton()
bind(classOf[UserDAO]).asEagerSingleton()
bind(classOf[UserTeamRolesDAO]).asEagerSingleton()
bind(classOf[UserExperiencesDAO]).asEagerSingleton()
bind(classOf[UserDataSetConfigurationDAO]).asEagerSingleton()
bind(classOf[UserCache]).asEagerSingleton()
Expand Down
12 changes: 10 additions & 2 deletions app/controllers/AuthenticationController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,14 @@ class AuthenticationController @Inject()(
val passwordInfo: PasswordInfo =
password.map(passwordHasher.hash).getOrElse(userService.getOpenIdConnectPasswordInfo)
for {
user <- userService.insert(organization._id, email, firstName, lastName, autoActivate, passwordInfo) ?~> "user.creation.failed"
user <- userService.insert(organization._id,
email,
firstName,
lastName,
autoActivate,
passwordInfo,
isAdmin = false,
isOrganizationOwner = false) ?~> "user.creation.failed"
multiUser <- multiUserDAO.findOne(user._multiUser)(GlobalAccessContext)
_ = analyticsService.track(SignupEvent(user, inviteBox.isDefined))
_ <- Fox.runIf(inviteBox.isDefined)(Fox.runOptional(inviteBox.toOption)(i =>
Expand Down Expand Up @@ -574,7 +581,8 @@ class AuthenticationController @Inject()(
lastName,
isActive = true,
passwordHasher.hash(signUpData.password),
isAdmin = true) ?~> "user.creation.failed"
isAdmin = true,
isOrganizationOwner = true) ?~> "user.creation.failed"
_ = analyticsService.track(SignupEvent(user, hadInvite = false))
multiUser <- multiUserDAO.findOne(user._multiUser)
dataStoreToken <- bearerTokenAuthenticatorService
Expand Down
8 changes: 4 additions & 4 deletions app/controllers/InitialDataController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ class InitialDataController @Inject()(initialDataService: InitialDataService, si
class InitialDataService @Inject()(userService: UserService,
userDAO: UserDAO,
multiUserDAO: MultiUserDAO,
userTeamRolesDAO: UserTeamRolesDAO,
userExperiencesDAO: UserExperiencesDAO,
taskTypeDAO: TaskTypeDAO,
dataStoreDAO: DataStoreDAO,
Expand Down Expand Up @@ -102,6 +101,7 @@ Samplecountry
Json.obj(),
userService.createLoginInfo(userId),
isAdmin = true,
isOrganizationOwner = true,
isDatasetManager = true,
isUnlisted = false,
isDeactivated = false,
Expand All @@ -123,6 +123,7 @@ Samplecountry
Json.obj(),
userService.createLoginInfo(userId2),
isAdmin = false,
isOrganizationOwner = false,
isDatasetManager = false,
isUnlisted = false,
isDeactivated = false,
Expand Down Expand Up @@ -187,9 +188,8 @@ Samplecountry
_ <- multiUserDAO.insertOne(multiUser)
_ <- userDAO.insertOne(user)
_ <- userExperiencesDAO.updateExperiencesForUser(user, Map("sampleExp" -> 10))
_ <- userTeamRolesDAO.insertTeamMembership(
user._id,
TeamMembership(organizationTeam._id, isTeamManager = isTeamManager))
_ <- userDAO.insertTeamMembership(user._id,
TeamMembership(organizationTeam._id, isTeamManager = isTeamManager))
_ = logger.info("Inserted default user")
} yield ()
}
Expand Down
35 changes: 35 additions & 0 deletions app/controllers/OrganizationController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,41 @@ class OrganizationController @Inject()(organizationDAO: OrganizationDAO,
Ok(Json.toJson(conf.WebKnossos.operatorData))
}

def getTermsOfService: Action[AnyContent] = Action {
Ok(
Json.obj(
"version" -> conf.WebKnossos.TermsOfService.version,
"enabled" -> conf.WebKnossos.TermsOfService.enabled,
"url" -> conf.WebKnossos.TermsOfService.url
))
}

def termsOfServiceAcceptanceNeeded: Action[AnyContent] = sil.SecuredAction.async { implicit request =>
for {
organization <- organizationDAO.findOne(request.identity._organization)
needsAcceptance = conf.WebKnossos.TermsOfService.enabled &&
organization.lastTermsOfServiceAcceptanceVersion < conf.WebKnossos.TermsOfService.version
acceptanceDeadline = conf.WebKnossos.TermsOfService.acceptanceDeadline
deadlinePassed = acceptanceDeadline.toEpochMilli < System.currentTimeMillis()
} yield
Ok(
Json.obj(
"acceptanceNeeded" -> needsAcceptance,
"acceptanceDeadline" -> acceptanceDeadline.toEpochMilli,
"acceptanceDeadlinePassed" -> deadlinePassed
))
}

def acceptTermsOfService(version: Int): Action[AnyContent] = sil.SecuredAction.async { implicit request =>
for {
_ <- bool2Fox(request.identity.isOrganizationOwner) ?~> "termsOfService.onlyOrganizationOwner"
_ <- bool2Fox(conf.WebKnossos.TermsOfService.enabled) ?~> "termsOfService.notEnabled"
requiredVersion = conf.WebKnossos.TermsOfService.version
_ <- bool2Fox(version == requiredVersion) ?~> Messages("termsOfService.versionMismatch", requiredVersion, version)
_ <- organizationDAO.acceptTermsOfService(request.identity._organization, version, System.currentTimeMillis())
} yield Ok
}

def update(organizationName: String): Action[JsValue] = sil.SecuredAction.async(parse.json) { implicit request =>
withJsonBodyUsing(organizationUpdateReads) {
case (displayName, newUserMailingList) =>
Expand Down
14 changes: 6 additions & 8 deletions app/controllers/TeamController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,19 @@ import com.mohiva.play.silhouette.api.Silhouette
import com.scalableminds.util.tools.Fox
import io.swagger.annotations._
import models.team._
import models.user.UserTeamRolesDAO
import models.user.UserDAO
import oxalis.security.WkEnv
import play.api.i18n.Messages
import play.api.libs.json._
import utils.ObjectId
import javax.inject.Inject
import play.api.mvc.{Action, AnyContent}
import utils.ObjectId

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

@Api
class TeamController @Inject()(teamDAO: TeamDAO,
userTeamRolesDAO: UserTeamRolesDAO,
teamService: TeamService,
sil: Silhouette[WkEnv])(implicit ec: ExecutionContext)
class TeamController @Inject()(teamDAO: TeamDAO, userDAO: UserDAO, teamService: TeamService, sil: Silhouette[WkEnv])(
implicit ec: ExecutionContext)
extends Controller {

private def teamNameReads: Reads[String] =
Expand Down Expand Up @@ -46,7 +44,7 @@ class TeamController @Inject()(teamDAO: TeamDAO,
_ <- bool2Fox(!team.isOrganizationTeam) ?~> "team.delete.organizationTeam" ~> FORBIDDEN
_ <- teamService.assertNoReferences(teamIdValidated) ?~> "team.delete.inUse" ~> FORBIDDEN
_ <- teamDAO.deleteOne(teamIdValidated)
_ <- userTeamRolesDAO.removeTeamFromAllUsers(teamIdValidated)
_ <- userDAO.removeTeamFromAllUsers(teamIdValidated)
_ <- teamDAO.removeTeamFromAllDatasetsAndFolders(teamIdValidated)
} yield JsonOk(Messages("team.deleted"))
}
Expand Down
28 changes: 12 additions & 16 deletions app/models/annotation/Annotation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,12 @@ class AnnotationLayerDAO @Inject()(SQLClient: SQLClient)(implicit ec: ExecutionC
class AnnotationDAO @Inject()(sqlClient: SQLClient, annotationLayerDAO: AnnotationLayerDAO)(
implicit ec: ExecutionContext)
extends SQLDAO[Annotation, AnnotationsRow, Annotations](sqlClient) {
val collection = Annotations
protected val collection = Annotations

def idColumn(x: Annotations): Rep[String] = x._Id
def isDeletedColumn(x: Annotations): Rep[Boolean] = x.isdeleted
protected def idColumn(x: Annotations): Rep[String] = x._Id
protected def isDeletedColumn(x: Annotations): Rep[Boolean] = x.isdeleted

def parse(r: AnnotationsRow): Fox[Annotation] =
protected def parse(r: AnnotationsRow): Fox[Annotation] =
for {
state <- AnnotationState.fromString(r.state).toFox
typ <- AnnotationType.fromString(r.typ).toFox
Expand Down Expand Up @@ -180,7 +180,8 @@ class AnnotationDAO @Inject()(sqlClient: SQLClient, annotationLayerDAO: Annotati
)
}

override def anonymousReadAccessQ(sharingToken: Option[String]) = s"visibility = '${AnnotationVisibility.Public}'"
override protected def anonymousReadAccessQ(sharingToken: Option[String]) =
s"visibility = '${AnnotationVisibility.Public}'"

private def listAccessQ(requestingUserId: ObjectId): String =
s"""
Expand All @@ -206,7 +207,7 @@ class AnnotationDAO @Inject()(sqlClient: SQLClient, annotationLayerDAO: Annotati
)
"""

override def readAccessQ(requestingUserId: ObjectId): String =
override protected def readAccessQ(requestingUserId: ObjectId): String =
s"""(
visibility = '${AnnotationVisibility.Public}'
or (visibility = '${AnnotationVisibility.Internal}'
Expand All @@ -218,12 +219,12 @@ class AnnotationDAO @Inject()(sqlClient: SQLClient, annotationLayerDAO: Annotati
in (select _organization from webknossos.users_ where _id = '$requestingUserId' and isAdmin)
)"""

override def deleteAccessQ(requestingUserId: ObjectId) =
override protected def deleteAccessQ(requestingUserId: ObjectId) =
s"""(_team in (select _team from webknossos.user_team_roles where isTeamManager and _user = '$requestingUserId') or _user = '$requestingUserId'
or (select _organization from webknossos.teams where webknossos.teams._id = _team)
in (select _organization from webknossos.users_ where _id = '$requestingUserId' and isAdmin))"""

override def updateAccessQ(requestingUserId: ObjectId): String =
override protected def updateAccessQ(requestingUserId: ObjectId): String =
deleteAccessQ(requestingUserId)

// read operations
Expand Down Expand Up @@ -543,20 +544,16 @@ class AnnotationDAO @Inject()(sqlClient: SQLClient, annotationLayerDAO: Annotati
_ <- run(
sqlu"insert into webknossos.annotation_contributors (_annotation, _user) values($id, $userId) on conflict do nothing")
} yield ()
}

class SharedAnnotationsDAO @Inject()(annotationDAO: AnnotationDAO, sqlClient: SQLClient)(implicit ec: ExecutionContext)
extends SimpleSQLDAO(sqlClient) {

// Does not use access query (because they dont support prefixes). Use only after separate access check!
def findAllSharedForTeams(teams: List[ObjectId]): Fox[List[Annotation]] =
for {
result <- run(
sql"""select distinct #${annotationDAO.columnsWithPrefix("a.")} from webknossos.annotations_ a
sql"""select distinct #${columnsWithPrefix("a.")} from webknossos.annotations_ a
join webknossos.annotation_sharedTeams l on a._id = l._annotation
where l._team in #${writeStructTupleWithQuotes(teams.map(t => sanitize(t.toString)))}"""
.as[AnnotationsRow])
parsed <- Fox.combined(result.toList.map(annotationDAO.parse))
parsed <- Fox.combined(result.toList.map(parse))
} yield parsed

def updateTeamsForSharedAnnotation(annotationId: ObjectId, teams: List[ObjectId])(
Expand All @@ -568,11 +565,10 @@ class SharedAnnotationsDAO @Inject()(annotationDAO: AnnotationDAO, sqlClient: SQ

val composedQuery = DBIO.sequence(List(clearQuery) ++ insertQueries)
for {
_ <- annotationDAO.assertUpdateAccess(annotationId)
_ <- assertUpdateAccess(annotationId)
_ <- run(composedQuery.transactionally.withTransactionIsolation(Serializable),
retryCount = 50,
retryIfErrorContains = List(transactionSerializationError))
} yield ()
}

}
10 changes: 5 additions & 5 deletions app/models/annotation/AnnotationPrivateLink.scala
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ class AnnotationPrivateLinkService @Inject()()(implicit ec: ExecutionContext) {

class AnnotationPrivateLinkDAO @Inject()(sqlClient: SQLClient)(implicit ec: ExecutionContext)
extends SQLDAO[AnnotationPrivateLink, AnnotationPrivatelinksRow, AnnotationPrivatelinks](sqlClient) {
val collection = AnnotationPrivatelinks
protected val collection = AnnotationPrivatelinks

def idColumn(x: AnnotationPrivatelinks): Rep[String] = x._Id
protected def idColumn(x: AnnotationPrivatelinks): Rep[String] = x._Id

def isDeletedColumn(x: AnnotationPrivatelinks): Rep[Boolean] = x.isdeleted
protected def isDeletedColumn(x: AnnotationPrivatelinks): Rep[Boolean] = x.isdeleted

def parse(r: AnnotationPrivatelinksRow): Fox[AnnotationPrivateLink] =
protected def parse(r: AnnotationPrivatelinksRow): Fox[AnnotationPrivateLink] =
Fox.successful(
AnnotationPrivateLink(
ObjectId(r._Id),
Expand All @@ -62,7 +62,7 @@ class AnnotationPrivateLinkDAO @Inject()(sqlClient: SQLClient)(implicit ec: Exec
)
)

override def readAccessQ(requestingUserId: ObjectId): String =
override protected def readAccessQ(requestingUserId: ObjectId): String =
s"""(_annotation in (select _id from webknossos.annotations_ where _user = '${requestingUserId.id}'))"""

def insertOne(aPL: AnnotationPrivateLink): Fox[Unit] =
Expand Down
7 changes: 3 additions & 4 deletions app/models/annotation/AnnotationService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,7 @@ class AnnotationService @Inject()(
nmlWriter: NmlWriter,
temporaryFileCreator: TemporaryFileCreator,
meshDAO: MeshDAO,
meshService: MeshService,
sharedAnnotationsDAO: SharedAnnotationsDAO
meshService: MeshService
)(implicit ec: ExecutionContext, val materializer: Materializer)
extends BoxImplicits
with FoxImplicits
Expand Down Expand Up @@ -589,11 +588,11 @@ class AnnotationService @Inject()(

// Does not use access query (because they dont support prefixes). Use only after separate access check!
def sharedAnnotationsFor(userTeams: List[ObjectId]): Fox[List[Annotation]] =
sharedAnnotationsDAO.findAllSharedForTeams(userTeams)
annotationDAO.findAllSharedForTeams(userTeams)

def updateTeamsForSharedAnnotation(annotationId: ObjectId, teams: List[ObjectId])(
implicit ctx: DBAccessContext): Fox[Unit] =
sharedAnnotationsDAO.updateTeamsForSharedAnnotation(annotationId, teams)
annotationDAO.updateTeamsForSharedAnnotation(annotationId, teams)

def zipAnnotations(annotations: List[Annotation], zipFileName: String, skipVolumeData: Boolean)(
implicit
Expand Down
8 changes: 4 additions & 4 deletions app/models/annotation/TracingStore.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,12 @@ class TracingStoreService @Inject()(tracingStoreDAO: TracingStoreDAO, rpc: RPC)(

class TracingStoreDAO @Inject()(sqlClient: SQLClient)(implicit ec: ExecutionContext)
extends SQLDAO[TracingStore, TracingstoresRow, Tracingstores](sqlClient) {
val collection = Tracingstores
protected val collection = Tracingstores

def idColumn(x: Tracingstores): Rep[String] = x.name
def isDeletedColumn(x: Tracingstores): Rep[Boolean] = x.isdeleted
protected def idColumn(x: Tracingstores): Rep[String] = x.name
protected def isDeletedColumn(x: Tracingstores): Rep[Boolean] = x.isdeleted

def parse(r: TracingstoresRow): Fox[TracingStore] =
protected def parse(r: TracingstoresRow): Fox[TracingStore] =
Fox.successful(
TracingStore(
r.name,
Expand Down
12 changes: 6 additions & 6 deletions app/models/binary/DataSet.scala
Original file line number Diff line number Diff line change
Expand Up @@ -62,11 +62,11 @@ class DataSetDAO @Inject()(sqlClient: SQLClient,
dataSetDataLayerDAO: DataSetDataLayerDAO,
organizationDAO: OrganizationDAO)(implicit ec: ExecutionContext)
extends SQLDAO[DataSet, DatasetsRow, Datasets](sqlClient) {
val collection = Datasets
protected val collection = Datasets

def idColumn(x: Datasets): Rep[String] = x._Id
protected def idColumn(x: Datasets): Rep[String] = x._Id

def isDeletedColumn(x: Datasets): Rep[Boolean] = x.isdeleted
protected def isDeletedColumn(x: Datasets): Rep[Boolean] = x.isdeleted

private def parseScaleOpt(literalOpt: Option[String]): Fox[Option[Vec3Double]] = literalOpt match {
case Some(literal) =>
Expand All @@ -79,7 +79,7 @@ class DataSetDAO @Inject()(sqlClient: SQLClient,
private def writeScaleLiteral(scale: Vec3Double): String =
writeStructTuple(List(scale.x, scale.y, scale.z).map(_.toString))

def parse(r: DatasetsRow): Fox[DataSet] =
protected def parse(r: DatasetsRow): Fox[DataSet] =
for {
scale <- parseScaleOpt(r.scale)
defaultViewConfigurationOpt <- Fox.runOptional(r.defaultviewconfiguration)(
Expand Down Expand Up @@ -405,7 +405,7 @@ class DataSetDAO @Inject()(sqlClient: SQLClient,

class DataSetResolutionsDAO @Inject()(sqlClient: SQLClient)(implicit ec: ExecutionContext)
extends SimpleSQLDAO(sqlClient) {
def parseRow(row: DatasetResolutionsRow): Fox[Vec3Int] =
private def parseRow(row: DatasetResolutionsRow): Fox[Vec3Int] =
for {
resolution <- Vec3Int.fromList(parseArrayTuple(row.resolution).map(_.toInt)) ?~> "could not parse resolution"
} yield resolution
Expand Down Expand Up @@ -447,7 +447,7 @@ class DataSetDataLayerDAO @Inject()(sqlClient: SQLClient, dataSetResolutionsDAO:
implicit ec: ExecutionContext)
extends SimpleSQLDAO(sqlClient) {

def parseRow(row: DatasetLayersRow, dataSetId: ObjectId, skipResolutions: Boolean = false): Fox[DataLayer] = {
private def parseRow(row: DatasetLayersRow, dataSetId: ObjectId, skipResolutions: Boolean): Fox[DataLayer] = {
val result: Fox[Fox[DataLayer]] = for {
category <- Category.fromString(row.category).toFox ?~> "Could not parse Layer Category"
boundingBox <- BoundingBox
Expand Down
Loading

0 comments on commit b3f37d2

Please sign in to comment.