diff --git a/README.md b/README.md index a918fc02..be7fa568 100644 --- a/README.md +++ b/README.md @@ -116,13 +116,20 @@ or with automatic redeploy on code changes TODO add documentation for authentication and permission handling. Currently the docs are filed in confluence and hackmd.io. +## Feature Flags + +Feature flags are used to enable or disable certain features. They have to be configured in the configuration file.Feature flags are: + +- `isRowPermissionCheckEnabled`: Enable or disable row permission checks (default: false) +- `isPublicFileServerEnabled`: Enable or disable the public file server. If enabled, files are accessible without authentication (default: false) + ## Highlevel Features -* Content Creation System -* Content Translation System -* Digital Asset Management -* Editing Publishing Workflow -* Workspaces & Custom Projections +- Content Creation System +- Content Translation System +- Digital Asset Management +- Editing Publishing Workflow +- Workspaces & Custom Projections ## License diff --git a/conf-example.json b/conf-example.json index 2ec47c03..d6867090 100644 --- a/conf-example.json +++ b/conf-example.json @@ -4,8 +4,8 @@ "uploadsDirectory": "uploads/", "workingDirectory": "./", "rolePermissionsPath": "./role-permissions.json", - "isPublicFileServer": false, - "isRowPermissionCheck": false, + "isPublicFileServerEnabled": false, + "isRowPermissionCheckEnabled": false, "openApiUrl": "https://my.domain.com/api/docs", "database": { "host": "localhost", diff --git a/src/main/scala/com/campudus/tableaux/Starter.scala b/src/main/scala/com/campudus/tableaux/Starter.scala index 9328cb8b..e6e1ba82 100644 --- a/src/main/scala/com/campudus/tableaux/Starter.scala +++ b/src/main/scala/com/campudus/tableaux/Starter.scala @@ -55,8 +55,12 @@ class Starter extends ScalaVerticle with LazyLogging { val authConfig = config.getJsonObject("auth", Json.obj()) val rolePermissionsPath = getStringDefault(config, "rolePermissionsPath", Starter.DEFAULT_ROLE_PERMISSIONS_PATH) val openApiUrl = Option(getStringDefault(config, "openApiUrl", null)) - val isPublicFileServer = config.getBoolean("isPublicFileServer", Starter.DEFAULT_IS_PUBLIC_FILE_SERVER) - val isRowPermissionCheck = config.getBoolean("isRowPermissionCheck", Starter.DEFAULT_IS_PUBLIC_FILE_SERVER) + + // feature flags + val isPublicFileServerEnabled = + config.getBoolean("isPublicFileServerEnabled", Starter.DEFAULT_IS_PUBLIC_FILE_SERVER) + val isRowPermissionCheckEnabled = + config.getBoolean("isRowPermissionCheckEnabled", Starter.DEFAULT_IS_PUBLIC_FILE_SERVER) val rolePermissions = FileUtils(vertxAccessContainer()).readJsonFile(rolePermissionsPath, Json.emptyObj()) @@ -68,8 +72,8 @@ class Starter extends ScalaVerticle with LazyLogging { uploadsDirectory = uploadsDirectory, rolePermissions = rolePermissions, openApiUrl = openApiUrl, - isPublicFileServer = isPublicFileServer, - isRowPermissionCheck = isRowPermissionCheck + isPublicFileServerEnabled = isPublicFileServerEnabled, + isRowPermissionCheckEnabled = isRowPermissionCheckEnabled ) connection = SQLConnection(vertxAccessContainer(), databaseConfig) diff --git a/src/main/scala/com/campudus/tableaux/TableauxConfig.scala b/src/main/scala/com/campudus/tableaux/TableauxConfig.scala index d874d252..6bf472a9 100644 --- a/src/main/scala/com/campudus/tableaux/TableauxConfig.scala +++ b/src/main/scala/com/campudus/tableaux/TableauxConfig.scala @@ -15,8 +15,8 @@ class TableauxConfig( uploadsDirectory: String, val rolePermissions: JsonObject, val openApiUrl: Option[String] = None, - val isPublicFileServer: Boolean = false, - val isRowPermissionCheck: Boolean = false + val isPublicFileServerEnabled: Boolean = false, + var isRowPermissionCheckEnabled: Boolean = false ) extends VertxAccess { def uploadsDirectoryPath(): Path = { diff --git a/src/main/scala/com/campudus/tableaux/database/model/TableauxModel.scala b/src/main/scala/com/campudus/tableaux/database/model/TableauxModel.scala index 762d9637..7896fa47 100644 --- a/src/main/scala/com/campudus/tableaux/database/model/TableauxModel.scala +++ b/src/main/scala/com/campudus/tableaux/database/model/TableauxModel.scala @@ -33,10 +33,10 @@ object TableauxModel { type RowPermissionSeq = Seq[String] - def apply(connection: DatabaseConnection, structureModel: StructureModel)( + def apply(connection: DatabaseConnection, structureModel: StructureModel, config: TableauxConfig)( implicit roleModel: RoleModel ): TableauxModel = { - new TableauxModel(connection, structureModel) + new TableauxModel(connection, structureModel, config) } } @@ -69,6 +69,8 @@ sealed trait StructureDelegateModel extends DatabaseQuery { protected val structureModel: StructureModel + protected val config: TableauxConfig + protected[this] implicit def roleModel: RoleModel def createTable(name: String, hidden: Boolean)(implicit user: TableauxUser): Future[Table] = { @@ -123,7 +125,8 @@ sealed trait StructureDelegateModel extends DatabaseQuery { class TableauxModel( override protected[this] val connection: DatabaseConnection, - override protected[this] val structureModel: StructureModel + override protected[this] val structureModel: StructureModel, + override protected[this] val config: TableauxConfig )(override implicit val roleModel: RoleModel) extends DatabaseQuery with StructureDelegateModel { @@ -1004,11 +1007,18 @@ class TableauxModel( } } - rowPermissions <- retrieveRowModel.retrieveRowPermissions(column.table.id, rowId) - _ <- roleModel.checkAuthorization(ViewRow, ComparisonObjects(rowPermissions), isInternalCall) - filteredValue <- removeUnauthorizedLinkAndConcatValues(column, value, true) + resultValue <- + if (config.isRowPermissionCheckEnabled) { + for { + rowPermissions <- retrieveRowModel.retrieveRowPermissions(column.table.id, rowId) + _ <- roleModel.checkAuthorization(ViewRow, ComparisonObjects(rowPermissions), isInternalCall) + filteredValue <- removeUnauthorizedLinkAndConcatValues(column, value, true) + } yield filteredValue + } else { + Future.successful(value) + } } yield { - Cell(column, rowId, filteredValue) + Cell(column, rowId, resultValue) } } @@ -1153,9 +1163,16 @@ class TableauxModel( isInternalCall = false ) row <- retrieveRow(table, filteredColumns, rowId) - // _ <- roleModel.checkAuthorization(ViewRow, ComparisonObjects(row.rowPermissions), isInternalCall = false) - // mutatedRow <- removeUnauthorizedLinkAndConcatValuesFromRow(filteredColumns, row) - } yield row + resultRow <- + if (config.isRowPermissionCheckEnabled) { + for { + _ <- roleModel.checkAuthorization(ViewRow, ComparisonObjects(row.rowPermissions), isInternalCall = false) + mutatedRow <- removeUnauthorizedLinkAndConcatValuesFromRow(filteredColumns, row) + } yield mutatedRow + } else { + Future.successful(row) + } + } yield resultRow } private def retrieveRow(table: Table, columns: Seq[ColumnType[_]], rowId: RowId)( @@ -1210,10 +1227,14 @@ class TableauxModel( linkDirection ) rowSeq <- mapRawRows(table, representingColumns, rawRows) - // rowSeqWithoutUnauthorizedValues <- removeUnauthorizedForeignValuesFromRows(representingColumns, rowSeq) + resultRowSeq <- + if (config.isRowPermissionCheckEnabled) { + removeUnauthorizedForeignValuesFromRows(representingColumns, rowSeq) + } else { + Future.successful(rowSeq) + } } yield { - val filteredForeignRows = - roleModel.filterDomainObjects(ViewRow, rowSeq, ComparisonObjects(), false) + val filteredForeignRows = roleModel.filterDomainObjects(ViewRow, resultRowSeq, ComparisonObjects(), false) val rowsSeq = RowSeq(filteredForeignRows, Page(pagination, Some(totalSize))) copyFirstColumnOfRowsSeq(rowsSeq) } @@ -1239,9 +1260,14 @@ class TableauxModel( isInternalCall = false ) rowSeq <- retrieveRows(table, filteredColumns, finalFlagOpt, archivedFlagOpt, pagination) - // mutatedRows <- removeUnauthorizedForeignValuesFromRows(filteredColumns, rowSeq.rows) + resultRows <- + if (config.isRowPermissionCheckEnabled) { + removeUnauthorizedForeignValuesFromRows(filteredColumns, rowSeq.rows) + } else { + Future.successful(rowSeq.rows) + } } yield { - val filteredRows = roleModel.filterDomainObjects(ViewRow, rowSeq.rows, ComparisonObjects(), false) + val filteredRows = roleModel.filterDomainObjects(ViewRow, resultRows, ComparisonObjects(), false) RowSeq(filteredRows, rowSeq.page) } } @@ -1539,8 +1565,15 @@ class TableauxModel( typeOpt: Option[String] )(implicit user: TableauxUser): Future[Seq[History]] = { for { - rowPermissions <- retrieveRowModel.retrieveRowPermissions(table.id, rowId) - _ <- roleModel.checkAuthorization(ViewRow, ComparisonObjects(rowPermissions)) + _ <- + if (config.isRowPermissionCheckEnabled) { + for { + rowPermissions <- retrieveRowModel.retrieveRowPermissions(table.id, rowId) + _ <- roleModel.checkAuthorization(ViewRow, ComparisonObjects(rowPermissions)) + } yield () + } else { + Future.successful(()) + } column <- retrieveColumn(table, columnId) _ <- checkColumnTypeForLangtag(column, langtagOpt) _ <- roleModel.checkAuthorization(ViewCellValue, ComparisonObjects(table, column)) @@ -1555,8 +1588,15 @@ class TableauxModel( typeOpt: Option[String] )(implicit user: TableauxUser): Future[Seq[History]] = { for { - rowPermissions <- retrieveRowModel.retrieveRowPermissions(table.id, rowId) - _ <- roleModel.checkAuthorization(ViewRow, ComparisonObjects(rowPermissions)) + _ <- + if (config.isRowPermissionCheckEnabled) { + for { + rowPermissions <- retrieveRowModel.retrieveRowPermissions(table.id, rowId) + _ <- roleModel.checkAuthorization(ViewRow, ComparisonObjects(rowPermissions)) + } yield () + } else { + Future.successful(()) + } columns <- retrieveColumns(table) cellHistorySeq <- retrieveHistoryModel.retrieveRow(table, rowId, langtagOpt, typeOpt) filteredCellHistorySeq = filterCellHistoriesForColumns(cellHistorySeq, columns) diff --git a/src/main/scala/com/campudus/tableaux/router/MediaRouter.scala b/src/main/scala/com/campudus/tableaux/router/MediaRouter.scala index 82df4dca..00ecb0a3 100644 --- a/src/main/scala/com/campudus/tableaux/router/MediaRouter.scala +++ b/src/main/scala/com/campudus/tableaux/router/MediaRouter.scala @@ -51,7 +51,7 @@ class MediaRouter(override val config: TableauxConfig, val controller: MediaCont router.get(folders).handler(retrieveRootFolder) router.getWithRegex(folder).handler(retrieveFolder) router.getWithRegex(file).handler(retrieveFile) - if (!config.isPublicFileServer) { + if (!config.isPublicFileServerEnabled) { router.getWithRegex(fileLangStatic).handler(serveFile) } @@ -84,7 +84,7 @@ class MediaRouter(override val config: TableauxConfig, val controller: MediaCont val router = Router.router(vertx) // RETRIEVE - if (config.isPublicFileServer) { + if (config.isPublicFileServerEnabled) { router.getWithRegex(fileLangStatic).handler(serveFile) } router diff --git a/src/main/scala/com/campudus/tableaux/router/RouterRegistry.scala b/src/main/scala/com/campudus/tableaux/router/RouterRegistry.scala index a6f245a2..c5004f67 100644 --- a/src/main/scala/com/campudus/tableaux/router/RouterRegistry.scala +++ b/src/main/scala/com/campudus/tableaux/router/RouterRegistry.scala @@ -34,7 +34,7 @@ object RouterRegistry extends LazyLogging { val systemModel = SystemModel(dbConnection) val structureModel = StructureModel(dbConnection) - val tableauxModel = TableauxModel(dbConnection, structureModel) + val tableauxModel = TableauxModel(dbConnection, structureModel, tableauxConfig) val folderModel = FolderModel(dbConnection) val fileModel = FileModel(dbConnection) val attachmentModel = AttachmentModel(dbConnection, fileModel) diff --git a/src/test/scala/com/campudus/tableaux/api/auth/SystemControllerAuthTest.scala b/src/test/scala/com/campudus/tableaux/api/auth/SystemControllerAuthTest.scala index 03b16244..2df87786 100644 --- a/src/test/scala/com/campudus/tableaux/api/auth/SystemControllerAuthTest.scala +++ b/src/test/scala/com/campudus/tableaux/api/auth/SystemControllerAuthTest.scala @@ -27,7 +27,7 @@ trait SystemControllerAuthTest extends TableauxTestBase { val dbConnection = DatabaseConnection(this.vertxAccess(), sqlConnection) val systemModel = SystemModel(dbConnection) val structureModel = StructureModel(dbConnection) - val tableauxModel = TableauxModel(dbConnection, structureModel) + val tableauxModel = TableauxModel(dbConnection, structureModel, tableauxConfig) val serviceModel = ServiceModel(dbConnection) SystemController(tableauxConfig, systemModel, tableauxModel, structureModel, serviceModel, roleModel) diff --git a/src/test/scala/com/campudus/tableaux/api/auth/TableauxControllerAuthTest.scala b/src/test/scala/com/campudus/tableaux/api/auth/TableauxControllerAuthTest.scala index 0149c280..352a0f45 100644 --- a/src/test/scala/com/campudus/tableaux/api/auth/TableauxControllerAuthTest.scala +++ b/src/test/scala/com/campudus/tableaux/api/auth/TableauxControllerAuthTest.scala @@ -41,7 +41,7 @@ trait TableauxControllerAuthTest extends TableauxTestBase { val sqlConnection = SQLConnection(this.vertxAccess(), databaseConfig) val dbConnection = DatabaseConnection(this.vertxAccess(), sqlConnection) val structureModel = StructureModel(dbConnection) - val tableauxModel = TableauxModel(dbConnection, structureModel) + val tableauxModel = TableauxModel(dbConnection, structureModel, tableauxConfig) TableauxController(tableauxConfig, tableauxModel, roleModel) } diff --git a/src/test/scala/com/campudus/tableaux/api/content/RetrieveTest.scala b/src/test/scala/com/campudus/tableaux/api/content/RetrieveTest.scala index 3b2691f0..ca866fa1 100644 --- a/src/test/scala/com/campudus/tableaux/api/content/RetrieveTest.scala +++ b/src/test/scala/com/campudus/tableaux/api/content/RetrieveTest.scala @@ -309,7 +309,7 @@ class RetrieveRowsTest extends TableauxTestBase { val sqlConnection = SQLConnection(this.vertxAccess(), databaseConfig) val dbConnection = DatabaseConnection(this.vertxAccess(), sqlConnection) val structureModel = StructureModel(dbConnection) - val tableauxModel = TableauxModel(dbConnection, structureModel) + val tableauxModel = TableauxModel(dbConnection, structureModel, tableauxConfig) TableauxController(tableauxConfig, tableauxModel, roleModel) } diff --git a/src/test/scala/com/campudus/tableaux/api/permission/RowPermissionsTest.scala b/src/test/scala/com/campudus/tableaux/api/permission/RowPermissionsTest.scala index f88a7360..e7320092 100644 --- a/src/test/scala/com/campudus/tableaux/api/permission/RowPermissionsTest.scala +++ b/src/test/scala/com/campudus/tableaux/api/permission/RowPermissionsTest.scala @@ -64,10 +64,12 @@ trait TestHelper extends TableauxTestBase { def createTableauxController(roleConfig: String = defaultViewTableRole)( implicit roleModel: RoleModel = initRoleModel(roleConfig) ): TableauxController = { + // mutate TableauxConfig to test with enabled row permission check + tableauxConfig.isRowPermissionCheckEnabled = true val sqlConnection = SQLConnection(this.vertxAccess(), databaseConfig) val dbConnection = DatabaseConnection(this.vertxAccess(), sqlConnection) val structureModel = StructureModel(dbConnection) - val tableauxModel = TableauxModel(dbConnection, structureModel) + val tableauxModel = TableauxModel(dbConnection, structureModel, tableauxConfig) TableauxController(tableauxConfig, tableauxModel, roleModel) } diff --git a/src/test/scala/com/campudus/tableaux/cache/CacheVerticleTest.scala b/src/test/scala/com/campudus/tableaux/cache/CacheVerticleTest.scala index 67a5e6fe..59fb4882 100644 --- a/src/test/scala/com/campudus/tableaux/cache/CacheVerticleTest.scala +++ b/src/test/scala/com/campudus/tableaux/cache/CacheVerticleTest.scala @@ -36,7 +36,7 @@ class CacheVerticleTest extends VertxAccess with TestAssertionHelper { "workingDirectory", "uploadsDirectory", rolePermissions, - isRowPermissionCheck = false + isRowPermissionCheckEnabled = false ) private var deploymentId: String = "CacheVerticleTest" diff --git a/src/test/scala/com/campudus/tableaux/controller/TableauxControllerTest.scala b/src/test/scala/com/campudus/tableaux/controller/TableauxControllerTest.scala index 416d0faf..bd4b1137 100644 --- a/src/test/scala/com/campudus/tableaux/controller/TableauxControllerTest.scala +++ b/src/test/scala/com/campudus/tableaux/controller/TableauxControllerTest.scala @@ -23,7 +23,7 @@ class TableauxControllerTest extends TableauxTestBase { val dbConnection = DatabaseConnection(this.vertxAccess(), sqlConnection) implicit val roleModel = RoleModel(tableauxConfig.rolePermissions) - val model = TableauxModel(dbConnection, StructureModel(dbConnection)) + val model = TableauxModel(dbConnection, StructureModel(dbConnection), tableauxConfig) TableauxController(tableauxConfig, model, roleModel) } diff --git a/src/test/scala/com/campudus/tableaux/testtools/TableauxTestBase.scala b/src/test/scala/com/campudus/tableaux/testtools/TableauxTestBase.scala index 7273ea6a..f878bc24 100644 --- a/src/test/scala/com/campudus/tableaux/testtools/TableauxTestBase.scala +++ b/src/test/scala/com/campudus/tableaux/testtools/TableauxTestBase.scala @@ -98,7 +98,7 @@ trait TableauxTestBase config.getString("workingDirectory"), config.getString("uploadsDirectory"), rolePermissions, - isRowPermissionCheck = false + isRowPermissionCheckEnabled = false ) val async = context.async()