0
Fork 0
mirror of https://github.com/verdaccio/verdaccio.git synced 2025-04-01 02:42:23 -05:00

Merge branch 'feature/package-provider' of https://github.com/zipscene/sinopia into package-provider

This commit is contained in:
Alex Kocharin 2015-04-08 23:26:43 +03:00
commit 37f6591563
9 changed files with 257 additions and 80 deletions

View file

@ -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)
#

View file

@ -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
@ -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) {
@ -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) {

View file

@ -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') {
@ -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' &&
@ -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')

View file

@ -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
@ -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)
})
})
})

View file

@ -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
@ -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

View file

@ -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
@ -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) {

View file

@ -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') )
}
}
})
}
}
}

117
lib/packages.js Normal file
View file

@ -0,0 +1,117 @@
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
}
// load all plugins referenced by any package
for (var i in config.packages) {
if(config.packages[i].plugin) {
config.packages[i].loaded_plugins = load_plugins(config.packages[i].plugin, plugin_params, 'package_provider', ['allow_access'])
}
}
self.default_plugin = new ConfigPackageProvider({}, plugin_params)
return self
}
function check_plugin_result(function_name, package, arg, cb) {
var self = this
var plugins = self.config.get_package_setting(package, 'loaded_plugins')
if (!plugins || !plugins.length) {
self.default_plugin[function_name](package, arg, cb)
return
}
var current_result
async.eachSeries(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 {
if(current_result === undefined) {
self.default_plugin[function_name](package, arg, cb)
} 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))
})
}

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;