From 8f1aafb328be801780bf9714e788a80bc0a9bd0a Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 2 Feb 2015 10:47:41 -0500 Subject: [PATCH 1/2] storage: acl: refactor --- lib/storage/acl.js | 264 +++++++++++++++++++++++++++++++++++++----- lib/storage/bucket.js | 28 ++++- lib/storage/file.js | 12 +- npm-debug.log | 29 +++++ regression/storage.js | 36 +++--- test/storage/acl.js | 145 +++++++++++++++++------ test/storage/file.js | 2 +- 7 files changed, 431 insertions(+), 85 deletions(-) create mode 100644 npm-debug.log diff --git a/lib/storage/acl.js b/lib/storage/acl.js index 2b03ba2de57..3730896395d 100644 --- a/lib/storage/acl.js +++ b/lib/storage/acl.js @@ -20,6 +20,8 @@ 'use strict'; +var nodeutil = require('util'); + /** * @type module:common/util * @private @@ -32,12 +34,12 @@ var util = require('../common/util.js'); * users and allow other users to access your buckets and objects. * * An ACL consists of one or more entries, where each entry grants permissions - * to a scope. Permissions define the actions that can be performed against an - * object or bucket (for example, `READ` or `WRITE`); the scope defines who the + * to an entity. Permissions define the actions that can be performed against an + * object or bucket (for example, `READ` or `WRITE`); the entity defines who the * permission applies to (for example, a specific user or group of users). * - * Where a `scope` value is accepted, we follow the format the Cloud Storage API - * expects. + * Where an `entity` value is accepted, we follow the format the Cloud Storage + * API expects. * * Refer to * https://cloud.google.com/storage/docs/json_api/v1/defaultObjectAccessControls @@ -67,17 +69,138 @@ var util = require('../common/util.js'); * @alias module:storage/acl */ function Acl(options) { + AclRoleAccessorMethods.call(this); + this.makeReq = options.makeReq; this.pathPrefix = options.pathPrefix; } +/** + * An object of convenience methods to add or delete owner ACL permissions for a + * given entity. + * + * 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 as an owner of a file. + * //- + * myFile.acl.owners.addUser('email@example.com', function(err, aclObject) {}); + * + * //- + * // For reference, the above command is the same as running the following. + * //- + * myFile.acl.add({ + * entity: 'user-email@example.com', + * role: storage.acl.OWNER_ROLE + * }, function(err, aclObject) {}); + */ +Acl.prototype.owners = {}; + +/** + * An object of convenience methods to add or delete reader ACL permissions for + * a given entity. + * + * 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 as an owner of a file. + * //- + * myFile.acl.readers.addUser('email@example.com', function(err, aclObject) {}); + * + * //- + * // For reference, the above command is the same as running the following. + * //- + * myFile.acl.add({ + * entity: 'user-email@example.com', + * role: storage.acl.READER_ROLE + * }, function(err, aclObject) {}); + */ +Acl.prototype.readers = {}; + +/** + * An object of convenience methods to add or delete writer ACL permissions for + * a given entity. + * + * 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 as an owner of a file. + * //- + * myFile.acl.writers.addUser('email@example.com', function(err, aclObject) {}); + * + * //- + * // For reference, the above command is the same as running the following. + * //- + * myFile.acl.add({ + * entity: 'user-email@example.com', + * 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}. * * @param {object} options - Configuration object. - * @param {string} options.scope - Whose permissions will be added. - * @param {string} options.role - Permissions allowed for the defined scope. See - * {module:storage#acl}. + * @param {string} options.entity - Whose permissions will be added. + * @param {string} options.role - Permissions allowed for the defined entity. + * See {module:storage#acl}. * @param {int=} options.generation - **File Objects Only** Select a specific * revision of this file (as opposed to the latest version, the default). * @param {function} callback - The callback function. @@ -86,7 +209,7 @@ function Acl(options) { * * @example * myBucket.acl.add({ - * scope: 'user-useremail@example.com', + * entity: 'user-useremail@example.com', * role: storage.acl.OWNER_ROLE * }, function(err, aclObject) {}); * @@ -96,7 +219,7 @@ function Acl(options) { * // revision of a file. * //- * myFile.acl.add({ - * scope: 'user-useremail@example.com', + * entity: 'user-useremail@example.com', * role: storage.acl.OWNER_ROLE, * generation: 1 * }, function(err, aclObject) {}); @@ -105,7 +228,7 @@ Acl.prototype.add = function(options, callback) { var that = this; var body = { - entity: options.scope, + entity: options.entity, role: options.role.toUpperCase() }; @@ -131,7 +254,7 @@ Acl.prototype.add = function(options, callback) { * Delete access controls on a {module:storage/bucket} or {module:storage/file}. * * @param {object=} options - Configuration object. - * @param {string} options.scope - Whose permissions will be revoked. + * @param {string} options.entity - Whose permissions will be revoked. * @param {int=} options.generation - **File Objects Only** Select a specific * revision of this file (as opposed to the latest version, the default). * @param {function} callback - The callback function. @@ -140,19 +263,19 @@ Acl.prototype.add = function(options, callback) { * * @example * myBucket.acl.delete({ - * scope: 'user-useremail@example.com' + * entity: 'user-useremail@example.com' * }, function(err) {}); * * //- * // For file ACL operations, you can also specify a `generation` property. * //- * myFile.acl.delete({ - * scope: 'user-useremail@example.com', + * entity: 'user-useremail@example.com', * generation: 1 * }, function(err) {}); */ Acl.prototype.delete = function(options, callback) { - var path = '/' + encodeURIComponent(options.scope); + var path = '/' + encodeURIComponent(options.entity); var query = null; if (options.generation) { @@ -166,13 +289,13 @@ Acl.prototype.delete = function(options, callback) { /** * Get access controls on a {module:storage/bucket} or {module:storage/file}. If - * a scope is omitted, you will receive an array of all applicable access + * an entity is omitted, you will receive an array of all applicable access * controls. * * @param {object|function} options - Configuration object. If you want to * receive a list of all access controls, pass the callback function as the * only argument. - * @param {string=} options.scope - Whose permissions will be fetched. + * @param {string=} options.entity - Whose permissions will be fetched. * @param {int=} options.generation - **File Objects Only** Select a specific * revision of this file (as opposed to the latest version, the default). * @@ -180,7 +303,7 @@ Acl.prototype.delete = function(options, callback) { * * @example * myBucket.acl.get({ - * scope: 'user-useremail@example.com' + * entity: 'user-useremail@example.com' * }, function(err, aclObject) {}); * * //- @@ -189,7 +312,7 @@ Acl.prototype.delete = function(options, callback) { * myBucket.acl.get(function(err, aclObjects) { * // aclObjects = [ * // { - * // scope: 'user-useremail@example.com', + * // entity: 'user-useremail@example.com', * // role: 'owner' * // } * // ] @@ -199,7 +322,7 @@ Acl.prototype.delete = function(options, callback) { * // For file ACL operations, you can also specify a `generation` property. * //- * myFile.acl.get({ - * scope: 'user-useremail@example.com', + * entity: 'user-useremail@example.com', * generation: 1 * } function(err, aclObject) {}); */ @@ -212,7 +335,7 @@ Acl.prototype.get = function(options, callback) { callback = options; options = null; } else { - path = '/' + encodeURIComponent(options.scope); + path = '/' + encodeURIComponent(options.entity); if (options.generation) { query = { @@ -243,9 +366,9 @@ Acl.prototype.get = function(options, callback) { * Update access controls on a {module:storage/bucket} or {module:storage/file}. * * @param {object=} options - Configuration object. - * @param {string} options.scope - Whose permissions will be added. - * @param {string} options.role - Permissions allowed for the defined scope. See - * {module:storage#acl}. + * @param {string} options.entity - Whose permissions will be updated. + * @param {string} options.role - Permissions allowed for the defined entity. + * See {module:storage#acl}. * @param {int=} options.generation - **File Objects Only** Select a specific * revision of this file (as opposed to the latest version, the default). * @param {function} callback - The callback function. @@ -256,7 +379,7 @@ Acl.prototype.get = function(options, callback) { * var storage = gcloud.storage(); * * myBucket.acl.update({ - * scope: 'user-useremail@example.com', + * entity: 'user-useremail@example.com', * role: storage.acl.WRITER_ROLE * }, function(err) {}); * @@ -264,14 +387,14 @@ Acl.prototype.get = function(options, callback) { * // For file ACL operations, you can also specify a `generation` property. * //- * myFile.acl.update({ - * scope: 'user-useremail@example.com', + * entity: 'user-useremail@example.com', * role: storage.acl.WRITER_ROLE, * generation: 1 * }, function(err) {}); */ Acl.prototype.update = function(options, callback) { var that = this; - var path = '/' + encodeURIComponent(options.scope); + var path = '/' + encodeURIComponent(options.entity); var query = null; if (options.generation) { @@ -301,7 +424,7 @@ Acl.prototype.update = function(options, callback) { */ Acl.prototype.makeAclObject_ = function(accessControlObject) { var obj = { - scope: accessControlObject.scope, + entity: accessControlObject.entity, role: accessControlObject.role }; @@ -328,3 +451,90 @@ Acl.prototype.makeReq_ = function(method, path, query, body, callback) { }; module.exports = Acl; + +/** + * 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@example.com', function(err, acl) {}); + * + * myBucket.acl.writers.addDomain('example.com', function(err, acl) {}); + * + * @private + */ +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. `user-email@example.com`. + '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.toLowerCase() + 's'; + + this[roleGroup] = entities.reduce(function(acc, entity) { + var isPrefix = entity.charAt(entity.length - 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 an `entity` and `role`. + acc[method] = function(entityId, callback) { + var apiEntity; + + if (isPrefix) { + apiEntity = entity + entityId; + } else { + // If the entity is not a prefix, it is a special entity group that + // does not require further details. The accessor methods only accept + // a callback. + apiEntity = entity; + callback = entityId; + } + + that[accessMethod]({ + entity: apiEntity, + role: role + }, callback); + }; + }); + + return acc; + }, {}); +}; + +module.exports.AclRoleAccessorMethods = AclRoleAccessorMethods; diff --git a/lib/storage/bucket.js b/lib/storage/bucket.js index 7e5c498fb72..17a725b7cb0 100644 --- a/lib/storage/bucket.js +++ b/lib/storage/bucket.js @@ -171,6 +171,24 @@ function Bucket(storage, name) { * @alias acl.default.update */ var aclDefaultUpdate = 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 */ } @@ -303,15 +321,19 @@ Bucket.prototype.getMetadata = function(callback) { * }, function(err, metadata) {}); */ Bucket.prototype.setMetadata = function(metadata, callback) { + var that = this; callback = callback || util.noop; + this.makeReq_('PATCH', '', null, metadata, function(err, resp) { if (err) { callback(err); return; } - this.metadata = resp; - callback(null, this.metadata); - }.bind(this)); + + that.metadata = resp; + + callback(null, that.metadata); + }); }; /** diff --git a/lib/storage/file.js b/lib/storage/file.js index 8e1a26ef7cf..d25cdea2cdd 100644 --- a/lib/storage/file.js +++ b/lib/storage/file.js @@ -714,16 +714,20 @@ File.prototype.getSignedUrl = function(options, callback) { * }, function(err, metadata) {}); */ File.prototype.setMetadata = function(metadata, callback) { - callback = callback || util.noop; + var that = this; var path = '/o/' + encodeURIComponent(this.name); + callback = callback || util.noop; + this.makeReq_('PATCH', path, null, metadata, function(err, resp) { if (err) { callback(err); return; } - this.metadata = resp; - callback(null, this.metadata); - }.bind(this)); + + that.metadata = resp; + + callback(null, that.metadata); + }); }; /** diff --git a/npm-debug.log b/npm-debug.log new file mode 100644 index 00000000000..d11ebe7c66a --- /dev/null +++ b/npm-debug.log @@ -0,0 +1,29 @@ +0 info it worked if it ends with ok +1 verbose cli [ '/Users/stephen/.nvm/v0.10.35/bin/node', +1 verbose cli '/Users/stephen/.nvm/v0.10.35/bin/npm', +1 verbose cli 'run', +1 verbose cli 'lint' ] +2 info using npm@1.4.28 +3 info using node@v0.10.35 +4 verbose run-script [ 'prelint', 'lint', 'postlint' ] +5 info prelint gcloud@0.12.0 +6 info lint gcloud@0.12.0 +7 verbose unsafe-perm in lifecycle true +8 info gcloud@0.12.0 Failed to exec lint script +9 error gcloud@0.12.0 lint: `jshint lib/ regression/ test/` +9 error Exit status 2 +10 error Failed at the gcloud@0.12.0 lint script. +10 error This is most likely a problem with the gcloud package, +10 error not with npm itself. +10 error Tell the author that this fails on your system: +10 error jshint lib/ regression/ test/ +10 error You can get their info via: +10 error npm owner ls gcloud +10 error There is likely additional logging output above. +11 error System Darwin 14.0.0 +12 error command "/Users/stephen/.nvm/v0.10.35/bin/node" "/Users/stephen/.nvm/v0.10.35/bin/npm" "run" "lint" +13 error cwd /Users/stephen/dev/gcloud-node +14 error node -v v0.10.35 +15 error npm -v 1.4.28 +16 error code ELIFECYCLE +17 verbose exit [ 1, true ] diff --git a/regression/storage.js b/regression/storage.js index 322024096c3..3503a3acfc0 100644 --- a/regression/storage.js +++ b/regression/storage.js @@ -90,7 +90,7 @@ describe('storage', function() { describe('buckets', function() { it('should get access controls', function(done) { - bucket.acl.get(done, function(err, accessControls) { + bucket.acl.get(function(err, accessControls) { assert.ifError(err); assert(Array.isArray(accessControls)); done(); @@ -99,26 +99,26 @@ describe('storage', function() { it('should add entity to default access controls', function(done) { bucket.acl.default.add({ - scope: USER_ACCOUNT, + entity: USER_ACCOUNT, role: storage.acl.OWNER_ROLE }, function(err, accessControl) { assert.ifError(err); assert.equal(accessControl.role, storage.acl.OWNER_ROLE); bucket.acl.default.get({ - scope: USER_ACCOUNT + entity: USER_ACCOUNT }, function(err, accessControl) { assert.ifError(err); assert.equal(accessControl.role, storage.acl.OWNER_ROLE); bucket.acl.default.update({ - scope: USER_ACCOUNT, + entity: USER_ACCOUNT, role: storage.acl.READER_ROLE }, function(err, accessControl) { assert.ifError(err); assert.equal(accessControl.role, storage.acl.READER_ROLE); - bucket.acl.default.delete({ scope: USER_ACCOUNT }, done); + bucket.acl.default.delete({ entity: USER_ACCOUNT }, done); }); }); }); @@ -134,37 +134,39 @@ describe('storage', function() { it('should grant an account access', function(done) { bucket.acl.add({ - scope: USER_ACCOUNT, + entity: USER_ACCOUNT, role: storage.acl.OWNER_ROLE }, function(err, accessControl) { assert.ifError(err); assert.equal(accessControl.role, storage.acl.OWNER_ROLE); - bucket.acl.get({ scope: USER_ACCOUNT }, function(err, accessControl) { + var opts = { entity: USER_ACCOUNT }; + + bucket.acl.get(opts, function(err, accessControl) { assert.ifError(err); assert.equal(accessControl.role, storage.acl.OWNER_ROLE); - bucket.acl.delete({ scope: USER_ACCOUNT }, done); + bucket.acl.delete(opts, done); }); }); }); it('should update an account', function(done) { bucket.acl.add({ - scope: USER_ACCOUNT, + entity: USER_ACCOUNT, role: storage.acl.OWNER_ROLE }, function(err, accessControl) { assert.ifError(err); assert.equal(accessControl.role, storage.acl.OWNER_ROLE); bucket.acl.update({ - scope: USER_ACCOUNT, + entity: USER_ACCOUNT, role: storage.acl.WRITER_ROLE }, function(err, accessControl) { assert.ifError(err); assert.equal(accessControl.role, storage.acl.WRITER_ROLE); - bucket.acl.delete({ scope: USER_ACCOUNT }, done); + bucket.acl.delete({ entity: USER_ACCOUNT }, done); }); }); }); @@ -199,38 +201,38 @@ describe('storage', function() { it('should grant an account access', function(done) { file.acl.add({ - scope: USER_ACCOUNT, + entity: USER_ACCOUNT, role: storage.acl.OWNER_ROLE }, function(err, accessControl) { assert.ifError(err); assert.equal(accessControl.role, storage.acl.OWNER_ROLE); - file.acl.get({ scope: USER_ACCOUNT }, function(err, accessControl) { + file.acl.get({ entity: USER_ACCOUNT }, function(err, accessControl) { assert.ifError(err); assert.equal(accessControl.role, storage.acl.OWNER_ROLE); - file.acl.delete({ scope: USER_ACCOUNT }, done); + file.acl.delete({ entity: USER_ACCOUNT }, done); }); }); }); it('should update an account', function(done) { file.acl.add({ - scope: USER_ACCOUNT, + entity: USER_ACCOUNT, role: storage.acl.OWNER_ROLE }, function(err, accessControl) { assert.ifError(err); assert.equal(accessControl.role, storage.acl.OWNER_ROLE); file.acl.update({ - scope: USER_ACCOUNT, + entity: USER_ACCOUNT, role: storage.acl.READER_ROLE }, function(err, accessControl) { assert.ifError(err); assert.equal(accessControl.role, storage.acl.READER_ROLE); - file.acl.delete({ scope: USER_ACCOUNT }, done); + file.acl.delete({ entity: USER_ACCOUNT }, done); }); }); }); diff --git a/test/storage/acl.js b/test/storage/acl.js index 42594408348..03e92e653df 100644 --- a/test/storage/acl.js +++ b/test/storage/acl.js @@ -20,7 +20,8 @@ var Acl = require('../../lib/storage/acl.js'); var assert = require('assert'); -var storage = require('../../lib/storage/index.js'); +var async = require('async'); +var Storage = require('../../lib/storage/index.js'); var util = require('../../lib/common/util.js'); describe('storage/acl', function() { @@ -28,8 +29,8 @@ describe('storage/acl', function() { var ERROR = new Error('Error.'); var MAKE_REQ = util.noop; var PATH_PREFIX = '/acl'; - var ROLE = storage.acl.OWNER_ROLE; - var SCOPE = 'user-user@example.com'; + var ROLE = Storage.acl.OWNER_ROLE; + var ENTITY = 'user-user@example.com'; beforeEach(function() { acl = new Acl({ makeReq: MAKE_REQ, pathPrefix: PATH_PREFIX }); @@ -48,17 +49,17 @@ describe('storage/acl', function() { assert.equal(method, 'POST'); assert.equal(path, ''); assert.strictEqual(query, null); - assert.deepEqual(body, { entity: SCOPE, role: ROLE }); + assert.deepEqual(body, { entity: ENTITY, role: ROLE }); done(); }; - acl.add({ scope: SCOPE, role: ROLE }, assert.ifError); + acl.add({ entity: ENTITY, role: ROLE }, assert.ifError); }); it('executes the callback with an ACL object', function(done) { - var apiResponse = { scope: SCOPE, role: ROLE }; - var expectedAclObject = { scope: SCOPE, role: ROLE }; + var apiResponse = { entity: ENTITY, role: ROLE }; + var expectedAclObject = { entity: ENTITY, role: ROLE }; acl.makeAclObject_ = function (obj) { assert.deepEqual(obj, apiResponse); @@ -69,7 +70,7 @@ describe('storage/acl', function() { callback(null, apiResponse); }; - acl.add({ scope: SCOPE, role: ROLE }, function(err, aclObject) { + acl.add({ entity: ENTITY, role: ROLE }, function(err, aclObject) { assert.ifError(err); assert.deepEqual(aclObject, expectedAclObject); done(); @@ -81,7 +82,7 @@ describe('storage/acl', function() { callback(ERROR); }; - acl.add({ scope: SCOPE, role: ROLE }, function(err) { + acl.add({ entity: ENTITY, role: ROLE }, function(err) { assert.deepEqual(err, ERROR); done(); }); @@ -92,14 +93,14 @@ describe('storage/acl', function() { it('makes the correct api request', function(done) { acl.makeReq_ = function(method, path, query, body) { assert.equal(method, 'DELETE'); - assert.equal(path, '/' + encodeURIComponent(SCOPE)); + assert.equal(path, '/' + encodeURIComponent(ENTITY)); assert.strictEqual(query, null); assert.strictEqual(body, null); done(); }; - acl.delete({ scope: SCOPE }, assert.ifError); + acl.delete({ entity: ENTITY }, assert.ifError); }); it('should execute the callback with an error', function(done) { @@ -107,7 +108,7 @@ describe('storage/acl', function() { callback(ERROR); }; - acl.delete({ scope: SCOPE }, function(err) { + acl.delete({ entity: ENTITY }, function(err) { assert.deepEqual(err, ERROR); done(); }); @@ -144,16 +145,16 @@ describe('storage/acl', function() { it('should pass an array of acl objects to the callback', function(done) { var apiResponse = { items: [ - { scope: SCOPE, role: ROLE }, - { scope: SCOPE, role: ROLE }, - { scope: SCOPE, role: ROLE } + { entity: ENTITY, role: ROLE }, + { entity: ENTITY, role: ROLE }, + { entity: ENTITY, role: ROLE } ] }; var expectedAclObjects = [ - { scope: SCOPE, role: ROLE }, - { scope: SCOPE, role: ROLE }, - { scope: SCOPE, role: ROLE } + { entity: ENTITY, role: ROLE }, + { entity: ENTITY, role: ROLE }, + { entity: ENTITY, role: ROLE } ]; acl.makeAclObject_ = function (obj, index) { @@ -172,18 +173,18 @@ describe('storage/acl', function() { }); }); - describe('ACL object for a scope', function() { + describe('ACL object for an entity', function() { it('should get a specific ACL object', function(done) { acl.makeReq_ = function(method, path, query, body) { assert.equal(method, 'GET'); - assert.equal(path, '/' + encodeURIComponent(SCOPE)); + assert.equal(path, '/' + encodeURIComponent(ENTITY)); assert.strictEqual(query, null); assert.strictEqual(body, null); done(); }; - acl.get({ scope: SCOPE }, assert.ifError); + acl.get({ entity: ENTITY }, assert.ifError); }); it('should accept a configuration object', function(done) { @@ -195,12 +196,12 @@ describe('storage/acl', function() { done(); }; - acl.get({ scope: SCOPE, generation: generation }, assert.ifError); + acl.get({ entity: ENTITY, generation: generation }, assert.ifError); }); it('should pass an acl object to the callback', function(done) { - var apiResponse = { scope: SCOPE, role: ROLE }; - var expectedAclObject = { scope: SCOPE, role: ROLE }; + var apiResponse = { entity: ENTITY, role: ROLE }; + var expectedAclObject = { entity: ENTITY, role: ROLE }; acl.makeAclObject_ = function () { return expectedAclObject; @@ -210,7 +211,7 @@ describe('storage/acl', function() { callback(null, apiResponse); }; - acl.get({ scope: SCOPE }, function(err, aclObject) { + acl.get({ entity: ENTITY }, function(err, aclObject) { assert.ifError(err); assert.deepEqual(aclObject, expectedAclObject); done(); @@ -234,19 +235,19 @@ describe('storage/acl', function() { it('should make the correct API request', function(done) { acl.makeReq_ = function(method, path, query, body) { assert.equal(method, 'PUT'); - assert.equal(path, '/' + encodeURIComponent(SCOPE)); + assert.equal(path, '/' + encodeURIComponent(ENTITY)); assert.strictEqual(query, null); assert.deepEqual(body, { role: ROLE }); done(); }; - acl.update({ scope: SCOPE, role: ROLE }, assert.ifError); + acl.update({ entity: ENTITY, role: ROLE }, assert.ifError); }); it('should pass an acl object to the callback', function(done) { - var apiResponse = { scope: SCOPE, role: ROLE }; - var expectedAclObject = { scope: SCOPE, role: ROLE }; + var apiResponse = { entity: ENTITY, role: ROLE }; + var expectedAclObject = { entity: ENTITY, role: ROLE }; acl.makeAclObject_ = function () { return expectedAclObject; @@ -256,7 +257,7 @@ describe('storage/acl', function() { callback(null, apiResponse); }; - acl.update({ scope: SCOPE, role: ROLE }, function(err, aclObject) { + acl.update({ entity: ENTITY, role: ROLE }, function(err, aclObject) { assert.ifError(err); assert.deepEqual(aclObject, expectedAclObject); done(); @@ -268,7 +269,7 @@ describe('storage/acl', function() { callback(ERROR); }; - acl.update({ scope: SCOPE, role: ROLE }, function(err) { + acl.update({ entity: ENTITY, role: ROLE }, function(err) { assert.deepEqual(err, ERROR); done(); }); @@ -283,7 +284,7 @@ describe('storage/acl', function() { }; var apiResponse = { - scope: SCOPE, + entity: ENTITY, role: ROLE, projectTeam: projectTeam, extra: 'ignored', @@ -291,7 +292,7 @@ describe('storage/acl', function() { }; assert.deepEqual(acl.makeAclObject_(apiResponse), { - scope: SCOPE, + entity: ENTITY, role: ROLE, projectTeam: projectTeam }); @@ -321,3 +322,81 @@ describe('storage/acl', function() { }); }); }); + +describe('storage/AclRoleAccessorMethods', function() { + var aclEntity; + + beforeEach(function() { + aclEntity = new Acl.AclRoleAccessorMethods(); + }); + + describe('initialization', function() { + it('should assign access methods for every role object', function() { + var expectedApi = [ + 'addAllAuthenticatedUsers', + 'deleteAllAuthenticatedUsers', + + 'addAllUsers', + 'deleteAllUsers', + + 'addDomain', + 'deleteDomain', + + 'addGroup', + 'deleteGroup', + + 'addProject', + 'deleteProject', + + 'addUser', + 'deleteUser' + ]; + + var actualOwnersApi = Object.keys(aclEntity.owners); + assert.deepEqual(actualOwnersApi, expectedApi); + + var actualReadersApi = Object.keys(aclEntity.readers); + assert.deepEqual(actualReadersApi, expectedApi); + + var actualWritersApi = Object.keys(aclEntity.writers); + assert.deepEqual(actualWritersApi, expectedApi); + }); + }); + + describe('_assignAccessMethods', function() { + it('should call parent method', function(done) { + var userName = 'email@example.com'; + var role = 'fakerole'; + + aclEntity.add = function (options, callback) { + assert.deepEqual(options, { + entity: 'user-' + userName, + role: role + }); + + callback(); + }; + + aclEntity.delete = function (options, callback) { + assert.deepEqual(options, { + entity: 'user-' + userName, + role: role + }); + + callback(); + }; + + aclEntity._assignAccessMethods(role); + + async.parallel([ + function(next) { + // The method name should be in plural form. (fakeroles vs fakerole) + aclEntity.fakeroles.addUser(userName, next); + }, + function(next) { + aclEntity.fakeroles.deleteUser(userName, next); + } + ], done); + }); + }); +}); diff --git a/test/storage/file.js b/test/storage/file.js index fd9cfa4e0ea..9058f8a4631 100644 --- a/test/storage/file.js +++ b/test/storage/file.js @@ -871,7 +871,7 @@ describe('File', function() { done(); }; - directoryFile.setMetadata(); + directoryFile.setMetadata(metadata); }); it('should execute callback', function(done) { From b670b6c27eadc4bd2a4c26de34f9be798d5f80a5 Mon Sep 17 00:00:00 2001 From: Stephen Sawchuk Date: Mon, 2 Feb 2015 11:09:55 -0500 Subject: [PATCH 2/2] add ACL examples to the docs --- docs/site/components/docs/docs.html | 14 ++++++++++++++ lib/storage/acl.js | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/site/components/docs/docs.html b/docs/site/components/docs/docs.html index 65e83d57595..a8e7c3f9b92 100644 --- a/docs/site/components/docs/docs.html +++ b/docs/site/components/docs/docs.html @@ -114,6 +114,20 @@

Storage Overview

var bucket = gcloud.storage.bucket('my-bucket');
+

ACLs

+

+ Google Cloud Storage uses access control lists (ACLs) to manage object and bucket access. ACLs are the mechanism you use to share files with other users and allow other users to access your buckets and files. +

+

+ Convenience methods are provided to perform common operations, such as the following. +

+
+// Allow a user to read files from a bucket. +bucket.acl.readers.addUser('email@example.com', function(err, aclObject) {}); + +// Revoke file ownership permissions from a group. +var myFile = bucket.file('my-file.txt'); +myFile.acl.owners.deleteGroup('group-id', function(err, aclObject) {});

See examples below for more on how to access your bucket to upload a file, read its files, create signed URLs, and more.

diff --git a/lib/storage/acl.js b/lib/storage/acl.js index 3730896395d..dcf6bc8da53 100644 --- a/lib/storage/acl.js +++ b/lib/storage/acl.js @@ -139,7 +139,7 @@ Acl.prototype.owners = {}; * * @example * //- - * // Add a user as an owner of a file. + * // Add a user as a reader of a file. * //- * myFile.acl.readers.addUser('email@example.com', function(err, aclObject) {}); * @@ -178,7 +178,7 @@ Acl.prototype.readers = {}; * * @example * //- - * // Add a user as an owner of a file. + * // Add a user as a writer of a file. * //- * myFile.acl.writers.addUser('email@example.com', function(err, aclObject) {}); *