Skip to content

Commit

Permalink
feat: adds support for x-forwarded-host
Browse files Browse the repository at this point in the history
Adds `trustProxy` option to HMAC_SHA1 and Provider constructor.

It overloads the Provider constructor with the third argument being either a nonceStore or an options object (for a nonceStore, a signer and the trustProxy flag):

    new Provider(key: string, secret: string, options: NonceStore|Options, signer: Algo)
  • Loading branch information
dinoboff committed Aug 17, 2017
1 parent 4df2936 commit 72c513a
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 17 deletions.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,20 @@ Once you find the `oauth_consumer_secret` based on the `oauth_consumer_key` in t

```coffeescript
lti = require 'ims-lti'
hmac = require 'ims-lti/lib/hmac-sha1'

provider = new lti.Provider consumer_key, consumer_secret, [nonce_store=MemoryStore], [signature_method=HMAC_SHA1]
provider = new lti.Provider consumer_key, consumer_secret

# To use x-forwarded-* headers
provider = new lti.Provider consumer_key, consumer_secret, trustProxy: true

# To provide the nonce store
store = new lti.Stores.MemoryStore
provider = new lti.Provider consumer_key, consumer_secret, nonceStore: store

# To provide the signer
sig = new hmac.HMAC_SHA1 trustProxy: false
provider = new lti.Provider consumer_key, consumer_secret, signer: sig
```

Once the provider has been initialized, a reqest object can be validated against it. During validation, OAuth signatures are checked against the passed consumer_secret and signautre_method ( HMAC_SHA1 assumed ). isValid returns true if the request is an lti request and is properly signed.
Expand Down
28 changes: 22 additions & 6 deletions src/hmac-sha1.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ _clean_request_body = (body, query) ->

class HMAC_SHA1

constructor: (options) ->
@trustProxy = (options and options.trustProxy) or false

toString: () ->
'HMAC_SHA1'

Expand All @@ -52,24 +55,37 @@ class HMAC_SHA1

@sign_string sig.join('&'), consumer_secret, token

host: (req) ->
if not @trustProxy
return req.headers.host

req.headers['x-forwarded-host'] or req.headers.host

protocol: (req) ->
xprotocol = req.headers['x-forwarded-proto']
if @trustProxy and xprotocol
return xprotocol

if req.protocol
return req.protocol

if req.connection.encrypted then 'https' else 'http'

build_signature: (req, body, consumer_secret, token) ->
hapiRawReq = req.raw and req.raw.req
if hapiRawReq
req = hapiRawReq

originalUrl = req.originalUrl or req.url
protocol = req.protocol
host = @host req
protocol = @protocol req

# Since canvas includes query parameters in the body we can omit the query string
if body.tool_consumer_info_product_family_code == 'canvas'
originalUrl = url.parse(originalUrl).pathname

if protocol is undefined
encrypted = req.connection.encrypted
protocol = (encrypted and 'https') or 'http'

parsedUrl = url.parse originalUrl, true
hitUrl = protocol + '://' + req.headers.host + parsedUrl.pathname
hitUrl = protocol + '://' + host + parsedUrl.pathname

@build_signature_raw hitUrl, parsedUrl, req.method, body, consumer_secret, token

Expand Down
14 changes: 9 additions & 5 deletions src/provider.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,23 @@ extensions = require './extensions'


class Provider
constructor: (consumer_key, consumer_secret, nonceStore, signature_method=(new HMAC_SHA1()) ) ->
constructor: (consumer_key, consumer_secret, optionsOrNonceStore, signature_method) ->

if typeof consumer_key is 'undefined' or consumer_key is null
throw new errors.ConsumerError 'Must specify consumer_key'

if typeof consumer_secret is 'undefined' or consumer_secret is null
throw new errors.ConsumerError 'Must specify consumer_secret'

if not nonceStore
nonceStore = new MemoryNonceStore()
if optionsOrNonceStore and optionsOrNonceStore.isNonceStore?()
options = {}
nonceStore = optionsOrNonceStore
else
options = optionsOrNonceStore or {}
nonceStore = options.nonceStore or new MemoryNonceStore()

if not nonceStore.isNonceStore?()
throw new errors.ParameterError 'Fourth argument must be a nonceStore object'
if not signature_method
signature_method = options.signer or new HMAC_SHA1(options)

@consumer_key = consumer_key
@consumer_secret = consumer_secret
Expand Down
28 changes: 27 additions & 1 deletion test/Provider.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe 'LTI.Provider', () ->
provider.consumer_key.should.equal consumer_key
provider.consumer_secret.should.equal consumer_secret
provider.signer.toString().should.equal sig.toString()

provider.signer.trustProxy.should.equal false


it 'should accept (consumer_key, consumer_secret, nonceStore, sig)', () =>
Expand All @@ -44,6 +44,32 @@ describe 'LTI.Provider', () ->
provider.nonceStore.should.equal nonceStore


it 'should accept (consumer_key, consumer_secret, nonceStore: store)', () =>
nonceStore =
isNonceStore: ()->true
isNew: ()->return
setUsed: ()->return

provider = new @lti.Provider('10204','secret-shhh',nonceStore:nonceStore,trustProxy:true)
provider.nonceStore.should.equal nonceStore
provider.signer.trustProxy.should.equal true


it 'should accept (consumer_key, consumer_secret, signer: sig)', () =>
sig =
me: 3
you: 1
total: 4

provider = new @lti.Provider('10204','secret-shhh',signer:sig)
provider.signer.should.equal sig


it 'should accept (consumer_key, consumer_secret, trustProxy: true)', () =>
provider = new @lti.Provider('10204','secret-shhh',trustProxy:true)
provider.signer.trustProxy.should.equal true


it 'should throw an error if no consumer_key or consumer_secret', () =>
(()=>provider = new @lti.Provider()).should.throw(lti.Errors.ConsumerError)
(()=>provider = new @lti.Provider('consumer-key')).should.throw(lti.Errors.ConsumerError)
Expand Down
36 changes: 32 additions & 4 deletions test/Signer.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@ should = require 'should'

HMAC_SHA1 = require '../lib/hmac-sha1'



signer = new HMAC_SHA1

describe 'Signer', () ->

it 'should include query params', (done) ->
signer = new HMAC_SHA1
req =
url: '/developers/LTI/test/v1p1/tool.php?foo=123&foo=bar'
method: 'POST'
Expand Down Expand Up @@ -36,3 +33,34 @@ describe 'Signer', () ->

done()

it 'should support x-forwarded-*', (done) ->
signer = new HMAC_SHA1 trustProxy: true
req =
url: '/developers/LTI/test/v1p1/tool.php?foo=123&foo=bar'
method: 'POST'
connection:
encrypted: true
headers:
host: 'localhost:5000'
'x-forwarded-host': 'www.imsglobal.org'
'x-forwarded-proto': 'http'
body =
resource_link_id: 'rsc1',
oauth_callback: 'about:blank',
lis_outcome_service_url: 'http://www.imsglobal.org/developers/LTI/test/v1p1/common/tool_consumer_outcome.php?b64=MTIzNDU6OjpzZWNyZXQ=',
lis_result_sourcedid: 'feb-123-456-2929::28883',
launch_presentation_return_url: 'http://www.imsglobal.org/developers/LTI/test/v1p1/lms_return.php',
lti_version: 'LTI-1p0',
lti_message_type: 'basic-lti-launch-request',
oauth_version: '1.0',
oauth_nonce: '7ee33f6dc94117e792ff529898ce3953',
oauth_timestamp: '1397708483',
oauth_consumer_key: '12345',
oauth_signature_method: 'HMAC-SHA1',
oauth_signature: 'dHORwwJqwh5hQQAlvaA9csSIOhc='

signature = signer.build_signature req, body, 'secret'
signature.should.equal body.oauth_signature

done()

0 comments on commit 72c513a

Please sign in to comment.