Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/twbs/rorschach.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChris Rebert <code@rebertia.com>2014-06-24 12:22:52 +0400
committerChris Rebert <code@rebertia.com>2014-06-24 12:22:52 +0400
commitfcb65c09915715196421cba4e86e2b5fa28da78e (patch)
treed125dc361e1808e4aea15c1822b4b0509b2304e0 /src
parented12a1b1d62c9e35e8645c1ef56bd0c13035b59a (diff)
copypasta from lmvtfy
Diffstat (limited to 'src')
-rw-r--r--src/main/scala/com/getbootstrap/rorschach/server/ActorWithLogging.scala6
-rw-r--r--src/main/scala/com/getbootstrap/rorschach/server/Boot.scala43
-rw-r--r--src/main/scala/com/getbootstrap/rorschach/server/GitHubIssuesWebHooksDirectives.scala23
-rw-r--r--src/main/scala/com/getbootstrap/rorschach/server/HubSignatureDirectives.scala47
-rw-r--r--src/main/scala/com/getbootstrap/rorschach/server/Settings.scala23
-rw-r--r--src/main/scala/com/getbootstrap/rorschach/util/HmacSha1.scala35
6 files changed, 177 insertions, 0 deletions
diff --git a/src/main/scala/com/getbootstrap/rorschach/server/ActorWithLogging.scala b/src/main/scala/com/getbootstrap/rorschach/server/ActorWithLogging.scala
new file mode 100644
index 0000000..c9c3a29
--- /dev/null
+++ b/src/main/scala/com/getbootstrap/rorschach/server/ActorWithLogging.scala
@@ -0,0 +1,6 @@
+package com.chrisrebert.lmvtfy.server
+
+import akka.actor.Actor
+import akka.actor.ActorLogging
+
+trait ActorWithLogging extends Actor with ActorLogging
diff --git a/src/main/scala/com/getbootstrap/rorschach/server/Boot.scala b/src/main/scala/com/getbootstrap/rorschach/server/Boot.scala
new file mode 100644
index 0000000..dce7a3a
--- /dev/null
+++ b/src/main/scala/com/getbootstrap/rorschach/server/Boot.scala
@@ -0,0 +1,43 @@
+package com.chrisrebert.lmvtfy.server
+
+import scala.concurrent.duration._
+import scala.util.Try
+import akka.actor.{ActorSystem, Props}
+import akka.io.IO
+import spray.can.Http
+import akka.pattern.ask
+import akka.routing.SmallestMailboxPool
+import akka.util.Timeout
+import com.chrisrebert.lmvtfy.github.GitHubIssueCommenter
+
+
+object Boot extends App {
+ val arguments = args.toSeq
+ val maybePort = arguments match {
+ case Seq(portStr: String) => {
+ Try{ portStr.toInt }.toOption
+ }
+ case _ => None
+ }
+ maybePort match {
+ case Some(port) => run(port)
+ case _ => {
+ System.err.println("USAGE: lmvtfy <port-number>")
+ System.exit(1)
+ }
+ }
+
+ def run(port: Int) {
+ implicit val system = ActorSystem("on-spray-can")
+ // import actorSystem.dispatcher
+
+ val commenter = system.actorOf(Props(classOf[GitHubIssueCommenter]))
+ val localValidator = system.actorOf(Props(classOf[ValidatorSingletonActor], commenter), "validator-service")
+ val exampleFetcherPool = system.actorOf(SmallestMailboxPool(5).props(Props(classOf[LiveExampleFetcher], localValidator)), "example-fetcher-pool")
+ val issueCommentEventHandler = system.actorOf(Props(classOf[IssueCommentEventHandler], exampleFetcherPool), "issue-comment-event-handler")
+ val webService = system.actorOf(Props(classOf[LmvtfyActor], issueCommentEventHandler), "lmvtfy-service")
+
+ implicit val timeout = Timeout(15.seconds)
+ IO(Http) ? Http.Bind(webService, interface = "0.0.0.0", port = port)
+ }
+}
diff --git a/src/main/scala/com/getbootstrap/rorschach/server/GitHubIssuesWebHooksDirectives.scala b/src/main/scala/com/getbootstrap/rorschach/server/GitHubIssuesWebHooksDirectives.scala
new file mode 100644
index 0000000..4ab9ca9
--- /dev/null
+++ b/src/main/scala/com/getbootstrap/rorschach/server/GitHubIssuesWebHooksDirectives.scala
@@ -0,0 +1,23 @@
+package com.chrisrebert.lmvtfy.server
+
+import com.chrisrebert.lmvtfy.github.{IssueOrCommentEvent, GitHubJsonProtocol}
+import scala.util.{Success, Failure, Try}
+import spray.json._
+import spray.routing.{Directive1, ValidationRejection}
+import spray.routing.directives.{BasicDirectives, RouteDirectives}
+
+trait GitHubIssuesWebHooksDirectives {
+ import RouteDirectives.reject
+ import BasicDirectives.provide
+ import HubSignatureDirectives.stringEntityMatchingHubSignature
+ import GitHubJsonProtocol._
+
+ def authenticatedIssueOrCommentEvent(secretKey: Array[Byte]): Directive1[IssueOrCommentEvent] = stringEntityMatchingHubSignature(secretKey).flatMap{ entityJsonString =>
+ Try{ entityJsonString.parseJson.convertTo[IssueOrCommentEvent] } match {
+ case Failure(err) => reject(ValidationRejection("JSON either malformed or does not match expected schema!"))
+ case Success(event) => provide(event)
+ }
+ }
+}
+
+object GitHubIssuesWebHooksDirectives extends GitHubIssuesWebHooksDirectives
diff --git a/src/main/scala/com/getbootstrap/rorschach/server/HubSignatureDirectives.scala b/src/main/scala/com/getbootstrap/rorschach/server/HubSignatureDirectives.scala
new file mode 100644
index 0000000..9de2f15
--- /dev/null
+++ b/src/main/scala/com/getbootstrap/rorschach/server/HubSignatureDirectives.scala
@@ -0,0 +1,47 @@
+package com.chrisrebert.lmvtfy.server
+
+import scala.util.{Try,Success,Failure}
+import spray.routing.{Directive1, MalformedHeaderRejection, MalformedRequestContentRejection, ValidationRejection}
+import spray.routing.directives.{BasicDirectives, HeaderDirectives, RouteDirectives, MarshallingDirectives}
+import com.chrisrebert.lmvtfy.util.{HmacSha1,Utf8ByteArray}
+
+trait HubSignatureDirectives {
+
+ import BasicDirectives.provide
+ import HeaderDirectives.headerValueByName
+ import RouteDirectives.reject
+ import MarshallingDirectives.{entity, as}
+
+ private val xHubSignature = "X-Hub-Signature"
+ private val hubSignatureHeaderValue = headerValueByName(xHubSignature)
+
+ val hubSignature: Directive1[Array[Byte]] = hubSignatureHeaderValue.flatMap { algoEqHex =>
+ val bytesFromHexOption = algoEqHex.split('=') match {
+ case Array("sha1", hex) => Try{ javax.xml.bind.DatatypeConverter.parseHexBinary(hex) }.toOption
+ case _ => None
+ }
+ bytesFromHexOption match {
+ case Some(bytesFromHex) => provide(bytesFromHex)
+ case None => reject(MalformedHeaderRejection(xHubSignature, "Malformed HMAC"))
+ }
+ }
+
+ private val bytesEntity = entity(as[Array[Byte]])
+
+ def stringEntityMatchingHubSignature(secretKey: Array[Byte]): Directive1[String] = hubSignature.flatMap { signature =>
+ bytesEntity.flatMap { dataBytes =>
+ val hmac = new HmacSha1(mac = signature, secretKey = secretKey, data = dataBytes)
+ if (hmac.isValid) {
+ dataBytes.utf8String match {
+ case Success(string) => provide(string)
+ case Failure(exc) => reject(MalformedRequestContentRejection("Request body is not valid UTF-8", Some(exc)))
+ }
+ }
+ else {
+ reject(ValidationRejection("Incorrect HMAC"))
+ }
+ }
+ }
+}
+
+object HubSignatureDirectives extends HubSignatureDirectives
diff --git a/src/main/scala/com/getbootstrap/rorschach/server/Settings.scala b/src/main/scala/com/getbootstrap/rorschach/server/Settings.scala
new file mode 100644
index 0000000..76b4eb6
--- /dev/null
+++ b/src/main/scala/com/getbootstrap/rorschach/server/Settings.scala
@@ -0,0 +1,23 @@
+package com.chrisrebert.lmvtfy.server
+
+import scala.collection.JavaConversions._
+import com.typesafe.config.Config
+import akka.actor.ActorSystem
+import akka.actor.Extension
+import akka.actor.ExtensionId
+import akka.actor.ExtensionIdProvider
+import akka.actor.ExtendedActorSystem
+import akka.util.ByteString
+import com.chrisrebert.lmvtfy.util.Utf8String
+
+class SettingsImpl(config: Config) extends Extension {
+ val RepoFullNames: Set[String] = config.getStringList("lmvtfy.github-repos-to-watch").toSet
+ val BotUsername: String = config.getString("lmvtfy.username")
+ val BotPassword: String = config.getString("lmvtfy.password")
+ val WebHookSecretKey: ByteString = ByteString(config.getString("lmvtfy.web-hook-secret-key").utf8Bytes)
+}
+object Settings extends ExtensionId[SettingsImpl] with ExtensionIdProvider {
+ override def lookup() = Settings
+ override def createExtension(system: ExtendedActorSystem) = new SettingsImpl(system.settings.config)
+ override def get(system: ActorSystem): SettingsImpl = super.get(system)
+}
diff --git a/src/main/scala/com/getbootstrap/rorschach/util/HmacSha1.scala b/src/main/scala/com/getbootstrap/rorschach/util/HmacSha1.scala
new file mode 100644
index 0000000..2856fea
--- /dev/null
+++ b/src/main/scala/com/getbootstrap/rorschach/util/HmacSha1.scala
@@ -0,0 +1,35 @@
+package com.chrisrebert.lmvtfy.util
+
+import javax.crypto.Mac
+import javax.crypto.spec.SecretKeySpec
+import java.security.{NoSuchAlgorithmException, InvalidKeyException, SignatureException}
+import java.security.MessageDigest
+
+object HmacSha1 {
+ private val HmacSha1Algorithm = "HmacSHA1"
+
+ implicit class HexByteArray(array: Array[Byte]) {
+ import javax.xml.bind.DatatypeConverter
+ def asHexBytes: String = DatatypeConverter.printHexBinary(array).toLowerCase
+ }
+}
+
+case class HmacSha1(mac: Array[Byte], secretKey: Array[Byte], data: Array[Byte]) {
+ import HmacSha1.HmacSha1Algorithm
+ import HmacSha1.HexByteArray
+
+ @throws[NoSuchAlgorithmException]("if HMAC-SHA1 is not supported")
+ @throws[InvalidKeyException]("if the secret key is malformed")
+ @throws[SignatureException]("under unknown circumstances")
+ private lazy val correct: Array[Byte] = {
+ val key = new SecretKeySpec(secretKey, HmacSha1Algorithm)
+ val mac = Mac.getInstance(HmacSha1Algorithm)
+ mac.init(key)
+ mac.doFinal(data)
+ }
+
+ lazy val isValid: Boolean = MessageDigest.isEqual(mac, correct)
+
+ def givenHex = mac.asHexBytes
+ def correctHex = correct.asHexBytes
+}