pyc-website

main website for pyc inc.

git clone https://9o.is/git/pyc-website.git

commit f43e77bfc7e21ddab643ff9cecb0013bd9813de2
parent 4b043736d3649ae2fac4818016d95ed62bd208ed
Author: Jul <jul@9o.is>
Date:   Fri,  6 Jun 2014 03:23:15 -0400

transfered s3 to separate project (fixes issue #24)

Diffstat:
Mproject/Build.scala | 3++-
Mproject/BuildSettings.scala | 12++++++++----
Msrc/main/scala/inc/pyc/lib/IdVerification.scala | 2+-
Dsrc/main/scala/inc/pyc/lib/aws/S3.scala | 179-------------------------------------------------------------------------------
4 files changed, 11 insertions(+), 185 deletions(-)

diff --git a/project/Build.scala b/project/Build.scala @@ -17,7 +17,8 @@ object LiftProjectBuild extends Build { "org.eclipse.jetty" % "jetty-webapp" % Ver.jetty % "container", "ch.qos.logback" % "logback-classic" % "1.0.13" % "compile", "org.scalatest" %% "scalatest" % "1.9.2" % "test", - "com.foursquare" %% "rogue-lift_3.0" % "2.3.0-SNAPSHOT" + "com.foursquare" %% "rogue-lift_3.0" % "2.3.0-SNAPSHOT", + "inc.pyc" %% "aws-s3" % "0.1" ) ) } diff --git a/project/BuildSettings.scala b/project/BuildSettings.scala @@ -56,9 +56,13 @@ object BuildSettings { else Seq("-deprecation", "-unchecked") }, - resolvers += "Sonatype Releases" at "http://oss.sonatype.org/content/repositories/releases", - resolvers += "Sonatype Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots", - resolvers += "tuhlmann" at "https://bitbucket.org/agynamix/mvn-repo/raw/master/snapshots/" + resolvers ++= Seq[Resolver]( + "Sonatype Releases" at "http://oss.sonatype.org/content/repositories/releases", + "Sonatype Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots", + "tuhlmann" at "https://bitbucket.org/agynamix/mvn-repo/raw/master/snapshots/", + s3resolver.value("PYC Releases", s3("releases-pyc-inc")), + s3resolver.value("PYC Snapshots", s3("snapshots-pyc-inc")) + ) ) val gruntSettings = Seq( @@ -115,7 +119,7 @@ object BuildSettings { publishTo := Some(s3resolver.value( "My "+{if (isSnapshot.value) "snapshots-pyc-inc" else "releases-pyc-inc"}+" S3 bucket", - s3(if (isSnapshot.value) "snapshots-pyc-inc" else "releasespyc-inc"+".pyc.inc")) withIvyPatterns), + s3(if (isSnapshot.value) "snapshots-pyc-inc" else "releases-pyc-inc")) withIvyPatterns), s3credentials := { file(".") / "project" / ".s3credentials" diff --git a/src/main/scala/inc/pyc/lib/IdVerification.scala b/src/main/scala/inc/pyc/lib/IdVerification.scala @@ -1,7 +1,6 @@ package inc.pyc package lib -import aws._ import model._ import config._ import net.liftweb._ @@ -11,6 +10,7 @@ import rest._ import common._ import js.JsCmds._ import net.liftmodules.mongoauth.MongoAuth +import inc.pyc.aws.s3._ /** * The identification files uploaded by a user will be stored here. diff --git a/src/main/scala/inc/pyc/lib/aws/S3.scala b/src/main/scala/inc/pyc/lib/aws/S3.scala @@ -1,178 +0,0 @@ -package inc.pyc -package lib -package aws - -import dispatch._, Defaults._ -import com.ning.http.client.RequestBuilder -import com.ning.http.util.Base64 -import java.net.URLEncoder._ - -private object AmazonS3 { - - import javax.crypto - - import java.util.{Date, Locale, SimpleTimeZone} - import java.text.SimpleDateFormat - - val UTF_8 = "UTF-8" - - val Root = "s3.amazonaws.com" - - object rfc822DateParser extends SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US) { - this.setTimeZone(new SimpleTimeZone(0, "GMT")) - } - - def trim(s: String): String = s.dropWhile(_ == ' ').reverse.dropWhile(_ == ' ').reverse.toString - - def md5(bytes: Array[Byte]) = { - import java.security.MessageDigest - - val r = MessageDigest.getInstance("MD5") - r.reset() - r.update(bytes) - Base64.encode(r.digest) - } - - def md5(stream: java.io.InputStream) = { - import java.security.MessageDigest - - val buffer = new Array[Byte](1024) - val r = MessageDigest.getInstance("MD5") - var numRead: Int = 0 - do { - numRead = stream.read(buffer) - if (numRead > 0) { - r.update(buffer, 0, numRead) - } - } while (numRead != -1) - - stream.close() - Base64.encode(r.digest()) - } - - def sign(method: String, path: String, secretKey: String, date: Date, - contentType: Option[String], contentMd5: Option[String], amzHeaders: Map[String, Set[String]]): String = { - sign(method, path, secretKey, Left(date), contentType, contentMd5, amzHeaders) - } - - def sign(method: String, path: String, secretKey: String, dateOrExpires: Either[Date, Long], - contentType: Option[String], contentMd5: Option[String], amzHeaders: Map[String, Set[String]]) = { - val SHA1 = "HmacSHA1" - val message = canonicalString(method, path, dateOrExpires, contentType, contentMd5, amzHeaders) - val sig = { - val mac = crypto.Mac.getInstance(SHA1) - val key = new crypto.spec.SecretKeySpec(bytes(secretKey), SHA1) - mac.init(key) - Base64.encode(mac.doFinal(bytes(message))) - } - sig - } - - def signedUri(accessKey: String, secretKey: String, method: String, path: String, amzHeaders: Map[String, Set[String]], - expires: Long = defaultExpiryTime, - contentType: Option[String] = None, contentMd5: Option[String] = None) = { - val signed = encode(sign(method, path, secretKey, Right(expires), contentType, contentMd5, amzHeaders), "UTF-8") - "%s?Signature=%s&Expires=%s&AWSAccessKeyId=%s".format(path, signed, expires, accessKey) - } - - def defaultExpiryTime = System.currentTimeMillis() / 1000 + 600 - - /** - * @return the canonical request string that needs to be signed for authentication - */ - def canonicalString(method: String, path: String, dateOrExpires: Either[Date, Long], contentType: Option[String], - contentMd5: Option[String], amzHeaders: Map[String, Set[String]]) = { - val amzString = amzHeaders.toList.sortWith(_._1.toLowerCase < _._1.toLowerCase).map { - case (k, v) => "%s:%s".format(k.toLowerCase, v.map(trim).mkString(",")) - } - val dateExpiresString = dateOrExpires match { - case Left(date) => rfc822DateParser.format(date) - case Right(expires) => expires.toString - } - (method :: contentMd5.getOrElse("") :: contentType.getOrElse("") :: dateExpiresString :: Nil) ++ amzString ++ List(path) mkString "\n" - } - - def bytes(s: String) = s.getBytes(UTF_8) - - implicit def Request2S3RequestSigner(r: RequestBuilder) = new S3RequestSigner(r) - - implicit def Request2S3RequestSigner(r: String) = new S3RequestSigner(new RequestBuilder().setUrl(r)) - - class S3RequestSigner(r: RequestBuilder) { - - import scala.collection.JavaConverters._ - - protected def path = RawUri(r.build.getUrl).path.getOrElse("") - - def <@(accessKey: String, secretKey: String) = { - val req = r.build - val contentStream = req.getStreamData - val contentMd5 = if (req.getContentLength <= 0) None else Some(md5(contentStream)) - - for (cmd5 <- contentMd5) - r.addHeader("Content-MD5", cmd5) - - val headers = req.getHeaders - val contentType = headers.keySet.asScala.find { - _.toLowerCase == "content-type" - }.map { - headers.get(_).asScala.head - } - - val d = new Date - r.addHeader("Authorization", "AWS %s:%s".format(accessKey, sign(req.getMethod, path, secretKey, d, contentType, contentMd5, amazonHeaders))) - r.addHeader("Date", AmazonS3.rfc822DateParser.format(d)) - r - } - - def signed(accessKey: String, secretKey: String, expires: Long = defaultExpiryTime, - contentType: Option[String] = None, contentMd5: Option[String] = None): RequestBuilder = { - val req = r.build - val path = RawUri(req.getUrl).path.getOrElse("") - val uri = signedUri(accessKey, secretKey, req.getMethod, path, amazonHeaders, expires, contentType, contentMd5) - val requestHeaders = for { - (key, Some(value)) <- Map("Content-Type" -> contentType, "Content-Md5" -> contentMd5) - } yield key -> value - (:/(AmazonS3.Root) / uri.substring(1)).setMethod(req.getMethod).setHeaders(req.getHeaders).secure <:< requestHeaders - } - - private def amazonHeaders = { - val headers = r.build.getHeaders - headers.keySet.asScala.filter { - _.toLowerCase.startsWith("x-amz") - } - .map(name => name -> headers.get(name).asScala.toSet).toMap - } - } - -} - -private object Bucket extends (String => RequestBuilder) { - def apply(name: String) = :/(AmazonS3.Root) / name -} - -/** - * Amazon S3 API - */ -case class S3(access_key: String, secret_key: String, bucket: String, path: String = "") { - import AmazonS3._ - - /** - * Creates a file with file name, binary data and file content type. - */ - def createFile(fn: String, data: Array[Byte], contentType: String) = - Http.configure(_ setCompressionEnabled true)(Bucket(bucket).PUT.setBody(data) / (path + fn) <:< - Map("content-type" -> contentType) <@(access_key, secret_key)) - - /** - * Deletes a file by file name. - */ - def deleteFile(fn: String) = - Http.configure(_ setCompressionEnabled true)(Bucket(bucket).DELETE / - (path + fn) <@(access_key, secret_key)) - - /** - * Generates file's S3 url given a file name. - */ - def fileUrl(fn: String) = "http://" + AmazonS3.Root + "/" + bucket + path + fn -} -\ No newline at end of file