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

Feature/package provider #207

Merged
merged 4 commits into from
Apr 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions conf/full.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ packages:
# # you can override storage directory for a group of packages this way:
# storage: 'local_storage'

# Delegate handling package access authorization to an external
# plugin for packages with this prefix
#'external-*':
# plugin: my_plugin

'*':
# allow all users to read packages (including non-authenticated users)
#
Expand Down
43 changes: 10 additions & 33 deletions lib/auth.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
var Crypto = require('crypto')
var jju = require('jju')
var Error = require('http-errors')
var Logger = require('./logger')
var assert = require('assert')
var Crypto = require('crypto')
var jju = require('jju')
var Error = require('http-errors')
var Path = require('path')
var Logger = require('./logger')
var load_plugins = require('./plugin-loader').load_plugins

module.exports = Auth

Expand All @@ -11,9 +14,9 @@ function Auth(config) {
self.logger = Logger.logger.child({ sub: 'auth' })
self.secret = config.secret

var stuff = {
var plugin_params = {
config: config,
logger: self.logger,
logger: self.logger
}

if (config.users_file) {
Expand All @@ -24,33 +27,7 @@ function Auth(config) {
}
}

self.plugins = Object.keys(config.auth || {}).map(function(p) {
var plugin, name
try {
name = 'sinopia-' + p
plugin = require(name)
} catch(x) {
try {
name = p
plugin = require(name)
} catch(x) {}
}

if (plugin == null) {
throw Error('"' + p + '" auth plugin not found\n'
+ 'try "npm install sinopia-' + p + '"')
}

if (typeof(plugin) !== 'function')
throw Error('"' + name + '" doesn\'t look like a valid auth plugin')

plugin = plugin(config.auth[p], stuff)

if (plugin == null || typeof(plugin.authenticate) !== 'function')
throw Error('"' + name + '" doesn\'t look like a valid auth plugin')

return plugin
})
self.plugins = load_plugins(config.auth, plugin_params, 'auth', ['authenticate'])

self.plugins.unshift({
authenticate: function(user, password, cb) {
Expand Down
21 changes: 21 additions & 0 deletions lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ function Config(config) {

function check_userlist(i, hash, action) {
if (hash[action] == null) hash[action] = []
check_stringlist(i, hash, action)
}

function check_stringlist(i, hash, action) {
if (!hash[action]) return

// if it's a string, split it to array
if (typeof(hash[action]) === 'string') {
Expand All @@ -97,6 +102,20 @@ function Config(config) {
hash[action] = flatten(hash[action])
}

// if a field in a string or array, converts into a hash with keys
// of the string and values of empty object
function check_objectset(i, hash, action) {
if (!hash[action]) return
if (Array.isArray(hash[action]) || typeof(hash[action]) === 'string') {
check_stringlist(i, hash, action)
var new_object = {}
hash[action].forEach(function(string) {
new_object[string] = {}
})
hash[action] = new_object
}
}

for (var i in self.packages) {
assert(
typeof(self.packages[i]) === 'object' &&
Expand All @@ -108,6 +127,8 @@ function Config(config) {
check_userlist(i, self.packages[i], 'proxy_access')
check_userlist(i, self.packages[i], 'proxy_publish')

check_objectset(i, self.packages[i], 'plugin')

// deprecated
check_userlist(i, self.packages[i], 'access')
check_userlist(i, self.packages[i], 'proxy')
Expand Down
21 changes: 13 additions & 8 deletions lib/index-api.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ var match = Middleware.match
var media = Middleware.media
var validate_name = Middleware.validate_name
var validate_pkg = Middleware.validate_package
var async = require('async')

module.exports = function(config, auth, storage) {
module.exports = function(config, auth, storage, package_provider) {
var app = express.Router()
var can = Middleware.allow(config)
var can = Middleware.allow(config, package_provider)

// validate all of these params as a package name
// this might be too harsh, so ask if it causes trouble
Expand Down Expand Up @@ -85,12 +86,16 @@ module.exports = function(config, auth, storage) {
app.get('/-/all/:anything?', function(req, res, next) {
storage.search(req.param.startkey || 0, {req: req}, function(err, result) {
if (err) return next(err)
for (var pkg in result) {
if (!config.allow_access(pkg, req.remote_user)) {
delete result[pkg]
}
}
return next(result)
async.eachSeries(Object.keys(result), function(pkg, cb) {
package_provider.allow_access(pkg, req.remote_user, function(err, allowed) {
if(err) return cb(err)
if(!allowed) delete result[pkg]
cb()
})
}, function(err) {
if(err) return next(err)
next(result)
})
})
})

Expand Down
28 changes: 16 additions & 12 deletions lib/index-web.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@ var express = require('express')
var fs = require('fs')
var Handlebars = require('handlebars')
var renderReadme = require('render-readme')
var async = require('async')
var Search = require('./search')
var Middleware = require('./middleware')
var match = Middleware.match
var validate_name = Middleware.validate_name
var validate_pkg = Middleware.validate_package

module.exports = function(config, auth, storage) {
module.exports = function(config, auth, storage, package_provider) {
var app = express.Router()
var can = Middleware.allow(config)
var can = Middleware.allow(config, package_provider)

// validate all of these params as a package name
// this might be too harsh, so ask if it causes trouble
Expand Down Expand Up @@ -47,17 +48,20 @@ module.exports = function(config, auth, storage) {

storage.get_local(function(err, packages) {
if (err) throw err // that function shouldn't produce any
next(template({
name: config.web && config.web.title ? config.web.title : 'Sinopia',
packages: packages.filter(allow),
baseUrl: base,
username: req.remote_user.name,
}))
async.filterSeries(packages, function(package, cb) {
package_provider.allow_access(package.name, req.remote_user, function(err, allowed) {
if(err) cb(false)
else cb(allowed)
})
}, function(packages) {
next(template({
name: config.web && config.web.title ? config.web.title : 'Sinopia',
packages: packages,
baseUrl: base,
username: req.remote_user.name,
}))
})
})

function allow(package) {
return config.allow_access(package.name, req.remote_user)
}
})

// Static
Expand Down
32 changes: 17 additions & 15 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
var express = require('express')
var Error = require('http-errors')
var compression = require('compression')
var Auth = require('./auth')
var Logger = require('./logger')
var Config = require('./config')
var Middleware = require('./middleware')
var Cats = require('./status-cats')
var Storage = require('./storage')
var express = require('express')
var Error = require('http-errors')
var compression = require('compression')
var Auth = require('./auth')
var Logger = require('./logger')
var Config = require('./config')
var Middleware = require('./middleware')
var Cats = require('./status-cats')
var Storage = require('./storage')
var PackageProvider = require('./packages')

module.exports = function(config_hash) {
Logger.setup(config_hash.logs)

var config = Config(config_hash)
var storage = Storage(config)
var auth = Auth(config)
var app = express()
var config = Config(config_hash)
var storage = Storage(config)
var auth = Auth(config)
var packages = PackageProvider(config)
var app = express()

// run in production mode by default, just in case
// it shouldn't make any difference anyway
Expand Down Expand Up @@ -85,14 +87,14 @@ module.exports = function(config_hash) {
})
}

app.use(require('./index-api')(config, auth, storage))
app.use(require('./index-api')(config, auth, storage, packages))

if (config.web && config.web.enable === false) {
app.get('/', function(req, res, next) {
next( Error[404]('web interface is disabled in the config file') )
})
} else {
app.use(require('./index-web')(config, auth, storage))
app.use(require('./index-web')(config, auth, storage, packages))
}

app.get('/*', function(req, res, next) {
Expand Down
29 changes: 17 additions & 12 deletions lib/middleware.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,24 +76,29 @@ function md5sum(data) {
return crypto.createHash('md5').update(data).digest('hex')
}

module.exports.allow = function(config) {
module.exports.allow = function(config, packages) {
return function(action) {
return function(req, res, next) {
if (config['allow_'+action](req.params.package, req.remote_user)) {
next()
} else {
if (!req.remote_user.name) {
if (req.remote_user.error) {
var message = "can't "+action+' restricted package, ' + req.remote_user.error
req.pause();
packages['allow_'+action](req.params.package, req.remote_user, function(error, is_allowed) {
req.resume();
if(error) {
next(error)
} else if(is_allowed) {
next()
} else {
if (!req.remote_user.name) {
if (req.remote_user.error) {
var message = "can't "+action+' restricted package, ' + req.remote_user.error
} else {
var message = "can't "+action+" restricted package without auth, did you forget 'npm set always-auth true'?"
}
next( Error[403](message) )
} else {
var message = "can't "+action+" restricted package, you are not logged in"
}
next( Error[403](message) )
} else {
next( Error[403]('user ' + req.remote_user.name
+ ' not allowed to ' + action + ' it') )
}
}
})
}
}
}
Expand Down
Loading