Initial commit; 0.0.1

master
Sven Slootweg 9 years ago
parent 2e730eaf8e
commit 4d96bcb861

3
.gitignore vendored

@ -1,3 +1,4 @@
# https://git-scm.com/docs/gitignore
# https://help.github.com/articles/ignoring-files
# Example .gitignore files: https://github.com/github/gitignore
# Example .gitignore files: https://github.com/github/gitignore
/node_modules/

@ -0,0 +1,94 @@
# bitmask-flags
A utility for working with bitmasks to do flags and permissions. Supports flag inheritance.
While the examples in this documentation will show usage for a permission system, you could of course use it for any kind of flags.
## License
[WTFPL](http://www.wtfpl.net/txt/copying/) or [CC0](https://creativecommons.org/publicdomain/zero/1.0/), whichever you prefer.
## Contributing
Pull requests welcome. Please make sure your modifications are in line with the overall code style, and ensure that you're editing the `.coffee` files, not the `.js` files.
As this module could potentially deal with authentication, tests are needed; a pull request for those would be especially welcome.
Build tool of choice is `gulp`; simply run `gulp` while developing, and it will watch for changes.
## Usage
### Create a new instance
```javascript
var bitmaskFlags = require("bitmask-flags");
var flagHandler = bitmaskFlags({
view_announcements: 1,
create_announcements: {
value: 2,
inheritedFlags: ["edit_announcements"]
},
edit_announcements: 4,
delete_announcements: 8
});
```
Why you need to explicitly specify the values for each flag? Because if they were assigned automatically in order, it'd be too easy to accidentally mess up the values of your flags during later editing or rearranging. Especially in a permission system, that could have disastrous consequences.
`bitmask-flags` will do a sanity check on instantiating to ensure that all of your flags are a power of 2. If that's not the case, it will throw an Error and bail out.
### Regular flags
The `user` object is a hypothetical ORM model/object of some sort. It hypothetically supports plain attributes and has a save() method, for the sake of illustrating how this module works.
```javascript
var permissions = flagHandler.create(); // This is how you create a new value, starting at 0 (no flags).
// or
var permissions = flagHandler.create(user.permissions); // This is how you use an existing starting value (eg. from the database).
// or
var permissions = flagHandler.create(user.permissions, user.originalPermissions); // If you want to use flag inheritance (explained later).
permissions.add("view_announcements"); // This permission is now set.
permissions.add("delete_announcements"); // The value now has `view_announcements` and `delete_announcements` flags.
permissions.remove("view_announcements"); // Now only `delete_announcements` is left.
console.log(permissions.has("delete_announcements")); // true
user.permissions = permissions.getValue();
user.save(); // Done!
```
### Flag inheritance
The really interesting thing, though, is flag inheritance. Sometimes one flag should automatically grant another flag, but the other flag can also be granted separately. `bitmask-flags` makes this easy.
Note that you will have to store *two* values in your database for this to work, rather than one - the second value indicates which flags were *explicitly* set. This way, when you've explicitly set a child flag, unsetting the parent flag won't change the state of the child flag. That also means you can explicitly set a flag that was *already* set through inheritance, and have it persist.
For these examples, we will assume that the section above never happened, and the user starts out with no permissions.
```javascript
var permissions = flagHandler.create(user.permissions, user.originalPermissions); // Like we saw before...
permissions.add("create_announcements"); // The user now has `create_announcements` AND `edit_anouncements`.
console.log(permissions.has("edit_announcements")); // true
permissions.remove("create_announcements"); // The user now has no permissions.
console.log(permissions.has("edit_announcements")); // false
/* Now let's try that again, but this time explicitly setting `edit_announcements`. */
permissions.add("create_announcements"); // The user now has `create_announcements` and `edit_anouncements`.
permissions.add("edit_announcements"); // The user *still* has `create_announcements` and `edit_anouncements`.
permissions.remove("create_announcements");
console.log(permissions.has("edit_announcements")); // true - Because we set `edit_announcements` explicitly, it wasn't unset along with `create_announcements`. Magic!
user.permissions = permissions.getValue();
user.originalPermissions = permissions.getOriginalValue();
user.save(); // Done! The inheritance structure will persist, even through database loads and saves.
```

@ -0,0 +1,28 @@
var gulp = require('gulp');
/* CoffeeScript compile deps */
var path = require('path');
var gutil = require('gulp-util');
var concat = require('gulp-concat');
var rename = require('gulp-rename');
var coffee = require('gulp-coffee');
var cache = require('gulp-cached');
var remember = require('gulp-remember');
var plumber = require('gulp-plumber');
var source = ["lib/**/*.coffee", "index.coffee"]
gulp.task('coffee', function() {
return gulp.src(source, {base: "."})
.pipe(plumber())
.pipe(cache("coffee"))
.pipe(coffee({bare: true}).on('error', gutil.log)).on('data', gutil.log)
.pipe(remember("coffee"))
.pipe(gulp.dest("."));
});
gulp.task('watch', function () {
gulp.watch(source, ['coffee']);
});
gulp.task('default', ['coffee', 'watch']);

@ -0,0 +1 @@
module.exports = require "./lib/bitmask-flags"

@ -0,0 +1 @@
module.exports = require("./lib/bitmask-flags");

@ -0,0 +1,74 @@
isPowerOfTwo = (number) -> number != 0 and ((number & (number - 1)) == 0)
class Bitmask
constructor: (@handler, initialValue, initialOriginalValue) ->
@value = initialValue
@originalValue = initialOriginalValue
_getFlagValue: (flag) ->
if @handler.flagMap[flag]?
return @handler.flagMap[flag].value
else
throw new Error("No such flag exists.")
_getFlagInheritances: (flag) ->
if @handler.flagMap[flag]?
return @handler.flagMap[flag].inheritedFlags
else
throw new Error("No such flag exists.")
add: (flag) ->
flagValue = @_getFlagValue(flag)
flagInheritances = @_getFlagInheritances(flag)
if @originalValue?
@originalValue = @originalValue | flagValue
@value = @value | flagValue
for inheritance in flagInheritances
flagValue = @_getFlagValue(inheritance)
@value = @value | flagValue
remove: (flag) ->
flagValue = @_getFlagValue(flag)
flagInheritances = @_getFlagInheritances(flag)
if @originalValue?
@originalValue = @originalValue & ~flagValue
@value = @value & ~flagValue
for inheritance in flagInheritances
flagValue = @_getFlagValue(inheritance)
if @originalValue? and !(@originalValue & flagValue)
@value = @value & ~flagValue
has: (flag) ->
flagValue = @_getFlagValue(flag)
return !!(@value & flagValue)
getValue: ->
return @value
getOriginalValue: ->
return @originalValue
class BitmaskHandler
constructor: (flagMap) ->
@setFlagMap flagMap
setFlagMap: (flagMap) ->
# Sanity check
for flag, value of flagMap
if typeof value == "number"
# Encapsulate in an object...
value = {value: value}
flagMap[flag] = value
value.inheritedFlags ?= []
for inheritance in value.inheritedFlags
if inheritance not of flagMap
throw new Error("The #{flag} flag attempts to inherit the non-existent #{inheritance} flag.")
if not isPowerOfTwo value.value
throw new Error("The value for the #{flag} flag (#{value.value}) is not a power of two.")
@flagMap = flagMap
create: (initialValue = 0, initialOriginalValue) ->
instance = new Bitmask(this, initialValue, initialOriginalValue)
module.exports = (flagMap) -> new BitmaskHandler(flagMap)

@ -0,0 +1,132 @@
var Bitmask, BitmaskHandler, isPowerOfTwo;
isPowerOfTwo = function(number) {
return number !== 0 && ((number & (number - 1)) === 0);
};
Bitmask = (function() {
function Bitmask(handler, initialValue, initialOriginalValue) {
this.handler = handler;
this.value = initialValue;
this.originalValue = initialOriginalValue;
}
Bitmask.prototype._getFlagValue = function(flag) {
if (this.handler.flagMap[flag] != null) {
return this.handler.flagMap[flag].value;
} else {
throw new Error("No such flag exists.");
}
};
Bitmask.prototype._getFlagInheritances = function(flag) {
if (this.handler.flagMap[flag] != null) {
return this.handler.flagMap[flag].inheritedFlags;
} else {
throw new Error("No such flag exists.");
}
};
Bitmask.prototype.add = function(flag) {
var flagInheritances, flagValue, inheritance, _i, _len, _results;
flagValue = this._getFlagValue(flag);
flagInheritances = this._getFlagInheritances(flag);
if (this.originalValue != null) {
this.originalValue = this.originalValue | flagValue;
}
this.value = this.value | flagValue;
_results = [];
for (_i = 0, _len = flagInheritances.length; _i < _len; _i++) {
inheritance = flagInheritances[_i];
flagValue = this._getFlagValue(inheritance);
_results.push(this.value = this.value | flagValue);
}
return _results;
};
Bitmask.prototype.remove = function(flag) {
var flagInheritances, flagValue, inheritance, _i, _len, _results;
flagValue = this._getFlagValue(flag);
flagInheritances = this._getFlagInheritances(flag);
if (this.originalValue != null) {
this.originalValue = this.originalValue & ~flagValue;
}
this.value = this.value & ~flagValue;
_results = [];
for (_i = 0, _len = flagInheritances.length; _i < _len; _i++) {
inheritance = flagInheritances[_i];
flagValue = this._getFlagValue(inheritance);
if ((this.originalValue != null) && !(this.originalValue & flagValue)) {
_results.push(this.value = this.value & ~flagValue);
} else {
_results.push(void 0);
}
}
return _results;
};
Bitmask.prototype.has = function(flag) {
var flagValue;
flagValue = this._getFlagValue(flag);
return !!(this.value & flagValue);
};
Bitmask.prototype.getValue = function() {
return this.value;
};
Bitmask.prototype.getOriginalValue = function() {
return this.originalValue;
};
return Bitmask;
})();
BitmaskHandler = (function() {
function BitmaskHandler(flagMap) {
this.setFlagMap(flagMap);
}
BitmaskHandler.prototype.setFlagMap = function(flagMap) {
var flag, inheritance, value, _i, _len, _ref;
for (flag in flagMap) {
value = flagMap[flag];
if (typeof value === "number") {
value = {
value: value
};
flagMap[flag] = value;
}
if (value.inheritedFlags == null) {
value.inheritedFlags = [];
}
_ref = value.inheritedFlags;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
inheritance = _ref[_i];
if (!(inheritance in flagMap)) {
throw new Error("The " + flag + " flag attempts to inherit the non-existent " + inheritance + " flag.");
}
}
if (!isPowerOfTwo(value.value)) {
throw new Error("The value for the " + flag + " flag (" + value.value + ") is not a power of two.");
}
}
return this.flagMap = flagMap;
};
BitmaskHandler.prototype.create = function(initialValue, initialOriginalValue) {
var instance;
if (initialValue == null) {
initialValue = 0;
}
return instance = new Bitmask(this, initialValue, initialOriginalValue);
};
return BitmaskHandler;
})();
module.exports = function(flagMap) {
return new BitmaskHandler(flagMap);
};

@ -0,0 +1,33 @@
{
"name": "node-bitmask-flags",
"version": "0.0.1",
"description": "A utility for dealing with flags and permissions using bitmasks.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git@git.cryto.net:projects/joepie91/node-bitmask-flags"
},
"keywords": [
"bitmask",
"flag",
"permissions",
"authorization"
],
"author": "Sven Slootweg",
"license": "WTFPL",
"devDependencies": {
"gulp": "~3.8.0",
"gulp-cached": "~0.0.3",
"gulp-coffee": "~2.0.1",
"gulp-concat": "~2.2.0",
"gulp-livereload": "~2.1.0",
"gulp-nodemon": "~1.0.4",
"gulp-plumber": "~0.6.3",
"gulp-remember": "~0.2.0",
"gulp-rename": "~1.2.0",
"gulp-util": "~2.2.17"
}
}

@ -0,0 +1,51 @@
/* I haven't bothered to write 'real' tests yet. I should probably do that at some point, given that this is supposed to deal with authentication. Pull requests welcome! */
var bitmaskFlags = require("./");
var flagHandler = bitmaskFlags({
view_announcements: 1,
create_announcements: {
value: 2,
inheritedFlags: ["edit_announcements"]
},
edit_announcements: 4,
delete_announcements: 8
});
var permissions = flagHandler.create(); // This is how you create a new value, starting at 0 (no flags).
permissions.add("view_announcements"); // This permission is now set.
permissions.add("delete_announcements"); // The value now has `view_announcements` and `delete_announcements` flags.
permissions.remove("view_announcements"); // Now only `delete_announcements` is left.
console.log(false, permissions.has("view_announcements")); // false
console.log(true, permissions.has("delete_announcements")); // true
console.log(permissions.getValue());
var permissions = flagHandler.create(0, 0); // Like we saw before...
permissions.add("create_announcements"); // The user now has `create_announcements` AND `edit_anouncements`.
console.log(true, permissions.has("edit_announcements")); // true
permissions.remove("create_announcements"); // The user now has no permissions.
console.log(false, permissions.has("edit_announcements")); // false
/* Now let's try that again, but this time explicitly setting `edit_announcements`. */
permissions.add("create_announcements"); // The user now has `create_announcements` and `edit_anouncements`.
permissions.add("edit_announcements"); // The user *still* has `create_announcements` and `edit_anouncements`.
console.log(permissions.getValue());
console.log(permissions.getOriginalValue());
permissions.remove("create_announcements");
console.log(true, permissions.has("edit_announcements")); // true - Because we set `edit_announcements` explicitly, it wasn't unset along with `create_announcements`. Magic!
console.log(permissions.getValue());
console.log(permissions.getOriginalValue());
Loading…
Cancel
Save