0
Fork 0
mirror of https://github.com/verdaccio/verdaccio.git synced 2025-02-17 23:45:29 -05:00

Add package settings and authorization plugin system

This commit is contained in:
Chris Breneman 2015-02-24 14:28:16 -05:00
parent 3fe188df21
commit 76a1e8df80
7 changed files with 221 additions and 71 deletions

View file

@ -1,9 +1,10 @@
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 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
@ -13,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) {
@ -26,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) {
@ -321,7 +296,7 @@ Auth.prototype.aes_decrypt = function(buf) {
function AnonymousUser() {
return {
name: undefined,
// groups without '$' are going to be deprecated eventually
// groups without '$' are going to be deprecated eventually
groups: [ '$all', '$anonymous', '@all', '@anonymous', 'all', 'undefined', 'anonymous' ],
real_groups: [],
}

View file

@ -11,9 +11,9 @@ var media = Middleware.media
var validate_name = Middleware.validate_name
var validate_pkg = Middleware.validate_package
module.exports = function(config, auth, storage) {
module.exports = function(config, auth, storage, packages) {
var app = express.Router()
var can = Middleware.allow(config)
var can = Middleware.allow(config, packages)
// validate all of these params as a package name
// this might be too harsh, so ask if it causes trouble

View file

@ -11,9 +11,9 @@ 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, packages) {
var app = express.Router()
var can = Middleware.allow(config)
var can = Middleware.allow(config, packages)
// validate all of these params as a package name
// this might be too harsh, so ask if it causes trouble

View file

@ -1,22 +1,24 @@
var express = require('express')
var fs = require('fs')
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 fs = require('fs')
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 can = Middleware.allow(config)
var config = Config(config_hash)
var storage = Storage(config)
var auth = Auth(config)
var packages = PackageProvider(config)
var app = express()
var can = Middleware.allow(config, packages)
// run in production mode by default, just in case
// it shouldn't make any difference anyway
@ -87,14 +89,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) {

View file

@ -76,24 +76,30 @@ 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
} else {
var message = "can't "+action+" restricted package without auth, did you forget 'npm set always-auth true'?"
}
next( Error[403](message) )
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 {
next( Error[403]('user ' + req.remote_user.name
+ ' not allowed to ' + action + ' it') )
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 {
next( Error[403]('user ' + req.remote_user.name
+ ' not allowed to ' + action + ' it') )
}
}
}
})
}
}
}

126
lib/packages.js Normal file
View file

@ -0,0 +1,126 @@
var Logger = require('./logger')
var load_plugins = require('./plugin-loader').load_plugins
var async = require('async')
module.exports = PackageProvider
// provides configuration and access control information for packages
function PackageProvider(config) {
var self = Object.create(PackageProvider.prototype)
self.config = config
self.logger = Logger.logger.child({ sub: 'packages' })
var plugin_params = {
config: config,
logger: self.logger
}
self.plugins = load_plugins(config.package_provider, plugin_params, 'package_provider', ['allow_access'])
self.plugins.push(new ConfigPackageProvider({}, plugin_params))
self.plugins.push(new DenyPackageProvider({}, plugin_params))
return self
}
function check_plugin_result(function_name, package, arg, cb) {
var current_result
async.eachSeries(this.plugins, function(plugin, next) {
if(current_result === undefined && typeof plugin[function_name] === 'function') {
plugin[function_name](package, arg, function(error, result) {
if(error) {
next(error)
} else {
current_result = result
next()
}
})
} else {
next()
}
}, function(error) {
if(error) {
cb(error)
} else {
cb(null, current_result)
}
})
}
PackageProvider.prototype.allow_access = function(package, user, cb) {
check_plugin_result.call(this, 'allow_access', package, user, cb)
}
PackageProvider.prototype.allow_publish = function(package, user, cb) {
check_plugin_result.call(this, 'allow_publish', package, user, cb)
}
PackageProvider.prototype.proxy_access = function(package, user, cb) {
check_plugin_result.call(this, 'proxy_access', package, user, cb)
}
PackageProvider.prototype.get_package_setting = function(package, user, cb) {
check_plugin_result.call(this, 'get_package_setting', package, user, cb)
}
// default fallthrough package provider engine to read packages from config file
function ConfigPackageProvider(settings, params) {
var self = Object.create(ConfigPackageProvider.prototype)
self.config = params.config
return self
}
ConfigPackageProvider.prototype.allow_access = function(package, user, cb) {
var self = this
setImmediate(function() {
cb(null, self.config.allow_access(package, user))
})
}
ConfigPackageProvider.prototype.allow_publish = function(package, user, cb) {
var self = this
setImmediate(function() {
cb(null, self.config.allow_publish(package, user))
})
}
ConfigPackageProvider.prototype.proxy_access = function(package, uplink, cb) {
var self = this
setImmediate(function() {
cb(null, self.config.proxy_access(package, uplink))
})
}
ConfigPackageProvider.prototype.get_package_setting = function(package, setting, cb) {
var self = this
setImmediate(function() {
cb(null, self.config.get_package_setting(package, setting))
})
}
// package provider to deny all access, used as default after everything else falls through
function DenyPackageProvider() {
var self = Object.create(DenyPackageProvider.prototype)
return self
}
DenyPackageProvider.prototype.allow_access = function(package, user, cb) {
setImmediate(function() {
cb(null, false)
})
}
DenyPackageProvider.prototype.allow_publish = function(package, user, cb) {
setImmediate(function() {
cb(null, false)
})
}
DenyPackageProvider.prototype.proxy_access = function(package, uplink, cb) {
setImmediate(function() {
cb(null, false)
})
}

41
lib/plugin-loader.js Normal file
View file

@ -0,0 +1,41 @@
function load_plugins(plugin_configs, params, type, required_functions) {
var plugins = Object.keys(plugin_configs || {}).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 + '" plugin not found\n'
+ 'try "npm install sinopia-' + p + '"')
}
if (typeof(plugin) !== 'function')
throw Error('"' + name + '" doesn\'t look like a valid plugin')
plugin = plugin(plugin_configs[p], params)
if (plugin == null)
throw Error('"' + name + '" doesn\'t look like a valid plugin')
if(required_functions) {
for(var i = 0; i < required_functions.length; ++i) {
if(typeof plugin[required_functions[i]] !== 'function')
throw Error('"' + name + '" doesn\'t look like a valid ' + type + ' plugin')
}
}
return plugin
})
return plugins
}
exports.load_plugins = load_plugins;