aboutsummaryrefslogtreecommitdiff
path: root/gpg/skeybase/src/main/scala/com/github/jodersky/skeybase/Verifier.scala
diff options
context:
space:
mode:
Diffstat (limited to 'gpg/skeybase/src/main/scala/com/github/jodersky/skeybase/Verifier.scala')
-rw-r--r--gpg/skeybase/src/main/scala/com/github/jodersky/skeybase/Verifier.scala112
1 files changed, 112 insertions, 0 deletions
diff --git a/gpg/skeybase/src/main/scala/com/github/jodersky/skeybase/Verifier.scala b/gpg/skeybase/src/main/scala/com/github/jodersky/skeybase/Verifier.scala
new file mode 100644
index 0000000..ccf0308
--- /dev/null
+++ b/gpg/skeybase/src/main/scala/com/github/jodersky/skeybase/Verifier.scala
@@ -0,0 +1,112 @@
+package com.github.jodersky.skeybase
+
+import scala.language.implicitConversions
+
+import scala.concurrent.ExecutionContext
+import scala.concurrent.Future
+import scala.util.Failure
+import scala.util.Success
+import scala.util.Try
+import akka.actor.ActorSystem
+import spray.http.HttpHeaders.Location
+import spray.http.HttpRequest
+import spray.http.HttpResponse
+import spray.http.Uri
+import spray.json.DefaultJsonProtocol
+import spray.json.JsonParser
+
+/** Verifies a user identity proof. */
+trait Verifier {
+
+ /** Checks if a given proof is actually signed by the provided key. */
+ def verify(fingerprint: String, proof: Proof)(implicit sys: ActorSystem): Future[Proof]
+
+}
+
+/** Contains utilities for concrete verifiers. */
+object Verifier {
+
+ object JsonProtocol extends DefaultJsonProtocol {
+ implicit val serviceFormat = jsonFormat4(Service.apply)
+ implicit val keyFormat = jsonFormat1(PublicKey.apply)
+ implicit val statementBodyFormat = jsonFormat2(StatementBody.apply)
+ implicit val statementFormat = jsonFormat1(OwnershipStatement.apply)
+ }
+ import JsonProtocol._
+
+ /** Convert a try to a future, useful for writing expressive for-comprehensions mixing futures and tries. */
+ implicit def tryToFuture[A](t: Try[A]): Future[A] = t match {
+ case Success(a) => Future.successful(a)
+ case Failure(e) => Future.failed(e)
+ }
+
+ /** Pipeline stage that follows redirects and also keeps track of the final URL. */
+ def withRedirects(
+ sendReceive: HttpRequest => Future[HttpResponse],
+ maxRedirects: Int = 5)(implicit ec: ExecutionContext): HttpRequest => Future[(Uri, HttpResponse)] = { request =>
+
+ def dispatch(request: HttpRequest, redirectsLeft: Int): Future[(Uri, HttpResponse)] = if (redirectsLeft <= 0) {
+ Future.failed(new UnsupportedOperationException("Too many redirects."))
+ } else {
+ sendReceive(request).flatMap { response =>
+ if (response.status.value.startsWith("3")) {
+ response.header[Location].map { location =>
+ dispatch(request.copy(uri = location.uri), redirectsLeft - 1)
+ } getOrElse {
+ Future.failed(new NoSuchElementException("Missing location header in redirect response."))
+ }
+ } else {
+ Future.successful(request.uri, response)
+ }
+ }
+ }
+
+ dispatch(request, maxRedirects)
+ }
+
+ /** Pipeline stage that ensures the request's host matches a provided parameter. */
+ def finalHost(host: String) = ((uri: Uri, response: HttpResponse) => {
+ if (uri.authority.host.address != host)
+ throw new VerificationException("Final host " + uri.authority.host.address + " is not " + host)
+ else
+ response
+ }).tupled
+
+ /** Extract an OpenPGP delimited message from a content. */
+ def extractSignedMessage(content: String): Try[String] = Try {
+ val header = "-----BEGIN PGP MESSAGE-----"
+ val footer = "-----END PGP MESSAGE-----"
+ val inner = content.lines.dropWhile(_ != header).takeWhile(_ != footer)
+ if (inner.isEmpty) {
+ throw new VerificationException("No OpenPGP message found.")
+ } else {
+ (inner ++ Seq(footer)).mkString("\n")
+ }
+ }
+
+ /** Verify the contents of a statement of ownership against a known service. */
+ def verifyOwnershipStatement(statement: String, goodService: Service): Try[OwnershipStatement] = Try {
+ val stmt = JsonParser(statement).convertTo[OwnershipStatement]
+
+ if (stmt.body.service != goodService) {
+ throw new VerificationException("The statement of ownership does not match the required service.")
+ } else {
+ stmt
+ }
+ }
+
+ /*
+ * if (!(uri.path.tail startsWith (Path(proof.nametag)))) {
+ * throw new VerificationException("Final github account does not match the one provided in the proof." + uri.path.head)
+ * }
+
+
+ def extractHtmlId(id: String, html: String): Option[String] = {
+ val cleaner = new HtmlCleaner
+ val root = cleaner.clean(html)
+ root.getElementsByName("div", true).find(_.getAttributeByName("id") == id).map { div =>
+ StringEscapeUtils.unescapeHtml4(div.getText.toString())
+ }
+ }*/
+
+} \ No newline at end of file