Comparing version 0.3.8 to 0.3.9
@@ -8,5 +8,4 @@ /** | ||
nextApp = customHeader; | ||
// default to this common header | ||
customHeader = "x-requested-with"; | ||
} | ||
customHeader = customHeader || "x-requested-with"; | ||
return function(request){ | ||
@@ -13,0 +12,0 @@ var headers = request.headers; |
@@ -12,3 +12,3 @@ /** | ||
exports = module.exports = function(nextApp){ | ||
exports.ErrorHandler = function(nextApp){ | ||
return function(request){ | ||
@@ -77,3 +77,1 @@ try{ | ||
}; | ||
exports.ErrorHandler = exports; |
@@ -8,3 +8,3 @@ /** | ||
var httpParamRegex = /^http[_-]/; | ||
exports = module.exports = function(nextApp){ | ||
exports.HttpParams = function(nextApp){ | ||
return function(request){ | ||
@@ -37,3 +37,1 @@ var parts = request.queryString.split("&"); | ||
}; | ||
// back-compat property reference | ||
exports.HttpParams = exports; |
@@ -5,3 +5,3 @@ /** | ||
var when = require("promised-io/promise").when; | ||
module.exports = function(nextApp){ | ||
exports.Metadata = function(nextApp){ | ||
return function(request){ | ||
@@ -57,5 +57,3 @@ var metadata; | ||
}; | ||
}; | ||
// back-compat property access | ||
module.exports.Metadata = module.exports; | ||
} | ||
@@ -62,0 +60,0 @@ /* |
@@ -13,4 +13,6 @@ /** | ||
module.exports = function(options){ | ||
function dir(){var sys=require('sys');for(var i=0,l=arguments.length;i<l;i++)sys.debug(sys.inspect(arguments[i]));} | ||
exports.RestStore = function(options){ | ||
return function(request){ | ||
// N.B. in async, options.getDataModel() can be a promise, so have to wait for it | ||
return when(options.getDataModel(request), function(model){ | ||
@@ -165,3 +167,1 @@ var path = request.pathInfo.substring(1); | ||
}; | ||
// back-compat property access | ||
module.exports.RestStore = module.exports; |
@@ -17,3 +17,3 @@ /* | ||
//function dir(){var sys=require('sys');for(var i=0,l=arguments.length;i<l;i++)sys.debug(sys.inspect(arguments[i]));} | ||
exports = module.exports = function(customRoutes, nextApp){ | ||
exports.Routes = function(customRoutes, nextApp){ | ||
// append custom routes | ||
@@ -61,4 +61,3 @@ for (var i = 0, l = customRoutes.length; i < l; ++i) { | ||
}; | ||
// back-compat property access | ||
exports.Routes = exports; | ||
function declare(method, regexp, handler, args){ | ||
@@ -65,0 +64,0 @@ if (typeof regexp === 'string') { |
@@ -14,3 +14,3 @@ /** | ||
exports = module.exports = function(options, nextApp){ | ||
exports.Session = function(options, nextApp){ | ||
// assign defaults | ||
@@ -77,4 +77,3 @@ if (!options) options = {}; | ||
}; | ||
// back-compat property reference | ||
exports.Session = exports; | ||
// gets a session, creating a new one if necessary | ||
@@ -81,0 +80,0 @@ exports.forceSession = function(request, expires){ |
{ | ||
"name": "pintura", | ||
"version": "0.3.8", | ||
"version": "0.3.9", | ||
"author": "Kris Zyp", | ||
@@ -64,3 +64,3 @@ "email": "kriszyp@gmail.com", | ||
"formidable": ">=1.0.0", | ||
"jsgi-node": ">=0.3.2", | ||
"jsgi-node": ">=0.3.3", | ||
"perstore": ">=0.3.0", | ||
@@ -67,0 +67,0 @@ "promised-io": ">=0.3.0", |
142
pintura.js
@@ -7,85 +7,85 @@ /** | ||
// load the default media types | ||
require('./media/json'); | ||
require('./media/javascript'); | ||
require('./media/url-encoded'); | ||
require('./media/atom'); | ||
require('./media/multipart-form-data'); | ||
require('./media/html'); | ||
require('./media/uri-list'); | ||
require('./media/plain'); | ||
require('./media/message/json'); | ||
require("./media/json"); | ||
require("./media/javascript"); | ||
require("./media/url-encoded"); | ||
require("./media/atom"); | ||
require("./media/multipart-form-data"); | ||
require("./media/html"); | ||
require("./media/uri-list"); | ||
require("./media/plain"); | ||
require("./media/message/json"); | ||
var configure = require('./jsgi/configure'); | ||
var deepCopy = require('perstore/util/copy').deepCopy; | ||
var config = exports.config = { | ||
mediaSelector: require('./media').Media.optimumMedia, | ||
database: require('perstore/stores'), | ||
security: require('./security').DefaultSecurity(), | ||
responseCache: require('perstore/store/memory').Memory({path: 'response'}), //require('perstore/store/filesystem').FileSystem('response', {defaultExtension: 'cache',dataFolder: 'cache' }), | ||
serverName: 'Pintura', | ||
mediaSelector: require("./media").Media.optimumMedia, | ||
database: require("perstore/stores"), | ||
security: require("./security").DefaultSecurity(), | ||
responseCache: require("perstore/store/memory").Memory({path: "response"}), //require("perstore/store/filesystem").FileSystem("response", {defaultExtension: "cache",dataFolder: "cache" }), | ||
serverName: "Pintura", | ||
customRoutes: [], | ||
groups: { | ||
public: [null], | ||
user: '*', | ||
admin: ['admin'], | ||
}, | ||
getDataModel: function(request){ | ||
getDataModel: function(request){ | ||
return exports.getDataModel(request); | ||
} | ||
}; | ||
exports.configure = function(newConfig){ | ||
// copy new configuration options into the config object | ||
deepCopy(newConfig, config); | ||
} | ||
exports.getDataModel = function(request){ | ||
// this is a simple default model | ||
return request.dataModel; | ||
exports.getDataModel = function(){ | ||
throw new Error("You must assign a getDataModel method to the pintura config object in order to expose data"); | ||
}; | ||
exports.registerModels = config.security.registerModels; | ||
exports.app = JsgiApp(null, config); | ||
exports.app = configure([ | ||
function JsgiApp(nextApp, config){ | ||
// This is the set of JSGI middleware and appliance that comprises the Pintura | ||
// request handling framework. | ||
{module: './context', config: {}}, | ||
// We detect if the request could have been forged from another site | ||
'./csrf', | ||
// Support handling various cross-site request mechanisms like JSONP, window.name, CS-XHR | ||
'./xsite', | ||
// Handle header emulation through query parameters (useful for cross-site and links) | ||
'./http-params', | ||
// Handle HEAD requests | ||
'./head', | ||
// Add some useful headers | ||
{module: './pintura-headers', config: config.serverName}, | ||
// Handle conditional requests | ||
{module: './conditional', config: true}, | ||
// Handle response conneg, converting from JS objects to byte representations | ||
{factory: require('./jsgi/media').Serialize, config: config.mediaSelector}, | ||
// Handle errors that are thrown, converting to appropriate status codes | ||
'./error', | ||
// Handle transactions | ||
'perstore/jsgi/transactional', | ||
// Handle sessions | ||
{module: './session', config: {}}, | ||
// Do authentication | ||
{module: './auth', config: config.security}, | ||
// Determine access to data models | ||
{module: './access', config: config.security}, | ||
// Handle request conneg, converting from byte representations to JS objects | ||
{factory: require('./jsgi/media').Deserialize, config: config.mediaSelector}, | ||
// Non-REST custom handlers | ||
{module: './routes', config: config.customRoutes}, | ||
// Add and retrieve metadata from objects | ||
'./metadata', | ||
// Final REST handler | ||
{module: './rest-store', config: config} | ||
]); | ||
var Connector = require('tunguska/connector').Connector; | ||
return require("./jsgi/context").SetContext({}, | ||
// We detect if the request could have been forged from another site | ||
require("./jsgi/csrf").CSRFDetect( | ||
// Support handling various cross-site request mechanisms like JSONP, window.name, CS-XHR | ||
require("./jsgi/xsite").CrossSite( | ||
// Handle header emulation through query parameters (useful for cross-site and links) | ||
require("./jsgi/http-params").HttpParams( | ||
// Handle HEAD requests | ||
require("./jsgi/head").Head( | ||
// Add some useful headers | ||
require("./jsgi/pintura-headers").PinturaHeaders(config.serverName, | ||
// Handle conditional requests | ||
require("./jsgi/conditional").Conditional(true, | ||
// Handle response conneg, converting from JS objects to byte representations | ||
require("./jsgi/media").Serialize(config.mediaSelector, | ||
// Handle errors that are thrown, converting to appropriate status codes | ||
require("./jsgi/error").ErrorHandler( | ||
// Handle transactions | ||
require("perstore/jsgi/transactional").Transactional( | ||
// Handle sessions | ||
require("./jsgi/session").Session({}, | ||
// Do authentication | ||
require("./jsgi/auth").Authentication(config.security, | ||
// Handle request conneg, converting from byte representations to JS objects | ||
require("./jsgi/media").Deserialize(config.mediaSelector, | ||
// Non-REST custom handlers | ||
require('./jsgi/routes').Routes(config.customRoutes, | ||
// Add and retrieve metadata from objects | ||
exports.directApp = require("./jsgi/metadata").Metadata( | ||
// Final REST handler | ||
require("./jsgi/rest-store").RestStore(config) | ||
) | ||
) | ||
) | ||
) | ||
) | ||
) | ||
) | ||
) | ||
) | ||
) | ||
) | ||
) | ||
) | ||
) | ||
); | ||
}; | ||
exports.JsgiApp = JsgiApp; | ||
var Connector = require("tunguska/connector").Connector; | ||
exports.addConnection = exports.app.addConnection = function(connection){ | ||
Connector('local-workers', connection); | ||
connection[connection.on ? 'on' : 'observe']('message', function(message){ | ||
Connector("local-workers", connection); | ||
connection[connection.on ? "on" : "observe"]("message", function(message){ | ||
message.pathInfo = message.channel || message.to; | ||
if(message.method && message.method !== 'subscribe'){ | ||
if(message.method && message.method !== "subscribe"){ | ||
exports.directApp(message); | ||
@@ -92,0 +92,0 @@ } |
184
README.md
@@ -74,10 +74,11 @@ [Pintura](http://www.google.com/maps?f=q&source=s_q&hl=en&geocode=&q=pintura&sll=40.554798,-111.881839&sspn=0.009211,0.016351&ie=UTF8&hq=&hnear=Pintura,+Washington,+Utah&ll=37.31666,-113.171539&spn=0.308538,0.523224&t=p&z=11) | ||
We can then expose this data model through Pintura's HTTP REST by registering our | ||
models through the `pintura/jsgi/access` module. The module description contains | ||
more information on registering modules for different groups and users, | ||
but we can easily just register our model for use for everyone: | ||
We can then expose this data model through Pintura's HTTP REST interface by implementing | ||
the getDataModel function on the pintura module. This function is called for each HTTP | ||
request: | ||
require("pintura/jsgi/access").registerModels({ | ||
Product: Product | ||
}); | ||
require("pintura/pintura").getDataModel = function(request){ | ||
return { | ||
Product: Product | ||
}; | ||
}; | ||
@@ -135,4 +136,3 @@ Our data model will then be available at the path of /Product/ such that we can make | ||
and which facets or access levels each user is given. The security configuration object | ||
is available at require("pintura/pintura").config.security, and can be configured | ||
by calling the `configure` method with object properties to be assigned. The primary functions | ||
is available at require("pintura/pintura").config.security. The primary functions | ||
that can be overriden or used are: | ||
@@ -155,88 +155,43 @@ | ||
require("pintura/pintura").configure({ | ||
security: { | ||
encryptPassword: function(username, password){ | ||
return password; | ||
} | ||
} | ||
}); | ||
require("pintura/pintura").config.security.encryptPassword = function(username, password){ | ||
return password; | ||
}; | ||
## Access After Authentication | ||
Once authentication is established, access to the data is determined by group membership. | ||
We can define group membership on the security object as well. The security object | ||
has several methods that are used to compute the groups for a user, and which models | ||
to expose based on these groups. | ||
Once authentication is established, we could then use the user's authentication state to restrict or allow access to different | ||
parts of the application data model. For example, we could check to see if a user is | ||
logged to determine if we should provide access to the "Secret" data: | ||
The simplest way to define access to models is to define the groups for users on the | ||
security object's `groupsUsers`. We can do this be assigning groups to `groupsUsers` | ||
object by properties with the group name and a value of an array of the included users: | ||
var pintura = require("pintura/pintura"); | ||
pintura.configure({ | ||
security: { | ||
groupsUsers: { | ||
// define john to be the only admin | ||
admin: ['john'], | ||
// define all users to be in the common group | ||
common: ['*'], | ||
// define unauthenticated users to be in the public group | ||
public: [null] | ||
} | ||
var publicModel = { | ||
Product: Product | ||
}; | ||
var authorizedModel = { | ||
Product: Product, | ||
Secret: SecretModel | ||
}; | ||
require("pintura/pintura").getDataModel = function(request){ | ||
var user = request.remoteUser; | ||
if(user){ | ||
return authorizedModel; | ||
} | ||
}); | ||
return publicModel; | ||
}; | ||
We could then register our data models with group information associated with it. We | ||
use the registerModels function to accomplish this. This can defined with an object where | ||
properties define the model by name, where each property value can be a model, a model | ||
with the groups allowed, or an array of models with groups. This is best illustrated | ||
by an example: | ||
pintura.registerModels({ | ||
// The User model is exposed (though /User/), regardless of which user/group | ||
User: User, | ||
// Expose the Product model, dependent on the group | ||
Product: [ | ||
{ | ||
// The main (unrestricted) Product model, for anyone in the admin group | ||
model: Product, | ||
groups: ['admin'] | ||
}, | ||
{ | ||
// The public (restricted) Product facet, for those in the user or public group | ||
model: PublicProduct, | ||
groups: ['user', 'public'] | ||
} | ||
], | ||
}, { | ||
// define a set of models to be exposed for the admin groups | ||
groups: ['admin'], | ||
models: { | ||
// we could define multiple models (that are available to the admin), | ||
// but here we are just defining the File model to be exposed. | ||
File: File | ||
} | ||
}); | ||
We could also potentially have a data model that is readonly for some users and | ||
editable for others. In the example above, we had specified Product table | ||
to be readonly for users that do not have admin access, we can create this read-only | ||
facet using the Restrictive facet constructor: | ||
editable for others. In the example above, we could specify that the Product table | ||
is readonly for users that are not logged in: | ||
var Restrictive = require("perstore/facet").Restrictive; | ||
var PublicProduct = Restrictive(Product); | ||
The security object also includes the following methods and properties that can be overriden or used to | ||
provide customized determination of group membership or access to data models: | ||
* groupsUsers - This is the object that defines the users in each group, as described above. | ||
* getGroupsForUser(user) - This should return an array of groups, for which the user | ||
has membership. | ||
* getModelForUser(user) - This should return the specific set of data models for the given user. | ||
This can be overriden to define a custom method for determining the data model. | ||
In addition, you can also alternately define a `getDataModel(request)` method on the pintura | ||
module object, to determine the data model. | ||
var publicModel = { | ||
// the Product table is restricted to readonly for public access | ||
Product: Restrictive(Product) | ||
}; | ||
var authorizedModel = { | ||
// the Product table is unrestricted here for authorized users | ||
Product: Product, | ||
Secret: SecretModel | ||
}; | ||
// assign the data model based on authentication as above | ||
Error Handling | ||
@@ -518,8 +473,4 @@ =========== | ||
described below. However, understanding the middleware modules can be important | ||
in understanding the full capabilities of Pintura, and for reconfiguring the middleware. | ||
in understanding the full capabilities of Pintura. | ||
Once you have defined a JSGI app, you can start your server with that app like: | ||
require("pintura/start-node").start(app); | ||
The middleware modules in Pintura | ||
@@ -530,49 +481,5 @@ are found in the "jsgi" folder. Most of these modules directly a function that can be | ||
## configure | ||
The Pintura middleware stack is defined by using the `configure` module. This is a function | ||
that takes an array of middleware definitions and creates the stack for you. Once the middleware | ||
stack has been created, you can also modify the stack, using standard array methods. For example, | ||
to create a stack of middleware, we could write: | ||
require('pintura/jsgi/configure'); | ||
configuredApp = configure([ | ||
{ // we can define middleware by referencing the module | ||
module: 'pintura/jsgi/auth', | ||
// and providing a config | ||
config: security | ||
}, | ||
'pintura/jsgi/csrf' // or just use a module id directly | ||
]); | ||
Each middleware entry in the stack can be defined as one of these: | ||
* An object with a module id in the `module` property, or a factory function in the `factory` property. You can optionally include a `config` property if the middleware takes a config in its arguments. | ||
* A plain module id. | ||
* A factory function. | ||
The return app will then have standard array methods available for modifying the stack. Any changes to the stack will cause it automatically rebuild itself. In addition, there are several extra methods: | ||
* get(id) - Get the configuration for a middleware by the module's last segment (in the case of 'pintura/jsgi/csrf', the id would 'csrf'), or the function name. | ||
* set(id, config) - Update the configuration of one of the middleware. | ||
* delete(id) - Remove a middleware component from the stack. | ||
The `indexOf` and `lastIndexOf` methods also support a string id as the argument, in which case it will search for a config with that id. | ||
## access | ||
{ | ||
module: 'pintura/jsgi/access', | ||
config: security | ||
} | ||
The access module provides access to the data models, based on the group membership of the currently | ||
authenticated user. The group membership is typically defined on the security object, which is | ||
then used by access module to calculate the data models available for that user. | ||
## auth | ||
{ | ||
module: 'pintura/jsgi/auth', | ||
config: security | ||
} | ||
app = require('pintura/jsgi/auth')(security, nextApp); | ||
@@ -588,6 +495,3 @@ The auth module handles HTTP authorization, performing the HTTP request side of user | ||
{ | ||
module: 'pintura/jsgi/rest-store', | ||
config: config | ||
} | ||
app = require('pintura/jsgi/rest-store')(config); | ||
@@ -594,0 +498,0 @@ This module delegates the HTTP REST requests to the appropriate data model. This |
@@ -11,4 +11,3 @@ /** | ||
sha1 = require("./util/sha1").b64_sha1, | ||
settings = require("perstore/util/settings"), | ||
modelModule = require("perstore/model"); | ||
settings = require("perstore/util/settings"); | ||
@@ -54,5 +53,3 @@ try{ | ||
var admins = settings.security && settings.security.admins; | ||
var groupToModels = {}; | ||
var security = { | ||
// authentication methods: | ||
encryptPassword: function(username, password){ | ||
@@ -121,81 +118,2 @@ return password && sha1(password); | ||
userModel = value; | ||
}, | ||
// access methods: | ||
groupsUsers: { | ||
user: ['*'], | ||
public: [null] | ||
}, | ||
getGroupsForUser: function(user){ | ||
var groupsUsers = this.groupsUsers; | ||
var groupsForUser = []; | ||
// at some point we may want to convert this to an array for faster access | ||
for(var groupName in groupsUsers){ | ||
var users = groupsUsers[groupName]; | ||
if(users.indexOf(user) > - 1 || users.indexOf('*') > - 1){ | ||
// the user is in this group | ||
groupsForUser.push(groupName); | ||
} | ||
} | ||
return groupsForUser; | ||
}, | ||
modelForUsers: {}, | ||
getModelForUser: function(user){ | ||
if(this.modelForUsers[user]){ | ||
// check the cache | ||
return this.modelForUsers[user]; | ||
} | ||
var groups = this.getGroupsForUser(user).concat(['_default']); | ||
var model = {}; | ||
groups.forEach(function(group){ | ||
var groupModel = groupToModels[group]; | ||
for(var key in groupModel){ | ||
if(!model[key] || !(model[key].quality > groupModel[key])){ | ||
model[key] = groupModel[key]; | ||
} | ||
} | ||
}); | ||
this.modelForUsers[user] = model; | ||
modelModule.initializeRoot(model); | ||
return model; | ||
}, | ||
registerModels: function(){ | ||
var groups; | ||
processItem([].slice.call(arguments)); | ||
function processItem(item, key){ | ||
if(item instanceof Array){ | ||
// process each item in an array | ||
item.forEach(function(item){ | ||
processItem(item, key); | ||
}); | ||
}else if(item && item.groups && typeof item === 'object'){ | ||
// define the groups and recurse | ||
groups = item.groups; | ||
if(item.model){ | ||
if(typeof item.model !== 'function'){ | ||
throw new Error('Model in ' + key + ' does not appear to be a valid model constructor' + item.model); | ||
} | ||
}else if(!item.models){ | ||
throw new Error('No valid model provided for ' + key + ' with groups ' + groups); | ||
} | ||
processItem(item.models || item.model, key); | ||
groups = null; | ||
}else if(typeof item === 'function'){ | ||
// a model itself, add the definition | ||
if(!key){ | ||
throw new Error('No key defined for model'); | ||
} | ||
(groups || item.groups || ['_default']).forEach(function(group){ | ||
(groupToModels[group] || (groupToModels[group] = {}))[key] = item; | ||
}); | ||
}else if(item && typeof item === 'object'){ | ||
// an object hash, process each as a key | ||
for(key in item){ | ||
processItem(item[key], key); | ||
} | ||
}else{ | ||
throw new Error('An invalid value encountered in model registration ' + item + ' for groups ' + groups + ' for name ' + key); | ||
} | ||
} | ||
} | ||
@@ -202,0 +120,0 @@ }; |
@@ -1,2 +0,2 @@ | ||
exports.testConfigure = require("./jsgi/configure"); | ||
exports.testFullRest = require("./rest"); | ||
exports.testJSGIMiddleware = require("./jsgi/index"); | ||
@@ -3,0 +3,0 @@ |
exports.testCSRF = require("./csrf"); | ||
exports.testConfigure = require("./csrf"); | ||
@@ -4,0 +3,0 @@ if (require.main === module) |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
51
236932
92
5559
746
Updatedjsgi-node@>=0.3.3