Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,21 @@ case class LoginForm(login: ModelProperty[LoginData] => Unit) {
if(ClientConf.openid.nonEmpty) {
Seq(
hr(clear.both),
label("Login providers:"), br
label("Login with:"), br
)
} else Seq[Modifier](),
ClientConf.openid.map{ openid =>
button(BootstrapStyles.Float.right(),ClientConf.style.boxButton,borderRadius := 10.px, borderColor := ClientConf.styleConf.colors.mainColor, borderWidth := 3.px, borderStyle := "solid",

img(src := openid.logo, maxWidth := 80.px),
onclick :+= ((e:Event) => {
e.preventDefault()
val redirectUri = URLEncoder.encode(s"${ClientConf.frontendUrl}/authenticate/${openid.provider_id}","UTF-8")
window.location.href = s"${openid.authorize_url}?client_id=${openid.client_id}&scope=${openid.scope}&response_type=code&state=${UUID.randomUUID()}&redirect_uri=$redirectUri"
})
)
},
div(display.flex,justifyContent.spaceAround,
ClientConf.openid.map{ openid =>
button(ClientConf.style.boxButton,
img(src := openid.logo, maxWidth := 80.px),
onclick :+= ((e:Event) => {
e.preventDefault()
val redirectUri = URLEncoder.encode(s"${ClientConf.frontendUrl}/authenticate/${openid.provider_id}","UTF-8")
window.location.href = s"${openid.authorize_url}?client_id=${openid.client_id}&scope=${openid.scope}&response_type=code&state=${UUID.randomUUID()}&redirect_uri=$redirectUri"
})
)
}
),
div(clear.both)

)
Expand Down
4 changes: 2 additions & 2 deletions project/Settings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,8 @@ object Settings {
"org.apache.xmlgraphics" % "batik-transcoder" % "1.16",
"org.apache.xmlgraphics" % "batik-codec" % "1.16",
"com.softwaremill.sttp.client4" %% "core" % "4.0.9",
"com.softwaremill.sttp.client4" %% "circe" % "4.0.9"

"com.softwaremill.sttp.client4" %% "circe" % "4.0.9",
"org.bouncycastle" % "bcpkix-jdk18on" % "1.83"
//"mil.nga.geopackage" % "geopackage" % "6.6.3"

// "com.github.pureconfig" %% "pureconfig" % "0.17.3"
Expand Down
24 changes: 17 additions & 7 deletions server/src/main/scala/ch/wsl/box/rest/Boot.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,14 @@ import wvlet.airframe.Design
import ch.wsl.box.model.Migrate
import ch.wsl.box.rest.logic.cron.{BoxCronLoader, CronScheduler}
import ch.wsl.box.rest.logic.notification.{MailHandler, NotificationsHandler}
import ch.wsl.box.rest.utils.CertificateUtils

import scala.concurrent.{Await, ExecutionContext, Future, Promise}
import scala.concurrent.duration._
import scala.io.StdIn


class Box(name:String,version:String)(implicit services: Services) {
class Box(name:String,version:String,https:Boolean)(implicit services: Services) {

implicit val executionContext = services.executionContext
implicit val system: ActorSystem = services.actorSystem
Expand Down Expand Up @@ -66,10 +67,18 @@ class Box(name:String,version:String)(implicit services: Services) {
Root(s"$name $version",akkaConf, origins).route
}

def server = if(https) {
Http().newServerAt( host, port).enableHttps(CertificateUtils.sslContext).bind(routes)
} else {
Http().newServerAt( host, port).bind(routes)
}

val httpsStr = if(https) "https" else "http"

for{
//pl <- preloading
//_ <- pl.terminate(1.seconds)
binding <- Http().bindAndHandle(routes, host, port) //attach the root route
binding <- server //attach the root route
res <- {
println(
s"""
Expand All @@ -83,7 +92,7 @@ class Box(name:String,version:String)(implicit services: Services) {
|
|===================================
|
|Box server started at http://$host:$port
|Box server started at $httpsStr://$host:$port

|""".stripMargin)
binding.whenTerminationSignalIssued.map{ _ =>
Expand All @@ -102,9 +111,10 @@ class Box(name:String,version:String)(implicit services: Services) {

object Boot extends App {

val (name,app_version) = args.length match {
case 2 => (args(0),args(1))
case _ => ("Standalone","DEV")
val (name,app_version,https) = args.length match {
case 3 => (args(0),args(1),args(2).toBoolean)
case 2 => (args(0),args(1),false)
case _ => ("Standalone","DEV",true)
}

def run(name:String,app_version:String,module:Design) {
Expand All @@ -124,7 +134,7 @@ object Boot extends App {
Registry.loadBox()

module.build[Services] { services =>
val server = new Box(name, app_version)(services)
val server = new Box(name, app_version,https)(services)
implicit val executionContext = services.executionContext

val binding = {
Expand Down
31 changes: 19 additions & 12 deletions server/src/main/scala/ch/wsl/box/rest/auth/oidc/AuthFlow.scala
Original file line number Diff line number Diff line change
Expand Up @@ -66,22 +66,29 @@ object AuthFlow {
// }
def code(provider:OIDCConf,c:String)(implicit ex:ExecutionContext, services: Services):Future[Either[ResponseException[String],CurrentUser]] = {

def authToken = basicRequest
.post(uri"${provider.token_url}")
.body(Map(
"grant_type" -> "authorization_code",
"client_id" -> provider.client_id,
"client_secret" -> provider.client_secret,
"code" -> c,
"redirect_uri" -> s"${services.config.frontendUrl}/authenticate/${provider.provider_id}"
))
.response(asJson[OpenIDToken])
.send(backend)
def authToken = {
val r = basicRequest
.post(uri"${provider.token_url}")
.body(Map(
"grant_type" -> "authorization_code",
"client_id" -> provider.client_id,
"client_secret" -> provider.client_secret,
"code" -> c,
"redirect_uri" -> s"${services.config.frontendUrl}/authenticate/${provider.provider_id}"
))
.response(asJson[OpenIDToken])

// println(r.toCurl)

r.send(backend).map{ x =>
x
}
}

def userInfo(token:OpenIDToken) = {
import UserInfo._
basicRequest
.get(uri"https://gitlabext.wsl.ch/oauth/userinfo")
.get(uri"${provider.user_info_url}")
.response(asJson[UserInfo])
.auth.bearer(token.access_token)
.send(backend)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ object FormMetadataFactory extends Logging with MetadataFactory{
case Some(u) => Auth.rolesOf(u)
case None => Future.successful(Seq())
}
} yield user.map(u => (BoxSession(CurrentUser(DbInfo(u,u,roles),UserInfo(u,u,None,roles,Json.Null))),form.exists(_.public_list)))
} yield user.map(u => (BoxSession(CurrentUser(DbInfo(u,u,roles),UserInfo(u,u,u,None,roles,Json.Null))),form.exists(_.public_list)))
}


Expand Down
2 changes: 1 addition & 1 deletion server/src/main/scala/ch/wsl/box/rest/utils/Auth.scala
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ object Auth extends Logging {
for{
validUser <- checkAuth(name,password)
roles <- if(validUser) rolesOf(username) else Future.successful(Seq())
} yield if(validUser) Some(CurrentUser(DbInfo(username,name,roles),UserInfo(name,name,None,roles,Json.Null))) else None
} yield if(validUser) Some(CurrentUser(DbInfo(username,name,roles),UserInfo(name,name,name,None,roles,Json.Null))) else None

}

Expand Down
16 changes: 11 additions & 5 deletions server/src/main/scala/ch/wsl/box/rest/utils/BoxSession.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,27 @@ import io.circe._
import io.circe.syntax._
import io.circe.generic.auto._
import io.circe.parser._
import scribe.Logging

import scala.concurrent.ExecutionContext
import scala.util.Try
import scala.util.{Failure, Success, Try}


case class BoxSession(user:CurrentUser) {
def userProfile(implicit services:Services): UserProfile = UserProfile(user.db.username,user.db.app_username)
}

object BoxSession {
object BoxSession extends Logging {
implicit def serializer: SessionSerializer[BoxSession, String] = new SingleValueSessionSerializer(
s => s.asJson.noSpaces,
(un: String) => Try {
val session = parse(un).flatMap(_.as[BoxSession]).toOption.get
session
(un: String) => {
parse(un).flatMap(_.as[BoxSession]) match {
case Left(value) => {
logger.warn(s"Session not decoded: ${value.getMessage}")
Failure(value)
}
case Right(value) => Success(value)
}
})

def fromLogin(request:LoginRequest)(implicit services: Services,executionContext: ExecutionContext) = Auth.getCurrentUser(request.username,request.password).map(_.map(cu => BoxSession(cu)))
Expand Down
59 changes: 59 additions & 0 deletions server/src/main/scala/ch/wsl/box/rest/utils/CertificateUtils.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package ch.wsl.box.rest.utils

import akka.http.scaladsl.{ConnectionContext, HttpsConnectionContext}

import java.security.KeyPairGenerator
import java.security.KeyStore
import java.security.cert.X509Certificate
import java.util.{Date, Locale}
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import org.bouncycastle.cert.X509v3CertificateBuilder
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter
import org.bouncycastle.operator.ContentSigner
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder

import javax.net.ssl.{KeyManagerFactory, SSLContext, TrustManagerFactory}

object CertificateUtils {
def generateSelfSignedCertificate: KeyStore = {
val keyPairGen = KeyPairGenerator.getInstance("RSA")
keyPairGen.initialize(2048)
val keyPair = keyPairGen.generateKeyPair()

val subPubKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic.getEncoded)

val certBuilder = new X509v3CertificateBuilder(
new X500Name("CN=localhost"),
new java.math.BigInteger(64, new java.security.SecureRandom()),
new Date(System.currentTimeMillis()),
new Date(System.currentTimeMillis() + 365 * 24 * 60 * 60 * 1000),
new X500Name("CN=localhost"),
subPubKeyInfo
)

val signer: ContentSigner = new JcaContentSignerBuilder("SHA256WithRSA").build(keyPair.getPrivate)
val cert: X509Certificate = new JcaX509CertificateConverter().getCertificate(certBuilder.build(signer))

val keyStore = KeyStore.getInstance("JKS")
keyStore.load(null, null)
keyStore.setKeyEntry("selfsigned", keyPair.getPrivate, "password".toCharArray, Array(cert))

keyStore
}

def sslContext:HttpsConnectionContext = {
val keyStore = CertificateUtils.generateSelfSignedCertificate
val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm)
keyManagerFactory.init(keyStore, "password".toCharArray)

val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm)
trustManagerFactory.init(keyStore)

val sslContext: SSLContext = SSLContext.getInstance("TLS")
sslContext.init(keyManagerFactory.getKeyManagers, trustManagerFactory.getTrustManagers, null)

ConnectionContext.httpsServer(sslContext)

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import io.circe.syntax.EncoderOps
import io.circe.{Decoder, HCursor, Json, JsonObject}

case class UserInfo(
sub:String,
name:String,
preferred_username:String,
email:Option[String],
Expand All @@ -15,18 +16,19 @@ case class UserInfo(

object UserInfo {

def simple(username:String) = UserInfo(username,username,None,Seq(),Json.Null)
def simple(username:String) = UserInfo(username,username,username,None,Seq(),Json.Null)

implicit val decoderRaw: Decoder[UserInfo] = new Decoder[UserInfo] {
override def apply(c: HCursor): Result[UserInfo] = for {
name <- c.downField("name").as[String]
preferred_username <- c.downField("preferred_username").as[String]
email <- c.downField("email").as[Option[String]]
preferred_username <- c.downField("preferred_username").as[Option[String]]
sub <- c.downField("sub").as[String]
roles <- c.downField("roles").as[Option[Seq[String]]]
claims <- c.downField("claims").as[Option[Json]]
} yield {
val newClaims:Json = claims.getOrElse(Json.obj()).deepMerge(c.value.asObject.map(_.filterKeys(_ != "claims").asJson).getOrElse(Json.obj()))
UserInfo(name, preferred_username, email,roles.toList.flatten,newClaims)
UserInfo(sub, name, preferred_username.orElse(email).getOrElse(sub), email,roles.toList.flatten,newClaims)
}
}
}
Loading