mirror of
https://github.com/sbt/sbt.git
synced 2026-09-03 16:39:24 +02:00
implement tokenfile authentication
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
import _root_.sjsonnew.{ Unbuilder, Builder, JsonFormat, deserializationError }
|
||||
trait ServerAuthenticationFormats { self: sjsonnew.BasicJsonProtocol =>
|
||||
implicit lazy val ServerAuthenticationFormat: JsonFormat[sbt.ServerAuthentication] = new JsonFormat[sbt.ServerAuthentication] {
|
||||
override def read[J](jsOpt: Option[J], unbuilder: Unbuilder[J]): sbt.ServerAuthentication = {
|
||||
jsOpt match {
|
||||
case Some(js) =>
|
||||
unbuilder.readString(js) match {
|
||||
case "Token" => sbt.ServerAuthentication.Token
|
||||
}
|
||||
case None =>
|
||||
deserializationError("Expected JsString but found None")
|
||||
}
|
||||
}
|
||||
override def write[J](obj: sbt.ServerAuthentication, builder: Builder[J]): Unit = {
|
||||
val str = obj match {
|
||||
case sbt.ServerAuthentication.Token => "Token"
|
||||
}
|
||||
builder.writeString(str)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* This code is generated using [[http://www.scala-sbt.org/contraband/ sbt-contraband]].
|
||||
*/
|
||||
|
||||
// DO NOT EDIT MANUALLY
|
||||
package sbt
|
||||
sealed abstract class ServerAuthentication extends Serializable
|
||||
object ServerAuthentication {
|
||||
|
||||
|
||||
case object Token extends ServerAuthentication
|
||||
}
|
||||
@@ -12,3 +12,7 @@ type Exec {
|
||||
type CommandSource {
|
||||
channelName: String!
|
||||
}
|
||||
|
||||
enum ServerAuthentication {
|
||||
Token
|
||||
}
|
||||
|
||||
@@ -17,6 +17,15 @@ object BasicKeys {
|
||||
val watch = AttributeKey[Watched]("watch", "Continuous execution configuration.", 1000)
|
||||
val serverPort =
|
||||
AttributeKey[Int]("server-port", "The port number used by server command.", 10000)
|
||||
|
||||
val serverHost =
|
||||
AttributeKey[String]("serverHost", "The host used by server command.", 10000)
|
||||
|
||||
val serverAuthentication =
|
||||
AttributeKey[Set[ServerAuthentication]]("serverAuthentication",
|
||||
"Method of authenticating server command.",
|
||||
10000)
|
||||
|
||||
private[sbt] val interactive = AttributeKey[Boolean](
|
||||
"interactive",
|
||||
"True if commands are currently being entered from an interactive environment.",
|
||||
|
||||
@@ -7,19 +7,22 @@ package server
|
||||
|
||||
import java.io.File
|
||||
import java.net.{ SocketTimeoutException, InetAddress, ServerSocket, Socket }
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
import java.util.concurrent.atomic.{ AtomicBoolean, AtomicLong }
|
||||
import java.nio.file.attribute.{ UserPrincipal, AclEntry, AclEntryPermission, AclEntryType }
|
||||
import scala.concurrent.{ Future, Promise }
|
||||
import scala.util.{ Try, Success, Failure }
|
||||
import scala.util.{ Try, Success, Failure, Random }
|
||||
import sbt.internal.util.ErrorHandling
|
||||
import sbt.internal.protocol.PortFile
|
||||
import sbt.internal.protocol.{ PortFile, TokenFile }
|
||||
import sbt.util.Logger
|
||||
import sbt.io.IO
|
||||
import sbt.io.syntax._
|
||||
import sjsonnew.support.scalajson.unsafe.{ Converter, CompactPrinter }
|
||||
import sbt.internal.protocol.codec._
|
||||
|
||||
private[sbt] sealed trait ServerInstance {
|
||||
def shutdown(): Unit
|
||||
def ready: Future[Unit]
|
||||
def authenticate(challenge: String): Boolean
|
||||
}
|
||||
|
||||
private[sbt] object Server {
|
||||
@@ -31,14 +34,16 @@ private[sbt] object Server {
|
||||
|
||||
def start(host: String,
|
||||
port: Int,
|
||||
onIncomingSocket: Socket => Unit,
|
||||
onIncomingSocket: (Socket, ServerInstance) => Unit,
|
||||
auth: Set[ServerAuthentication],
|
||||
portfile: File,
|
||||
tokenfile: File,
|
||||
log: Logger): ServerInstance =
|
||||
new ServerInstance {
|
||||
new ServerInstance { self =>
|
||||
val running = new AtomicBoolean(false)
|
||||
val p: Promise[Unit] = Promise[Unit]()
|
||||
val ready: Future[Unit] = p.future
|
||||
val token = new AtomicLong(Random.nextLong)
|
||||
|
||||
val serverThread = new Thread("sbt-socket-server") {
|
||||
override def run(): Unit = {
|
||||
@@ -57,7 +62,7 @@ private[sbt] object Server {
|
||||
while (running.get()) {
|
||||
try {
|
||||
val socket = serverSocket.accept()
|
||||
onIncomingSocket(socket)
|
||||
onIncomingSocket(socket, self)
|
||||
} catch {
|
||||
case _: SocketTimeoutException => // its ok
|
||||
}
|
||||
@@ -67,6 +72,15 @@ private[sbt] object Server {
|
||||
}
|
||||
serverThread.start()
|
||||
|
||||
override def authenticate(challenge: String): Boolean = {
|
||||
try {
|
||||
val l = challenge.toLong
|
||||
token.compareAndSet(l, Random.nextLong)
|
||||
} catch {
|
||||
case _: NumberFormatException => false
|
||||
}
|
||||
}
|
||||
|
||||
override def shutdown(): Unit = {
|
||||
log.info("shutting down server")
|
||||
if (portfile.exists) {
|
||||
@@ -78,10 +92,51 @@ private[sbt] object Server {
|
||||
running.set(false)
|
||||
}
|
||||
|
||||
def writeTokenfile(): Unit = {
|
||||
import JsonProtocol._
|
||||
|
||||
val uri = s"tcp://$host:$port"
|
||||
val t = TokenFile(uri, token.get.toString)
|
||||
val jsonToken = Converter.toJson(t).get
|
||||
|
||||
if (tokenfile.exists) {
|
||||
IO.delete(tokenfile)
|
||||
}
|
||||
IO.touch(tokenfile)
|
||||
ownerOnly(tokenfile)
|
||||
IO.write(tokenfile, CompactPrinter(jsonToken), IO.utf8, true)
|
||||
}
|
||||
|
||||
/** Set the persmission of the file such that the only the owner can read/write it. */
|
||||
def ownerOnly(file: File): Unit = {
|
||||
def acl(owner: UserPrincipal) = {
|
||||
val builder = AclEntry.newBuilder
|
||||
builder.setPrincipal(owner)
|
||||
builder.setPermissions(AclEntryPermission.values(): _*)
|
||||
builder.setType(AclEntryType.ALLOW)
|
||||
builder.build
|
||||
}
|
||||
file match {
|
||||
case _ if IO.isPosix =>
|
||||
IO.chmod("rw-------", file)
|
||||
case _ if IO.hasAclFileAttributeView =>
|
||||
val view = file.aclFileAttributeView
|
||||
view.setAcl(java.util.Collections.singletonList(acl(view.getOwner)))
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
// This file exists through the lifetime of the server.
|
||||
def writePortfile(): Unit = {
|
||||
import JsonProtocol._
|
||||
val p = PortFile(s"tcp://$host:$port", None)
|
||||
|
||||
val uri = s"tcp://$host:$port"
|
||||
val tokenRef =
|
||||
if (auth(ServerAuthentication.Token)) {
|
||||
writeTokenfile()
|
||||
Some(tokenfile.toURI.toString)
|
||||
} else None
|
||||
val p = PortFile(uri, tokenRef)
|
||||
val json = Converter.toJson(p).get
|
||||
IO.write(portfile, CompactPrinter(json))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user