Skip to content

Commit

Permalink
Add password protection for database (scalacenter#708)
Browse files Browse the repository at this point in the history
* add password protection for database

* update CONTRIBUTING.md and add configuration to hostname

* revert oauth secrets removal, fix ci again
  • Loading branch information
rochala authored Jan 11, 2023
1 parent ad0fbe6 commit 4d74dc6
Show file tree
Hide file tree
Showing 12 changed files with 101 additions and 50 deletions.
11 changes: 11 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@ contact one of the maintainers.

Scastie production environment is used for internal deployment. Its configuration is present at `./deployment/production.conf`.

Production requires `mongodb-prod.conf` file to be in the same directory as `production.conf` configuration file.
The template for this file is located at `./deployment/mongodb.template.conf`

It also requires OAuth2 configuration at `oauth2-prod.conf` in the same directory as `production.conf`

<details>
<summary>Instructions</summary>

Expand All @@ -185,6 +190,7 @@ sbt
In case docker images are not published e.g. when staging for the current version is not deployed,
it must be done before proceeding with the production deployment. It is done by running:


```
sbt> publishContainers
```
Expand All @@ -202,6 +208,11 @@ sbt> deploy
Scastie also has a staging environment. The deployment can be done by running the task `deployStaging`.
It will do normal deployment, but with Staging environment configuration file located at: `./deployment/staging.conf`

Staging requires `mongodb-staging.conf` file to be in the same directory as `staging.conf` configuration file.
The template for this file is located at `./deployment/mongodb.template.conf`

It also requires OAuth2 configuration at `oauth2-staging.conf` in the same directory as `staging.conf`

<details>
<summary>Instructions</summary>

Expand Down
2 changes: 1 addition & 1 deletion balancer/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
com.olegych.scastie.balancer {
snippets-container = files
snippets-storage = files
snippets-dir = ./target/snippets/
old-snippets-dir = ./target/old-snippets/

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ class DispatchActor(progressActor: ActorRef, statusActor: ActorRef)
container.close()
}

val containerType = config.getString("snippets-container")
val containerType = config.getString("snippets-storage")

private val container =
containerType match {
Expand Down
5 changes: 3 additions & 2 deletions balancer/src/test/resources/application.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
com.olegych.scastie.balancer {
snippets-container = files
snippets-storage = files
}
akka.actor.provider = akka.actor.LocalActorRefProvider

akka.actor.provider = akka.actor.LocalActorRefProvider
1 change: 1 addition & 0 deletions deployment/local.conf
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ com.olegych.scastie {
remote-sbt-ports-start = 5150
remote-sbt-ports-size = 1

snippets-storage = files
snippets-dir = snippets
old-snippets-dir = old-snippets
}
Expand Down
6 changes: 6 additions & 0 deletions deployment/mongodb.template.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
scastie.mongodb {
user=<user>
password=<password>
database=<database>
port=<port>
}
5 changes: 4 additions & 1 deletion deployment/production.conf
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
include "mongodb-prod.conf"
include "oauth2-prod.conf"

com.olegych.scastie {
production = true
balancer {
snippets-container = mongo
snippets-storage = mongo
// server 1536M

# 12GB RAM
Expand Down
5 changes: 4 additions & 1 deletion deployment/staging.conf
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
include "mongodb-staging.conf"
include "oauth2-staging.conf"

com.olegych.scastie {
production = true
balancer {
snippets-container = mongo
snippets-storage = mongo
// server 1536M

# 12GB RAM
Expand Down
14 changes: 13 additions & 1 deletion project/Deployment.scala
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ class ScastieConfig(val configurationFile: File) {
val balancerConfig = config.getConfig("com.olegych.scastie.balancer")
val runnersHostname = balancerConfig.getString("remote-hostname")
val sbtRunnersPortsStart = balancerConfig.getInt("remote-sbt-ports-start")
val containerType = balancerConfig.getString("snippets-storage")

private val sbtRunnersPortsSize = balancerConfig.getInt("remote-sbt-ports-size")
val sbtRunnersPortsEnd = sbtRunnersPortsStart + sbtRunnersPortsSize - 1
}
Expand Down Expand Up @@ -350,12 +352,22 @@ class Deployment(
if (!local) s"/home/${config.userName}/"
else ""

val isMongoDB = config.containerType == "mongo"
val mongodbConfig = if (deploymentType == Production) "mongodb-prod.conf" else "mongodb-staging.conf"

val content =
s"""|#!/usr/bin/env bash
|
|whoami
|
|kill -9 `cat ${baseDir}RUNNING_PID`
|if [ -e ${baseDir}RUNNING_PID ]; then
| kill -9 `cat ${baseDir}RUNNING_PID`
|fi
|
|if [ ! -f ${baseDir}${mongodbConfig} ] && ${isMongoDB}; then
| echo "mongodb configuration file: ${baseDir}${mongodbConfig} is missing"
| exit 1
|fi
|
|rm -rf ${baseDir}server/*
|unzip -o -d ${baseDir}server ${baseDir}$serverZipFileName
Expand Down
12 changes: 10 additions & 2 deletions server/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
@@ -1,11 +1,19 @@
com.olegych.scastie {
production = false
balancer {
// snippets-container = mongo
snippets-container = files
// snippets-storage = mongo
snippets-storage = files
}
}

scastie.mongodb {
user=scastie
password=""
database=scastie
host=localhost
port=27017
}

com.olegych.scastie.web {
session-secret = "WWItju7orWthk7vbAPqI72XOBCfZFxbVjMH169o9eLjHmMCGXw2VdBsQeTNF3WH0"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import play.api.libs.json._
import java.lang.System.{lineSeparator => nl}
import scala.concurrent.ExecutionContext
import scala.concurrent.Future
import com.typesafe.config.ConfigFactory

sealed trait BaseMongoSnippet {
def snippetId: SnippetId
Expand Down Expand Up @@ -45,10 +46,23 @@ object MongoSnippet {
implicit val formatMongoSnippet: OFormat[MongoSnippet] = Json.format[MongoSnippet]
}

class MongoDBSnippetsContainer(_ec: ExecutionContext) extends SnippetsContainer {
class MongoDBSnippetsContainer(_ec: ExecutionContext, ci: Boolean = false) extends SnippetsContainer {
protected implicit val ec: ExecutionContext = _ec

private val mongoUri = "mongodb://localhost:27017/snippets"
val mongoUri = {
if (ci)
s"mongodb://localhost:27017/scastie"
else {
val config = ConfigFactory.load().getConfig("scastie.mongodb")
val user = config.getString("user")
val password = config.getString("password")
val databaseName = config.getString("database")
val host = config.getString("host")
val port = config.getInt("port")
s"mongodb://$user:$password@$host:$port/$databaseName"
}
}


// TODO: Change client logic to use provided codecs
// MongoDB client provides its own BSON converter, but would require changes in API.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@ class SnippetsContainerTest extends AnyFunSuite with BeforeAndAfterAll with Opti
val root = Files.createTempDirectory("test")
val oldRoot = Files.createTempDirectory("old-test")

private lazy val mongoContainer = new MongoDBSnippetsContainer(scala.concurrent.ExecutionContext.Implicits.global)
private val testContainer: SnippetsContainer = {
if (mongo)
mongoContainer
new MongoDBSnippetsContainer(scala.concurrent.ExecutionContext.Implicits.global, ci = true)
else {
new FilesSnippetsContainer(root, oldRoot)(
Executors.newSingleThreadExecutor()
Expand All @@ -34,7 +33,7 @@ class SnippetsContainerTest extends AnyFunSuite with BeforeAndAfterAll with Opti
override protected def afterAll(): Unit = {
deleteRecursively(root)
deleteRecursively(oldRoot)
if (mongo) mongoContainer.close()
if (mongo) testContainer.close()
}

private implicit class FAwait[T](f: Future[T]) {
Expand All @@ -58,10 +57,9 @@ class SnippetsContainerTest extends AnyFunSuite with BeforeAndAfterAll with Opti
}

test("create snippet with logged in user") {
val container = testContainer
val bob = "bob"
val snippetId =
container.create(Inputs.default, user = Some(UserLogin(bob)))
testContainer.create(Inputs.default, user = Some(UserLogin(bob)))
assert(snippetId.await.user.get.login == bob)
}

Expand All @@ -72,118 +70,112 @@ class SnippetsContainerTest extends AnyFunSuite with BeforeAndAfterAll with Opti
}

test("create then read") {
val container = testContainer
val inputs = Inputs.default
val snippetId = container.create(inputs, user = None).await
val result = container.readSnippet(snippetId).await
val snippetId = testContainer.create(inputs, user = None).await
val result = testContainer.readSnippet(snippetId).await

assert(result.value.inputs == inputs.withSavedConfig)
}

test("fork") {
val container = testContainer
val inputs =
Inputs.default.copy(code = "source", isShowingInUserProfile = true)
val snippetId = container.save(inputs, user = None).await
val snippetId = testContainer.save(inputs, user = None).await

val forkedInputs =
Inputs.default.copy(code = "forked", isShowingInUserProfile = true)
val forkedSnippetId =
container.fork(snippetId, forkedInputs, user = None).await
testContainer.fork(snippetId, forkedInputs, user = None).await

val forkedBis = container.readSnippet(forkedSnippetId).await.get
val forkedBis = testContainer.readSnippet(forkedSnippetId).await.get

assert(forkedSnippetId != snippetId)
assert(forkedBis.inputs.forked.get == snippetId)
}

test("update") {
val container = testContainer
val user = UserLogin("github-user-update" + Random.nextInt())
val inputs1 =
Inputs.default.copy(code = "inputs1").copy(isShowingInUserProfile = true)
val snippetId1 = container.save(inputs1, Some(user)).await
val snippetId1 = testContainer.save(inputs1, Some(user)).await
assert(snippetId1.user.get.update == 0)

val inputs2 =
Inputs.default.copy(code = "inputs2").copy(isShowingInUserProfile = true)
val snippetId2 = container.update(snippetId1, inputs2).await.get
val snippetId2 = testContainer.update(snippetId1, inputs2).await.get
assert(snippetId2.user.get.update == 1, "we get a new update id")

val readInputs1 = container.readSnippet(snippetId1).await.get.inputs
val readInputs2 = container.readSnippet(snippetId2).await.get.inputs
val readInputs1 = testContainer.readSnippet(snippetId1).await.get.inputs
val readInputs2 = testContainer.readSnippet(snippetId2).await.get.inputs

assert(readInputs1 == inputs1.copy(isShowingInUserProfile = false).withSavedConfig, "we don't mutate previous input")
assert(readInputs2 == inputs2.copy(forked = Some(snippetId1)).withSavedConfig, "we update forked")

val snippets = container.listSnippets(user).await
val snippets = testContainer.listSnippets(user).await
assert(snippets.size == 1 && snippets.head.snippetId == snippetId2, "we hide old version")
}

test("listSnippets") {
val container = testContainer
val user = UserLogin("github-user-list" + Random.nextInt())
val user2 = UserLogin("github-user-list2" + Random.nextInt())

val inputs1 = Inputs.default.copy(code = "inputs1")
container.save(inputs1, Some(user)).await
testContainer.save(inputs1, Some(user)).await
Thread.sleep(100)

val inputs2 = Inputs.default.copy(code = "inputs2")
container.save(inputs2, Some(user)).await
testContainer.save(inputs2, Some(user)).await
Thread.sleep(100)

val inputs3 = Inputs.default.copy(code = "inputs3")
container.save(inputs3, Some(user)).await
testContainer.save(inputs3, Some(user)).await
Thread.sleep(100)

val user2inputs = Inputs.default.copy(code = "inputs3")
container.save(user2inputs, Some(user2)).await
testContainer.save(user2inputs, Some(user2)).await
Thread.sleep(100)

val inputs4 =
Inputs.default.copy(code = "inputs4", isShowingInUserProfile = false)
container.create(inputs4, Some(user)).await
testContainer.create(inputs4, Some(user)).await

val snippets = container.listSnippets(user).await
val snippets = testContainer.listSnippets(user).await
assert(
snippets.map(_.summary) == List("inputs3", "inputs2", "inputs1")
)
}

test("delete") {
val container = testContainer
val user = UserLogin("github-user-delete" + Random.nextInt())

val inputs1 = Inputs.default.copy(code = "inputs1")
val snippetId1 = container.save(inputs1, Some(user)).await
val snippetId1 = testContainer.save(inputs1, Some(user)).await

val inputs1U = Inputs.default.copy(code = "inputs1 updated")
container.update(snippetId1, inputs1U).await.get
testContainer.update(snippetId1, inputs1U).await.get

val inputs2 = Inputs.default.copy(code = "inputs2")
val snippetId2 = container.save(inputs2, Some(user)).await
val snippetId2 = testContainer.save(inputs2, Some(user)).await

val inputs2U = Inputs.default.copy(code = "inputs2 updated")
val snippetId2U = container.update(snippetId2, inputs2U).await.get
val snippetId2U = testContainer.update(snippetId2, inputs2U).await.get

assert(container.listSnippets(user).await.size == 2)
assert(testContainer.listSnippets(user).await.size == 2)

container.deleteAll(snippetId2U).await
testContainer.deleteAll(snippetId2U).await

assert(container.readSnippet(snippetId2U).await == None)
assert(container.readSnippet(snippetId2).await == None)
assert(testContainer.readSnippet(snippetId2U).await == None)
assert(testContainer.readSnippet(snippetId2).await == None)

assert(container.listSnippets(user).await.size == 1)
assert(testContainer.listSnippets(user).await.size == 1)
}

test("appendOutput") {
val container = testContainer
val inputs = Inputs.default
val snippetId = container.create(inputs, user = None).await
val snippetId = testContainer.create(inputs, user = None).await
val progress = SnippetProgress.default.copy(snippetId = Some(snippetId))
container.appendOutput(progress)
val result = container.readSnippet(snippetId).await
testContainer.appendOutput(progress)
val result = testContainer.readSnippet(snippetId).await

assert(result.value.progresses.headOption.value == progress, "we properly append output")
}
Expand Down

0 comments on commit 4d74dc6

Please sign in to comment.