-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #124 from guardian/an/import-panda-hmac
Pull panda-hmac into repo
- Loading branch information
Showing
15 changed files
with
409 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Client Examples | ||
|
||
Sometimes the best way to learn is by example. This folder contains a few example client implementations for various languages to show how you might call a HMAC'd service. If you've recently worked against `play-hmac` in a language without an example it would be great if you could add a snippet here. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
14.17.3 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
const crypto = require('crypto'); | ||
const reqwest = require('reqwest'); | ||
|
||
// The secret you share with the remote service. | ||
// Should *NOT* be hard coded, put it somewhere private (S3, Dynamo, properties file, etc.) | ||
const sharedSecret = "Sanguine, my brother."; | ||
|
||
// Make a hmac token from the required components. You probably want to copy this :) | ||
function makeHMACToken(secret, date, uri) { | ||
const hmac = crypto.createHmac('sha256', secret); | ||
|
||
const content = date + '\n' + uri; | ||
|
||
hmac.update(content, 'utf-8'); | ||
|
||
return "HMAC " + hmac.digest('base64'); | ||
} | ||
|
||
// It's important to remember the leading / | ||
const uri = "/api/examples"; | ||
const date = (new Date()).toUTCString(); | ||
const token = makeHMACToken(sharedSecret, date, uri); | ||
|
||
// Make a request to our example API with the generated HMAC | ||
reqwest({ | ||
url: "http://example.com" + uri, | ||
method: 'GET', | ||
headers: { | ||
'X-Gu-Tools-HMAC-Date': date, | ||
'X-Gu-Tools-HMAC-Token': token, | ||
'X-Gu-Tools-Service-Name': 'example-service-name' | ||
} | ||
}).then(function(resp) { | ||
console.log('We did it!'); | ||
}, function(err, msg) { | ||
console.error('Something went wrong :('); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"dependencies": { | ||
"reqwest": "2.0.5", | ||
"xhr2": "0.1.3" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
#!/usr/bin/python | ||
|
||
import hashlib | ||
import hmac | ||
from optparse import OptionParser | ||
from datetime import datetime | ||
import base64 | ||
from email.utils import formatdate | ||
import requests | ||
from time import mktime | ||
from urlparse import urlparse | ||
from pprint import pprint | ||
|
||
def get_token(uri, secret): | ||
httpdate = formatdate(timeval=mktime(datetime.now().timetuple()),localtime=False,usegmt=True) | ||
url_parts = urlparse(uri) | ||
|
||
string_to_sign = "{0}\n{1}".format(httpdate, url_parts.path) | ||
print "string_to_sign: " + string_to_sign | ||
hm = hmac.new(secret, string_to_sign,hashlib.sha256) | ||
return "HMAC {0}".format(base64.b64encode(hm.digest())), httpdate | ||
|
||
#START MAIN | ||
parser = OptionParser() | ||
parser.add_option("--host", dest="host", help="host to access", default="video.local.dev-gutools.co.uk") | ||
parser.add_option("-a", "--atom", dest="atom", help="uuid of the atom to request") | ||
parser.add_option("-s", "--secret", dest="secret", help="shared secret to use") | ||
(options, args) = parser.parse_args() | ||
|
||
if options.secret is None: | ||
print "You must supply the password in --secret" | ||
exit(1) | ||
|
||
uri = "https://{host}/pluto/resend/{id}".format(host=options.host, id=options.atom) | ||
print "uri is " + uri | ||
authtoken, httpdate = get_token(uri, options.secret) | ||
print authtoken | ||
|
||
headers = { | ||
'X-Gu-Tools-HMAC-Date': httpdate, | ||
'X-Gu-Tools-HMAC-Token': authtoken | ||
} | ||
|
||
print headers | ||
response = requests.post(uri,headers=headers) | ||
print "Server returned {0}".format(response.status_code) | ||
pprint(response.headers) | ||
if response.status_code==200: | ||
pprint(response.json()) | ||
else: | ||
print response.text |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
requests==2.18.4 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package example | ||
|
||
import java.net.URI | ||
import com.gu.hmac.HMACHeaders | ||
|
||
// When using scala you get the `hmac-headers` library and use it directly to generate your HMAC tokens | ||
object HMACClient extends HMACHeaders { | ||
val secret = "Sanguine, my brother." | ||
|
||
// Unlike the javascript example, with the hmac-headers library you don't provide it a date, it generates one for you | ||
def makeHMACToken(uri: String): HMACHeaderValues = { | ||
createHMACHeaderValues(new URI(uri)) | ||
} | ||
} | ||
|
||
object ExampleRequestSender { | ||
def sendRequest = { | ||
val uri = "/api/examples" | ||
ws.url("example.com" + uri) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
name := "scala-hmac-client-example" | ||
|
||
scalaVersion := "2.11.8" | ||
|
||
libraryDependencies += "com.gu" %% "panda-hmac" % "1.1" |
81 changes: 81 additions & 0 deletions
81
pan-domain-auth-hmac/src/main/scala/com/gu/pandahmac/HmacAuthActions.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package com.gu.pandahmac | ||
import com.gu.hmac.{HMACHeaders, ValidateHMACHeader} | ||
import com.gu.pandomainauth.action.{AuthActions, UserRequest} | ||
import com.gu.pandomainauth.model.User | ||
import org.slf4j.LoggerFactory | ||
import play.api.libs.ws.WSClient | ||
import play.api.mvc.Results._ | ||
import play.api.mvc._ | ||
|
||
import java.net.URI | ||
import scala.concurrent.{ExecutionContext, Future} | ||
|
||
|
||
object HMACHeaderNames { | ||
val hmacKey = "X-Gu-Tools-HMAC-Token" | ||
val dateKey = "X-Gu-Tools-HMAC-Date" | ||
// Optional header to give the emulated user a nice name, if this isn't present we default to 'hmac-authed-service' | ||
val serviceNameKey = "X-Gu-Tools-Service-Name" | ||
} | ||
|
||
trait HMACAuthActions extends AuthActions with HMACSecrets { | ||
/** | ||
* Play application | ||
* Play application components that you must provide in order to use AuthActions | ||
*/ | ||
def wsClient: WSClient | ||
def controllerComponents: ControllerComponents | ||
|
||
private implicit val ec: ExecutionContext = controllerComponents.executionContext | ||
|
||
private def authByKeyOrPanda[A](request: Request[A], block: RequestHandler[A], useApiAuth: Boolean): Future[Result] = { | ||
val oHmac: Option[String] = request.headers.get(HMACHeaderNames.hmacKey) | ||
val oDate: Option[String] = request.headers.get(HMACHeaderNames.dateKey) | ||
val oServiceName: Option[String] = request.headers.get(HMACHeaderNames.serviceNameKey) | ||
val uri = new URI(request.uri) | ||
|
||
(oHmac, oDate) match { | ||
case (Some(hmac), Some(date)) => { | ||
if (validateHMACHeaders(date, hmac, uri)) { | ||
val user = User(oServiceName.getOrElse("hmac-authed-service"), "", "", None) | ||
block(new UserRequest(user, request)) | ||
} else { | ||
Future.successful(Unauthorized) | ||
} | ||
} | ||
case _ => if(useApiAuth) apiAuthByPanda(request, block) else authByPanda(request, block) | ||
} | ||
} | ||
|
||
type RequestHandler[A] = UserRequest[A] => Future[Result] | ||
|
||
def authByPanda[A](request: Request[A], block: RequestHandler[A]): Future[Result] = | ||
AuthAction.invokeBlock(request, (request: UserRequest[A]) => { | ||
block(new UserRequest(request.user, request)) | ||
}) | ||
|
||
def apiAuthByPanda[A](request: Request[A], block: RequestHandler[A]): Future[Result] = | ||
APIAuthAction.invokeBlock(request, (request: UserRequest[A]) => { | ||
block(new UserRequest(request.user, request)) | ||
}) | ||
|
||
|
||
/* as per https://www.playframework.com/documentation/2.6.x/ScalaActionsComposition */ | ||
object HMACAuthAction extends ActionBuilder[UserRequest, AnyContent] { | ||
override def parser: BodyParser[AnyContent] = HMACAuthActions.this.controllerComponents.parsers.default | ||
override protected def executionContext: ExecutionContext = HMACAuthActions.this.controllerComponents.executionContext | ||
|
||
override def invokeBlock[A](request: Request[A], block: RequestHandler[A]): Future[Result] = { | ||
authByKeyOrPanda(request, block, useApiAuth = false) | ||
} | ||
} | ||
|
||
|
||
object APIHMACAuthAction extends ActionBuilder[UserRequest, AnyContent] { | ||
override def parser: BodyParser[AnyContent] = HMACAuthActions.this.controllerComponents.parsers.default | ||
override protected def executionContext: ExecutionContext = HMACAuthActions.this.controllerComponents.executionContext | ||
|
||
override def invokeBlock[A](request: Request[A], block: RequestHandler[A]): Future[Result] = | ||
authByKeyOrPanda(request, block, useApiAuth = true) | ||
} | ||
} |
Oops, something went wrong.