Skip to content

Commit

Permalink
Create normalised methods for getting rules and rulesets
Browse files Browse the repository at this point in the history
  • Loading branch information
groenroos committed Aug 6, 2021
1 parent 7d1adc4 commit 3037602
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 30 deletions.
11 changes: 5 additions & 6 deletions hooks/sapling/model/retrieve.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,28 @@
/* Dependencies */
import Response from '@sapling/sapling/lib/Response.js';
import SaplingError from '@sapling/sapling/lib/SaplingError.js';
import Utils from '@sapling/sapling/lib/Utils.js';


/* Hook /api/model/:model */
export default async function retrieve(app, request, response) {
if (request.params.model) {
/* Fetch the given model */
const schema = new Utils().deepClone(app.storage.schema[request.params.model] || []);
const rules = app.storage.getRules(request.params.model);

/* If no model, respond with an error */
if (schema.length === 0) {
if (Object.keys(rules).length === 0) {
return new Response(app, request, response, new SaplingError('No such model'));
}

/* Remove any internal/private model values (begin with _) */
for (const k in schema) {
for (const k in rules) {
if (k.startsWith('_')) {
delete schema[k];
delete rules[k];
}
}

/* Send it out */
return new Response(app, request, response, null, schema);
return new Response(app, request, response, null, rules);
}

return new Response(app, request, response, new SaplingError('No model specified'));
Expand Down
5 changes: 4 additions & 1 deletion hooks/sapling/user/login.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ import SaplingError from '@sapling/sapling/lib/SaplingError.js';

/* Hook /api/user/login */
export default async function login(app, request, response) {
/* Fetch the user model */
const rules = app.storage.getRules('users');

/* Find all identifiable fields */
const identifiables = Object.keys(app.storage.schema.users).filter(field => app.storage.schema.users[field].identifiable);
const identifiables = Object.keys(rules).filter(field => rules[field].identifiable);

/* Figure out which request value is used */
let identValue = false;
Expand Down
43 changes: 26 additions & 17 deletions lib/Request.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export default class Request {
getCreatorConstraint(request, role) {
const conditions = {};

if (request.permission && request.permission.role.includes('owner') && role !== 'admin') {
if (request.session && request.session.user && request.permission && request.permission.role.includes('owner') && role !== 'admin') {
conditions._creator = request.session.user._id;
}

Expand All @@ -82,7 +82,7 @@ export default class Request {
/* Request method */
const method = request.method && request.method.toUpperCase();

/* Trim uneeded parts of the request */
/* Trim unneeded parts of the request */
if (parts[0] === '') {
parts.splice(0, 1);
}
Expand Down Expand Up @@ -122,6 +122,27 @@ export default class Request {
}
}

/* Format incoming data */
if (request.body) {
/* Go through every key in incoming data */
for (const key in request.body) {
if (Object.prototype.hasOwnProperty.call(request.body, key)) {
/* Get the corresponding ruleset */
const rule = this.app.storage.getRule(key, collection);

/* Trim incoming data unless otherwise specified in model */
if (typeof request.body[key] === 'string' && (!rule || !('trim' in rule) || rule.trim !== false)) {
request.body[key] = String(request.body[key]).trim();
}

/* If the data is a number, convert from string */
if (rule.type === 'number') {
request.body[key] = Number.parseFloat(request.body[key], 10);
}
}
}
}

/* Modify the request object */
return _.extend(request, {
collection,
Expand All @@ -144,7 +165,7 @@ export default class Request {
const { collection, body, session, type } = request;

/* Get the collection definition */
const rules = this.app.storage.schema[collection] || {};
const rules = this.app.storage.getRules(collection);

let errors = [];
const data = body || {};
Expand All @@ -153,7 +174,7 @@ export default class Request {
const role = this.app.user.getRole({ session });

/* Model must be defined before pushing data */
if (!rules && this.app.config.strict) {
if (Object.keys(rules).length === 0 && this.app.config.strict) {
new Response(this.app, request, response, new SaplingError({
status: '500',
code: '1010',
Expand All @@ -176,12 +197,7 @@ export default class Request {
}

/* Get the corresponding ruleset */
const rule = rules[key];

/* Trim incoming data unless otherwise specified in model */
if (typeof data[key] === 'string' && (!rule || !('trim' in rule) || rule.trim !== false)) {
data[key] = String(data[key]).trim();
}
const rule = this.app.storage.getRule(key, collection);

/* If the field isn't defined */
if (!rule) {
Expand All @@ -195,13 +211,6 @@ export default class Request {
continue;
}

const dataType = (rule.type || rule).toLowerCase();

/* If the data is a number, convert from string */
if (dataType === 'number') {
data[key] = Number.parseFloat(data[key], 10);
}

/* Test in the validation library */
const error = new Validation().validate(data[key], key, rule);
if (error.length > 0) {
Expand Down
60 changes: 56 additions & 4 deletions lib/Storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@

/* Dependencies */
import _ from 'underscore';
import isobject from 'isobject';
import moment from 'moment';

import { console } from './Cluster.js';
import SaplingError from './SaplingError.js';
import Response from './Response.js';
import Utils from './Utils.js';


/* Default user structure */
Expand Down Expand Up @@ -157,6 +159,55 @@ export default class Storage {
}


/**
* Return an object for all the rules of a given model.
* Returns an empty object if model is not defined.
*
* @param {string} collection The name of the model
* @returns {object} The mode ruleset
*/
getRules(collection) {
const rules = new Utils().deepClone(this.schema[collection] || {});

for (const key in rules) {
if (Object.prototype.hasOwnProperty.call(rules, key)) {
if (isobject(rules[key]) && 'type' in rules[key]) {
rules[key].type = String(rules[key].type).toLowerCase();
} else if (typeof rules[key] === 'string') {
rules[key] = { type: rules[key].toLowerCase() };
} else {
rules[key] = { type: 'string' };
}
}
}

return rules;
}


/**
* Return an object for the rule of a given field in
* a given model. In strict mode, returns null when
* no model is defined. Otherwise, an object will
* always be returned.
*
* @param {string} field The name of the field
* @param {string} collection The name of the model
* @returns {object} The model rule
*/
getRule(field, collection) {
const rules = this.getRules(collection);

/* If it doesn't exist, return null in strict mode */
if (!('field' in rules) && this.app.config.strict) {
return null;
}

/* Otherwise, send it */
return rules[field] || { type: 'string' };
}


/**
* Serve an incoming GET request from the database
*
Expand All @@ -170,7 +221,7 @@ export default class Storage {
request = this.app.request.parse(request);

/* Get the collection definition */
const rules = this.schema[request.collection] || {};
const rules = this.getRules(request.collection);
const options = {};
let conditions = {};

Expand Down Expand Up @@ -235,12 +286,13 @@ export default class Storage {
/* Get it from the database */
try {
const array = await this.db.read(request.collection, conditions, options, references);
const rules = this.getRules(request.collection);

/* Get the list of fields we should not be able to see */
const omit = this.app.user.disallowedFields(role, this.schema[request.collection]);
const omit = this.app.user.disallowedFields(role, rules);

/* Get the list of fields that only owners can see */
const ownerFields = this.app.user.ownerFields(this.schema[request.collection]);
const ownerFields = this.app.user.ownerFields(rules);

/* Process fields against both lists */
for (let i = 0; i < array.length; ++i) {
Expand Down Expand Up @@ -286,7 +338,7 @@ export default class Storage {
this.app.user.isUserAuthenticatedForRoute(request, response);

/* Get the collection definition */
const rules = this.schema[request.collection] || {};
const rules = this.getRules(request.collection);

let conditions = {};
const data = request.body;
Expand Down
6 changes: 4 additions & 2 deletions lib/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,12 @@ export default class User {
roles = [roles];
}

const rules = this.app.storage.getRules('users');

return roles.some(role => {
/* Get the indices of both comparison targets */
const roleIndex = this.app.storage.schema.users.role.values.indexOf(role);
const testIndex = this.app.storage.schema.users.role.values.indexOf(test);
const roleIndex = rules.role.values.indexOf(role);
const testIndex = rules.role.values.indexOf(test);

/* "admin" or "anyone" must always return true */
if (test === 'admin' || role === 'anyone') {
Expand Down

0 comments on commit 3037602

Please sign in to comment.