Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix basic auth #1413

Merged
merged 7 commits into from Feb 11, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 22 additions & 4 deletions lib/auth.js
Expand Up @@ -8,8 +8,9 @@ var md5 = helpers.md5
, toBase64 = helpers.toBase64


function Auth () {
function Auth (request) {
// define all public properties here
this.request = request
this.hasAuth = false
this.sentAuth = false
this.bearerToken = null
Expand Down Expand Up @@ -108,11 +109,28 @@ Auth.prototype.digest = function (method, path, authHeader) {
return authHeader
}

Auth.prototype.response = function (method, path, headers) {
Auth.prototype.onRequest = function (user, pass, sendImmediately, bearer) {
var self = this
, request = self.request

var authHeader
if (bearer !== undefined) {
authHeader = self.bearer(bearer, sendImmediately)
} else {
authHeader = self.basic(user, pass, sendImmediately)
}
if (authHeader) {
request.setHeader('authorization', authHeader)
}
}

Auth.prototype.onResponse = function (response) {
var self = this
, request = self.request

if (!self.hasAuth || self.sentAuth) { return null }

var c = caseless(headers)
var c = caseless(response.headers)

var authHeader = c.get('www-authenticate')
var authVerb = authHeader && authHeader.split(' ')[0].toLowerCase()
Expand All @@ -126,7 +144,7 @@ Auth.prototype.response = function (method, path, headers) {
return self.bearer(self.bearerToken, true)

case 'digest':
return self.digest(method, path, authHeader)
return self.digest(request.method, request.path, authHeader)
}
}

Expand Down
104 changes: 104 additions & 0 deletions lib/multipart.js
@@ -0,0 +1,104 @@
'use strict'

var uuid = require('node-uuid')
, CombinedStream = require('combined-stream')
, isstream = require('isstream')


function Multipart (request) {
this.request = request
this.boundary = uuid()
this.chunked = false
this.body = null
}

Multipart.prototype.isChunked = function (options) {
var self = this
, chunked = false
, parts = options.data || options

if (!parts.forEach) {
throw new Error('Argument error, options.multipart.')
}

if (self.request.getHeader('transfer-encoding') === 'chunked') {
chunked = true
}

if (options.chunked !== undefined) {
chunked = options.chunked
}

if (!chunked) {
parts.forEach(function (part) {
if(typeof part.body === 'undefined') {
throw new Error('Body attribute missing in multipart.')
}
if (isstream(part.body)) {
chunked = true
}
})
}

return chunked
}

Multipart.prototype.setHeaders = function (chunked) {
var self = this

if (chunked && !self.request.hasHeader('transfer-encoding')) {
self.request.setHeader('transfer-encoding', 'chunked')
}

var header = self.request.getHeader('content-type')
var contentType = (!header || header.indexOf('multipart') === -1)
? 'multipart/related'
: header.split(';')[0]

self.request.setHeader('content-type', contentType + '; boundary=' + self.boundary)
}

Multipart.prototype.build = function (parts, chunked) {
var self = this
var body = chunked ? new CombinedStream() : []

function add (part) {
return chunked ? body.append(part) : body.push(new Buffer(part))
}

if (self.request.preambleCRLF) {
add('\r\n')
}

parts.forEach(function (part) {
var preamble = '--' + self.boundary + '\r\n'
Object.keys(part).forEach(function (key) {
if (key === 'body') { return }
preamble += key + ': ' + part[key] + '\r\n'
})
preamble += '\r\n'
add(preamble)
add(part.body)
add('\r\n')
})
add('--' + self.boundary + '--')

if (self.request.postambleCRLF) {
add('\r\n')
}

return body
}

Multipart.prototype.onRequest = function (options) {
var self = this

var chunked = self.isChunked(options)
, parts = options.data || options

self.setHeaders(chunked)
self.chunked = chunked
self.body = self.build(parts, chunked)
}

exports.Multipart = Multipart
35 changes: 20 additions & 15 deletions lib/oauth.js
Expand Up @@ -7,7 +7,11 @@ var querystring = require('querystring')
, oauth = require('oauth-sign')


exports.buildParams = function (_oauth, uri, method, query, form, qsLib) {
function OAuth (request) {
this.request = request
}

OAuth.prototype.buildParams = function (_oauth, uri, method, query, form, qsLib) {
var oa = {}
for (var i in _oauth) {
oa['oauth_' + i] = _oauth[i]
Expand Down Expand Up @@ -54,7 +58,7 @@ exports.buildParams = function (_oauth, uri, method, query, form, qsLib) {
return oa
}

exports.concatParams = function (oa, sep, wrap) {
OAuth.prototype.concatParams = function (oa, sep, wrap) {
wrap = wrap || ''

var params = Object.keys(oa).filter(function (i) {
Expand All @@ -71,13 +75,15 @@ exports.concatParams = function (oa, sep, wrap) {
}).join(sep)
}

exports.oauth = function (args) {
var uri = args.uri || {}
, method = args.method || ''
, headers = caseless(args.headers)
, body = args.body || ''
, _oauth = args.oauth || {}
, qsLib = args.qsLib || qs
OAuth.prototype.onRequest = function (_oauth) {
var self = this
, request = self.request

var uri = request.uri || {}
, method = request.method || ''
, headers = caseless(request.headers)
, body = request.body || ''
, qsLib = request.qsLib || qs

var form
, query
Expand All @@ -99,23 +105,22 @@ exports.oauth = function (args) {

var oa = this.buildParams(_oauth, uri, method, query, form, qsLib)

var data
switch (transport) {
case 'header':
data = 'OAuth ' + this.concatParams(oa, ',', '"')
request.setHeader('Authorization', 'OAuth ' + this.concatParams(oa, ',', '"'))
break

case 'query':
data = (query ? '&' : '?') + this.concatParams(oa, '&')
request.path = (query ? '&' : '?') + this.concatParams(oa, '&')
break

case 'body':
data = (form ? form + '&' : '') + this.concatParams(oa, '&')
request.body = (form ? form + '&' : '') + this.concatParams(oa, '&')
break

default:
throw new Error('oauth: transport_method invalid')
}

return {oauth:data, transport:transport}
}

exports.OAuth = OAuth
146 changes: 146 additions & 0 deletions lib/redirect.js
@@ -0,0 +1,146 @@
'use strict'

var url = require('url')
var isUrl = /^https?:/

function Redirect (request) {
this.request = request
this.followRedirect = true
this.followRedirects = true
this.followAllRedirects = false
this.allowRedirect = function () {return true}
this.maxRedirects = 10
this.redirects = []
this.redirectsFollowed = 0
}

Redirect.prototype.onRequest = function () {
var self = this
, request = self.request

if (request.maxRedirects !== undefined) {
self.maxRedirects = request.maxRedirects
}
if (typeof request.followRedirect === 'function') {
self.allowRedirect = request.followRedirect
}
if (request.followRedirect !== undefined) {
self.followRedirects = !!request.followRedirect
}
if (request.followAllRedirects !== undefined) {
self.followAllRedirects = request.followAllRedirects
}
if (self.followRedirects || self.followAllRedirects) {
self.redirects = self.redirects || []
}
}

Redirect.prototype.redirectTo = function (response) {
var self = this
, request = self.request

var redirectTo = null
if (response.statusCode >= 300 && response.statusCode < 400 && response.caseless.has('location')) {
var location = response.caseless.get('location')
// debug('redirect', location)

if (self.followAllRedirects) {
redirectTo = location
} else if (self.followRedirects) {
switch (request.method) {
case 'PATCH':
case 'PUT':
case 'POST':
case 'DELETE':
// Do not follow redirects
break
default:
redirectTo = location
break
}
}
} else if (response.statusCode === 401) {
var authHeader = request._auth.onResponse(response)
if (authHeader) {
request.setHeader('authorization', authHeader)
redirectTo = request.uri
}
}
return redirectTo
}

Redirect.prototype.onResponse = function (response) {
var self = this
, request = self.request

var redirectTo = self.redirectTo(response)
if (!redirectTo || !self.allowRedirect.call(request, response)) {
return false
}


// debug('redirect to', redirectTo)

// ignore any potential response body. it cannot possibly be useful
// to us at this point.
if (request._paused) {
response.resume()
}

if (self.redirectsFollowed >= self.maxRedirects) {
request.emit('error', new Error('Exceeded maxRedirects. Probably stuck in a redirect loop ' + request.uri.href))
return false
}
self.redirectsFollowed += 1

if (!isUrl.test(redirectTo)) {
redirectTo = url.resolve(request.uri.href, redirectTo)
}

var uriPrev = request.uri
request.uri = url.parse(redirectTo)

// handle the case where we change protocol from https to http or vice versa
if (request.uri.protocol !== uriPrev.protocol) {
request._updateProtocol()
}

self.redirects.push(
{ statusCode : response.statusCode
, redirectUri: redirectTo
}
)
if (self.followAllRedirects && response.statusCode !== 401 && response.statusCode !== 307) {
request.method = 'GET'
}
// request.method = 'GET' // Force all redirects to use GET || commented out fixes #215
delete request.src
delete request.req
delete request.agent
delete request._started
if (response.statusCode !== 401 && response.statusCode !== 307) {
// Remove parameters from the previous response, unless this is the second request
// for a server that requires digest authentication.
delete request.body
delete request._form
if (request.headers) {
request.removeHeader('host')
request.removeHeader('content-type')
request.removeHeader('content-length')
if (request.uri.hostname !== request.originalHost.split(':')[0]) {
// Remove authorization if changing hostnames (but not if just
// changing ports or protocols). This matches the behavior of curl:
// https://github.com/bagder/curl/blob/6beb0eee/lib/http.c#L710
request.removeHeader('authorization')
}
}
}

request.emit('redirect')

request.init()

return true
}

exports.Redirect = Redirect