Skip to content

Commit

Permalink
storage: acl: refactor
Browse files Browse the repository at this point in the history
  • Loading branch information
stephenplusplus committed Jan 21, 2015
1 parent d6d9c08 commit b0c41f6
Show file tree
Hide file tree
Showing 4 changed files with 416 additions and 4 deletions.
252 changes: 251 additions & 1 deletion lib/storage/acl.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
*/
var util = require('../common/util.js');

var nodeutil = require('util');

/**
* Google Cloud Storage uses access control lists (ACLs) to manage object and
* bucket access. ACLs are the mechanism you use to share objects with other
Expand Down Expand Up @@ -67,10 +69,131 @@ var util = require('../common/util.js');
* @alias module:storage/acl
*/
function Acl(options) {
AclRoleAccessorMethods.call(this);

this.makeReq = options.makeReq;
this.pathPrefix = options.pathPrefix;
}

/**
* A convenience object of methods to add or delete ACL permissions from a
* scope.
*
* The supported methods include:
*
* - `myFile.acl.owners.addAllAuthenticatedUsers`
* - `myFile.acl.owners.deleteAllAuthenticatedUsers`
* - `myFile.acl.owners.addAllUsers`
* - `myFile.acl.owners.deleteAllUsers`
* - `myFile.acl.owners.addDomain`
* - `myFile.acl.owners.deleteDomain`
* - `myFile.acl.owners.addGroup`
* - `myFile.acl.owners.deleteGroup`
* - `myFile.acl.owners.addProject`
* - `myFile.acl.owners.deleteProject`
* - `myFile.acl.owners.addUser`
* - `myFile.acl.owners.deleteUser`
*
* @alias acl.owners
*
* @return {object}
*
* @example
* //-
* // Add a user scope as an owner of a file.
* //-
* myFile.acl.owners.addUser('[email protected]', function(err, aclObject) {});
*
* //-
* // For reference, the above command is the same as running the following.
* //-
* myFile.acl.add({
* scope: '[email protected]',
* role: storage.acl.OWNER_ROLE
* }, function(err, aclObject) {});
*/
Acl.prototype.owners = {};

/**
* A convenience object of methods to add or delete ACL permissions from a
* scope.
*
* The supported methods include:
*
* - `myFile.acl.readers.addAllAuthenticatedUsers`
* - `myFile.acl.readers.deleteAllAuthenticatedUsers`
* - `myFile.acl.readers.addAllUsers`
* - `myFile.acl.readers.deleteAllUsers`
* - `myFile.acl.readers.addDomain`
* - `myFile.acl.readers.deleteDomain`
* - `myFile.acl.readers.addGroup`
* - `myFile.acl.readers.deleteGroup`
* - `myFile.acl.readers.addProject`
* - `myFile.acl.readers.deleteProject`
* - `myFile.acl.readers.addUser`
* - `myFile.acl.readers.deleteUser`
*
* @alias acl.readers
*
* @return {object}
*
* @example
* //-
* // Add a user scope as an owner of a file.
* //-
* myFile.acl.readers.addUser('[email protected]', function(err, aclObject) {});
*
* //-
* // For reference, the above command is the same as running the following.
* //-
* myFile.acl.add({
* scope: '[email protected]',
* role: storage.acl.READER_ROLE
* }, function(err, aclObject) {});
*/
Acl.prototype.readers = {};

/**
* A convenience object of methods to add or delete ACL permissions from a
* scope.
*
* The supported methods include:
*
* - `myFile.acl.writers.addAllAuthenticatedUsers`
* - `myFile.acl.writers.deleteAllAuthenticatedUsers`
* - `myFile.acl.writers.addAllUsers`
* - `myFile.acl.writers.deleteAllUsers`
* - `myFile.acl.writers.addDomain`
* - `myFile.acl.writers.deleteDomain`
* - `myFile.acl.writers.addGroup`
* - `myFile.acl.writers.deleteGroup`
* - `myFile.acl.writers.addProject`
* - `myFile.acl.writers.deleteProject`
* - `myFile.acl.writers.addUser`
* - `myFile.acl.writers.deleteUser`
*
* @alias acl.writers
*
* @return {object}
*
* @example
* //-
* // Add a user scope as an owner of a file.
* //-
* myFile.acl.writers.addUser('[email protected]', function(err, aclObject) {});
*
* //-
* // For reference, the above command is the same as running the following.
* //-
* myFile.acl.add({
* scope: '[email protected]',
* role: storage.acl.WRITER_ROLE
* }, function(err, aclObject) {});
*/
Acl.prototype.writers = {};

nodeutil.inherits(Acl, AclRoleAccessorMethods);

/**
* Add access controls on a {module:storage/bucket} or {module:storage/file}.
*
Expand Down Expand Up @@ -294,14 +417,56 @@ Acl.prototype.update = function(options, callback) {
});
};

/**
* Make a {module:storage/bucket} or {module:storage/file} object private.
*
* This is a short-hand method which will remove ACL permissions for the
* "allUsers" scope.
*
* @param {function} callback - The callback function.
*
* @alias acl.makePrivate
*
* @example
* myBucket.acl.makePrivate(function(err) {});
* myFile.acl.makePrivate(function(err) {});
*/
Acl.prototype.makePrivate = function(callback) {
this.delete({
scope: 'allUsers'
}, callback);
};

/**
* Make a {module:storage/bucket} or {module:storage/file} object publicly
* readable.
*
* This is a short-hand method which will grant "reader" permissions to the
* "allUsers" scope.
*
* @param {function} callback - The callback function.
*
* @alias acl.makePublic
*
* @example
* myBucket.acl.makePublic(function(err) {});
* myFile.acl.makePublic(function(err) {});
*/
Acl.prototype.makePublic = function(callback) {
this.add({
scope: 'allUsers',
role: 'READER'
}, callback);
};

/**
* Transform API responses to a consistent object format.
*
* @private
*/
Acl.prototype.makeAclObject_ = function(accessControlObject) {
var obj = {
scope: accessControlObject.scope,
scope: accessControlObject.entity,
role: accessControlObject.role
};

Expand All @@ -328,3 +493,88 @@ Acl.prototype.makeReq_ = function(method, path, query, body, callback) {
};

module.exports = Acl;

/*! Developer Documentation
* Attach functionality to a {module:storage/acl} instance. This will add an
* object for each role group (owners, readers, and writers), with each object
* containing methods to add or delete a type of entity.
*
* As an example, here are a few methods that are created.
*
* myBucket.acl.readers.deleteGroup('groupId', function(err) {});
*
* myBucket.acl.owners.addUser('[email protected]', function(err) {});
*
* myBucket.acl.writers.addDomain('example.com', function(err) {});
*/
function AclRoleAccessorMethods() {
AclRoleAccessorMethods.roles.forEach(this._assignAccessMethods.bind(this));
}

AclRoleAccessorMethods.accessMethods = [
'add',
'delete'
];

AclRoleAccessorMethods.entities = [
// Special entity groups that do not require further specification.
'allAuthenticatedUsers',
'allUsers',

// Entity groups that require specification, e.g. `[email protected]`.
'domain-',
'group-',
'project-',
'user-'
];

AclRoleAccessorMethods.roles = [
'owner',
'reader',
'writer'
];

AclRoleAccessorMethods.prototype._assignAccessMethods = function(role) {
var that = this;

var accessMethods = AclRoleAccessorMethods.accessMethods;
var entities = AclRoleAccessorMethods.entities;
var roleGroup = role + 's';

this[roleGroup] = entities.reduce(function(acc, entity) {
var isPrefix = entity.substr(-1) === '-';

accessMethods.forEach(function(accessMethod) {
var method = accessMethod + entity[0].toUpperCase() + entity.substr(1);

if (isPrefix) {
method = method.replace('-', '');
}

// Wrap the parent accessor method (e.g. `add` or `delete`) to avoid the
// more complex API of specifying a `scope` and `role`.
acc[method] = function(entityId, callback) {
var scope;

if (isPrefix) {
scope = entity + entityId;
} else {
// If the scope is not a prefix, it is a special entity group that
// does not require further details. The accessor methods only accept
// a callback.
scope = entity;
callback = entityId;
}

that[accessMethod]({
scope: scope,
role: role
}, callback);
};
});

return acc;
}, {});
};

module.exports.AclRoleAccessorMethods = AclRoleAccessorMethods;
30 changes: 30 additions & 0 deletions lib/storage/bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,36 @@ function Bucket(storage, name) {
* @alias acl.default.update
*/
var aclDefaultUpdate = true;

/**
* Maps to {module:storage/bucket#acl.makePrivate}.
* @alias acl.default.makePrivate
*/
var aclDefaultMakePrivate = true;

/**
* Maps to {module:storage/bucket#acl.makePublic}.
* @alias acl.default.makePublic
*/
var aclDefaultMakePublic = true;

/**
* Maps to {module:storage/bucket#acl.owners}.
* @alias acl.default.owners
*/
var aclDefaultOwners = true;

/**
* Maps to {module:storage/bucket#acl.readers}.
* @alias acl.default.readers
*/
var aclDefaultReaders = true;

/**
* Maps to {module:storage/bucket#acl.writers}.
* @alias acl.default.writers
*/
var aclDefaultWriters = true;
/* jshint ignore:end */
}

Expand Down
24 changes: 24 additions & 0 deletions regression/storage.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,18 @@ describe('storage', function() {
});
});

it('should make a bucket publicly readable', function(done) {
bucket.acl.makePublic(function(err, accessControl) {
assert.equal(accessControl.role, storage.acl.READER_ROLE);
assert.equal(accessControl.scope, 'allUsers');
done();
});
});

it('should make a bucket private', function(done) {
bucket.acl.makePrivate(done);
});

it('should update an account', function(done) {
bucket.acl.add({
scope: USER_ACCOUNT,
Expand Down Expand Up @@ -234,6 +246,18 @@ describe('storage', function() {
});
});
});

it('should make a file publicly readable', function(done) {
file.acl.makePublic(function(err, accessControl) {
assert.equal(accessControl.role, storage.acl.READER_ROLE);
assert.equal(accessControl.scope, 'allUsers');
done();
});
});

it('should make a file private', function(done) {
file.acl.makePrivate(done);
});
});
});

Expand Down
Loading

0 comments on commit b0c41f6

Please sign in to comment.