UNPKG

26.4 kBJavaScriptView Raw
1// Copyright 2019 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14import { promisifyAll } from '@google-cloud/promisify';
15/**
16 * Attach functionality to a {@link Storage.acl} instance. This will add an
17 * object for each role group (owners, readers, and writers), with each object
18 * containing methods to add or delete a type of entity.
19 *
20 * As an example, here are a few methods that are created.
21 *
22 * myBucket.acl.readers.deleteGroup('groupId', function(err) {});
23 *
24 * myBucket.acl.owners.addUser('email@example.com', function(err, acl) {});
25 *
26 * myBucket.acl.writers.addDomain('example.com', function(err, acl) {});
27 *
28 * @private
29 */
30class AclRoleAccessorMethods {
31 constructor() {
32 this.owners = {};
33 this.readers = {};
34 this.writers = {};
35 /**
36 * An object of convenience methods to add or delete owner ACL permissions
37 * for a given entity.
38 *
39 * The supported methods include:
40 *
41 * - `myFile.acl.owners.addAllAuthenticatedUsers`
42 * - `myFile.acl.owners.deleteAllAuthenticatedUsers`
43 * - `myFile.acl.owners.addAllUsers`
44 * - `myFile.acl.owners.deleteAllUsers`
45 * - `myFile.acl.owners.addDomain`
46 * - `myFile.acl.owners.deleteDomain`
47 * - `myFile.acl.owners.addGroup`
48 * - `myFile.acl.owners.deleteGroup`
49 * - `myFile.acl.owners.addProject`
50 * - `myFile.acl.owners.deleteProject`
51 * - `myFile.acl.owners.addUser`
52 * - `myFile.acl.owners.deleteUser`
53 *
54 * @name Acl#owners
55 *
56 * @example
57 * ```
58 * const storage = require('@google-cloud/storage')();
59 * const myBucket = storage.bucket('my-bucket');
60 * const myFile = myBucket.file('my-file');
61 *
62 * //-
63 * // Add a user as an owner of a file.
64 * //-
65 * const myBucket = gcs.bucket('my-bucket');
66 * const myFile = myBucket.file('my-file');
67 * myFile.acl.owners.addUser('email@example.com', function(err, aclObject)
68 * {});
69 *
70 * //-
71 * // For reference, the above command is the same as running the following.
72 * //-
73 * myFile.acl.add({
74 * entity: 'user-email@example.com',
75 * role: gcs.acl.OWNER_ROLE
76 * }, function(err, aclObject) {});
77 *
78 * //-
79 * // If the callback is omitted, we'll return a Promise.
80 * //-
81 * myFile.acl.owners.addUser('email@example.com').then(function(data) {
82 * const aclObject = data[0];
83 * const apiResponse = data[1];
84 * });
85 * ```
86 */
87 this.owners = {};
88 /**
89 * An object of convenience methods to add or delete reader ACL permissions
90 * for a given entity.
91 *
92 * The supported methods include:
93 *
94 * - `myFile.acl.readers.addAllAuthenticatedUsers`
95 * - `myFile.acl.readers.deleteAllAuthenticatedUsers`
96 * - `myFile.acl.readers.addAllUsers`
97 * - `myFile.acl.readers.deleteAllUsers`
98 * - `myFile.acl.readers.addDomain`
99 * - `myFile.acl.readers.deleteDomain`
100 * - `myFile.acl.readers.addGroup`
101 * - `myFile.acl.readers.deleteGroup`
102 * - `myFile.acl.readers.addProject`
103 * - `myFile.acl.readers.deleteProject`
104 * - `myFile.acl.readers.addUser`
105 * - `myFile.acl.readers.deleteUser`
106 *
107 * @name Acl#readers
108 *
109 * @example
110 * ```
111 * const storage = require('@google-cloud/storage')();
112 * const myBucket = storage.bucket('my-bucket');
113 * const myFile = myBucket.file('my-file');
114 *
115 * //-
116 * // Add a user as a reader of a file.
117 * //-
118 * myFile.acl.readers.addUser('email@example.com', function(err, aclObject)
119 * {});
120 *
121 * //-
122 * // For reference, the above command is the same as running the following.
123 * //-
124 * myFile.acl.add({
125 * entity: 'user-email@example.com',
126 * role: gcs.acl.READER_ROLE
127 * }, function(err, aclObject) {});
128 *
129 * //-
130 * // If the callback is omitted, we'll return a Promise.
131 * //-
132 * myFile.acl.readers.addUser('email@example.com').then(function(data) {
133 * const aclObject = data[0];
134 * const apiResponse = data[1];
135 * });
136 * ```
137 */
138 this.readers = {};
139 /**
140 * An object of convenience methods to add or delete writer ACL permissions
141 * for a given entity.
142 *
143 * The supported methods include:
144 *
145 * - `myFile.acl.writers.addAllAuthenticatedUsers`
146 * - `myFile.acl.writers.deleteAllAuthenticatedUsers`
147 * - `myFile.acl.writers.addAllUsers`
148 * - `myFile.acl.writers.deleteAllUsers`
149 * - `myFile.acl.writers.addDomain`
150 * - `myFile.acl.writers.deleteDomain`
151 * - `myFile.acl.writers.addGroup`
152 * - `myFile.acl.writers.deleteGroup`
153 * - `myFile.acl.writers.addProject`
154 * - `myFile.acl.writers.deleteProject`
155 * - `myFile.acl.writers.addUser`
156 * - `myFile.acl.writers.deleteUser`
157 *
158 * @name Acl#writers
159 *
160 * @example
161 * ```
162 * const storage = require('@google-cloud/storage')();
163 * const myBucket = storage.bucket('my-bucket');
164 * const myFile = myBucket.file('my-file');
165 *
166 * //-
167 * // Add a user as a writer of a file.
168 * //-
169 * myFile.acl.writers.addUser('email@example.com', function(err, aclObject)
170 * {});
171 *
172 * //-
173 * // For reference, the above command is the same as running the following.
174 * //-
175 * myFile.acl.add({
176 * entity: 'user-email@example.com',
177 * role: gcs.acl.WRITER_ROLE
178 * }, function(err, aclObject) {});
179 *
180 * //-
181 * // If the callback is omitted, we'll return a Promise.
182 * //-
183 * myFile.acl.writers.addUser('email@example.com').then(function(data) {
184 * const aclObject = data[0];
185 * const apiResponse = data[1];
186 * });
187 * ```
188 */
189 this.writers = {};
190 AclRoleAccessorMethods.roles.forEach(this._assignAccessMethods.bind(this));
191 }
192 _assignAccessMethods(role) {
193 const accessMethods = AclRoleAccessorMethods.accessMethods;
194 const entities = AclRoleAccessorMethods.entities;
195 const roleGroup = role.toLowerCase() + 's';
196 // eslint-disable-next-line @typescript-eslint/no-explicit-any
197 this[roleGroup] = entities.reduce((acc, entity) => {
198 const isPrefix = entity.charAt(entity.length - 1) === '-';
199 accessMethods.forEach(accessMethod => {
200 let method = accessMethod + entity[0].toUpperCase() + entity.substring(1);
201 if (isPrefix) {
202 method = method.replace('-', '');
203 }
204 // Wrap the parent accessor method (e.g. `add` or `delete`) to avoid the
205 // more complex API of specifying an `entity` and `role`.
206 // eslint-disable-next-line @typescript-eslint/no-explicit-any
207 acc[method] = (entityId, options, callback) => {
208 let apiEntity;
209 if (typeof options === 'function') {
210 callback = options;
211 options = {};
212 }
213 if (isPrefix) {
214 apiEntity = entity + entityId;
215 }
216 else {
217 // If the entity is not a prefix, it is a special entity group
218 // that does not require further details. The accessor methods
219 // only accept a callback.
220 apiEntity = entity;
221 callback = entityId;
222 }
223 options = Object.assign({
224 entity: apiEntity,
225 role,
226 }, options);
227 const args = [options];
228 if (typeof callback === 'function') {
229 args.push(callback);
230 }
231 // eslint-disable-next-line @typescript-eslint/no-explicit-any
232 return this[accessMethod].apply(this, args);
233 };
234 });
235 return acc;
236 }, {});
237 }
238}
239AclRoleAccessorMethods.accessMethods = ['add', 'delete'];
240AclRoleAccessorMethods.entities = [
241 // Special entity groups that do not require further specification.
242 'allAuthenticatedUsers',
243 'allUsers',
244 // Entity groups that require specification, e.g. `user-email@example.com`.
245 'domain-',
246 'group-',
247 'project-',
248 'user-',
249];
250AclRoleAccessorMethods.roles = ['OWNER', 'READER', 'WRITER'];
251/**
252 * Cloud Storage uses access control lists (ACLs) to manage object and
253 * bucket access. ACLs are the mechanism you use to share objects with other
254 * users and allow other users to access your buckets and objects.
255 *
256 * An ACL consists of one or more entries, where each entry grants permissions
257 * to an entity. Permissions define the actions that can be performed against an
258 * object or bucket (for example, `READ` or `WRITE`); the entity defines who the
259 * permission applies to (for example, a specific user or group of users).
260 *
261 * Where an `entity` value is accepted, we follow the format the Cloud Storage
262 * API expects.
263 *
264 * Refer to
265 * https://cloud.google.com/storage/docs/json_api/v1/defaultObjectAccessControls
266 * for the most up-to-date values.
267 *
268 * - `user-userId`
269 * - `user-email`
270 * - `group-groupId`
271 * - `group-email`
272 * - `domain-domain`
273 * - `project-team-projectId`
274 * - `allUsers`
275 * - `allAuthenticatedUsers`
276 *
277 * Examples:
278 *
279 * - The user "liz@example.com" would be `user-liz@example.com`.
280 * - The group "example@googlegroups.com" would be
281 * `group-example@googlegroups.com`.
282 * - To refer to all members of the Google Apps for Business domain
283 * "example.com", the entity would be `domain-example.com`.
284 *
285 * For more detailed information, see
286 * {@link http://goo.gl/6qBBPO| About Access Control Lists}.
287 *
288 * @constructor Acl
289 * @mixin
290 * @param {object} options Configuration options.
291 */
292class Acl extends AclRoleAccessorMethods {
293 constructor(options) {
294 super();
295 this.pathPrefix = options.pathPrefix;
296 this.request_ = options.request;
297 }
298 /**
299 * @typedef {array} AddAclResponse
300 * @property {object} 0 The Acl Objects.
301 * @property {object} 1 The full API response.
302 */
303 /**
304 * @callback AddAclCallback
305 * @param {?Error} err Request error, if any.
306 * @param {object} acl The Acl Objects.
307 * @param {object} apiResponse The full API response.
308 */
309 /**
310 * Add access controls on a {@link Bucket} or {@link File}.
311 *
312 * See {@link https://cloud.google.com/storage/docs/json_api/v1/bucketAccessControls/insert| BucketAccessControls: insert API Documentation}
313 * See {@link https://cloud.google.com/storage/docs/json_api/v1/objectAccessControls/insert| ObjectAccessControls: insert API Documentation}
314 *
315 * @param {object} options Configuration options.
316 * @param {string} options.entity Whose permissions will be added.
317 * @param {string} options.role Permissions allowed for the defined entity.
318 * See {@link https://cloud.google.com/storage/docs/access-control Access
319 * Control}.
320 * @param {number} [options.generation] **File Objects Only** Select a specific
321 * revision of this file (as opposed to the latest version, the default).
322 * @param {string} [options.userProject] The ID of the project which will be
323 * billed for the request.
324 * @param {AddAclCallback} [callback] Callback function.
325 * @returns {Promise<AddAclResponse>}
326 *
327 * @example
328 * ```
329 * const storage = require('@google-cloud/storage')();
330 * const myBucket = storage.bucket('my-bucket');
331 * const myFile = myBucket.file('my-file');
332 *
333 * const options = {
334 * entity: 'user-useremail@example.com',
335 * role: gcs.acl.OWNER_ROLE
336 * };
337 *
338 * myBucket.acl.add(options, function(err, aclObject, apiResponse) {});
339 *
340 * //-
341 * // For file ACL operations, you can also specify a `generation` property.
342 * // Here is how you would grant ownership permissions to a user on a
343 * specific
344 * // revision of a file.
345 * //-
346 * myFile.acl.add({
347 * entity: 'user-useremail@example.com',
348 * role: gcs.acl.OWNER_ROLE,
349 * generation: 1
350 * }, function(err, aclObject, apiResponse) {});
351 *
352 * //-
353 * // If the callback is omitted, we'll return a Promise.
354 * //-
355 * myBucket.acl.add(options).then(function(data) {
356 * const aclObject = data[0];
357 * const apiResponse = data[1];
358 * });
359 *
360 * ```
361 * @example <caption>include:samples/acl.js</caption>
362 * region_tag:storage_add_file_owner
363 * Example of adding an owner to a file:
364 *
365 * @example <caption>include:samples/acl.js</caption>
366 * region_tag:storage_add_bucket_owner
367 * Example of adding an owner to a bucket:
368 *
369 * @example <caption>include:samples/acl.js</caption>
370 * region_tag:storage_add_bucket_default_owner
371 * Example of adding a default owner to a bucket:
372 */
373 add(options, callback) {
374 const query = {};
375 if (options.generation) {
376 query.generation = options.generation;
377 }
378 if (options.userProject) {
379 query.userProject = options.userProject;
380 }
381 this.request({
382 method: 'POST',
383 uri: '',
384 qs: query,
385 maxRetries: 0, //explicitly set this value since this is a non-idempotent function
386 json: {
387 entity: options.entity,
388 role: options.role.toUpperCase(),
389 },
390 }, (err, resp) => {
391 if (err) {
392 callback(err, null, resp);
393 return;
394 }
395 callback(null, this.makeAclObject_(resp), resp);
396 });
397 }
398 /**
399 * @typedef {array} RemoveAclResponse
400 * @property {object} 0 The full API response.
401 */
402 /**
403 * @callback RemoveAclCallback
404 * @param {?Error} err Request error, if any.
405 * @param {object} apiResponse The full API response.
406 */
407 /**
408 * Delete access controls on a {@link Bucket} or {@link File}.
409 *
410 * See {@link https://cloud.google.com/storage/docs/json_api/v1/bucketAccessControls/delete| BucketAccessControls: delete API Documentation}
411 * See {@link https://cloud.google.com/storage/docs/json_api/v1/objectAccessControls/delete| ObjectAccessControls: delete API Documentation}
412 *
413 * @param {object} options Configuration object.
414 * @param {string} options.entity Whose permissions will be revoked.
415 * @param {int} [options.generation] **File Objects Only** Select a specific
416 * revision of this file (as opposed to the latest version, the default).
417 * @param {string} [options.userProject] The ID of the project which will be
418 * billed for the request.
419 * @param {RemoveAclCallback} callback The callback function.
420 * @returns {Promise<RemoveAclResponse>}
421 *
422 * @example
423 * ```
424 * const storage = require('@google-cloud/storage')();
425 * const myBucket = storage.bucket('my-bucket');
426 * const myFile = myBucket.file('my-file');
427 *
428 * myBucket.acl.delete({
429 * entity: 'user-useremail@example.com'
430 * }, function(err, apiResponse) {});
431 *
432 * //-
433 * // For file ACL operations, you can also specify a `generation` property.
434 * //-
435 * myFile.acl.delete({
436 * entity: 'user-useremail@example.com',
437 * generation: 1
438 * }, function(err, apiResponse) {});
439 *
440 * //-
441 * // If the callback is omitted, we'll return a Promise.
442 * //-
443 * myFile.acl.delete().then(function(data) {
444 * const apiResponse = data[0];
445 * });
446 *
447 * ```
448 * @example <caption>include:samples/acl.js</caption>
449 * region_tag:storage_remove_bucket_owner
450 * Example of removing an owner from a bucket:
451 *
452 * @example <caption>include:samples/acl.js</caption>
453 * region_tag:storage_remove_bucket_default_owner
454 * Example of removing a default owner from a bucket:
455 *
456 * @example <caption>include:samples/acl.js</caption>
457 * region_tag:storage_remove_file_owner
458 * Example of removing an owner from a bucket:
459 */
460 delete(options, callback) {
461 const query = {};
462 if (options.generation) {
463 query.generation = options.generation;
464 }
465 if (options.userProject) {
466 query.userProject = options.userProject;
467 }
468 this.request({
469 method: 'DELETE',
470 uri: '/' + encodeURIComponent(options.entity),
471 qs: query,
472 }, (err, resp) => {
473 callback(err, resp);
474 });
475 }
476 /**
477 * @typedef {array} GetAclResponse
478 * @property {object|object[]} 0 Single or array of Acl Objects.
479 * @property {object} 1 The full API response.
480 */
481 /**
482 * @callback GetAclCallback
483 * @param {?Error} err Request error, if any.
484 * @param {object|object[]} acl Single or array of Acl Objects.
485 * @param {object} apiResponse The full API response.
486 */
487 /**
488 * Get access controls on a {@link Bucket} or {@link File}. If
489 * an entity is omitted, you will receive an array of all applicable access
490 * controls.
491 *
492 * See {@link https://cloud.google.com/storage/docs/json_api/v1/bucketAccessControls/get| BucketAccessControls: get API Documentation}
493 * See {@link https://cloud.google.com/storage/docs/json_api/v1/objectAccessControls/get| ObjectAccessControls: get API Documentation}
494 *
495 * @param {object|function} [options] Configuration options. If you want to
496 * receive a list of all access controls, pass the callback function as
497 * the only argument.
498 * @param {string} options.entity Whose permissions will be fetched.
499 * @param {number} [options.generation] **File Objects Only** Select a specific
500 * revision of this file (as opposed to the latest version, the default).
501 * @param {string} [options.userProject] The ID of the project which will be
502 * billed for the request.
503 * @param {GetAclCallback} [callback] Callback function.
504 * @returns {Promise<GetAclResponse>}
505 *
506 * @example
507 * ```
508 * const storage = require('@google-cloud/storage')();
509 * const myBucket = storage.bucket('my-bucket');
510 * const myFile = myBucket.file('my-file');
511 *
512 * myBucket.acl.get({
513 * entity: 'user-useremail@example.com'
514 * }, function(err, aclObject, apiResponse) {});
515 *
516 * //-
517 * // Get all access controls.
518 * //-
519 * myBucket.acl.get(function(err, aclObjects, apiResponse) {
520 * // aclObjects = [
521 * // {
522 * // entity: 'user-useremail@example.com',
523 * // role: 'owner'
524 * // }
525 * // ]
526 * });
527 *
528 * //-
529 * // For file ACL operations, you can also specify a `generation` property.
530 * //-
531 * myFile.acl.get({
532 * entity: 'user-useremail@example.com',
533 * generation: 1
534 * }, function(err, aclObject, apiResponse) {});
535 *
536 * //-
537 * // If the callback is omitted, we'll return a Promise.
538 * //-
539 * myBucket.acl.get().then(function(data) {
540 * const aclObject = data[0];
541 * const apiResponse = data[1];
542 * });
543 *
544 * ```
545 * @example <caption>include:samples/acl.js</caption>
546 * region_tag:storage_print_file_acl
547 * Example of printing a file's ACL:
548 *
549 * @example <caption>include:samples/acl.js</caption>
550 * region_tag:storage_print_file_acl_for_user
551 * Example of printing a file's ACL for a specific user:
552 *
553 * @example <caption>include:samples/acl.js</caption>
554 * region_tag:storage_print_bucket_acl
555 * Example of printing a bucket's ACL:
556 *
557 * @example <caption>include:samples/acl.js</caption>
558 * region_tag:storage_print_bucket_acl_for_user
559 * Example of printing a bucket's ACL for a specific user:
560 */
561 get(optionsOrCallback, cb) {
562 const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : null;
563 const callback = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
564 let path = '';
565 const query = {};
566 if (options) {
567 path = '/' + encodeURIComponent(options.entity);
568 if (options.generation) {
569 query.generation = options.generation;
570 }
571 if (options.userProject) {
572 query.userProject = options.userProject;
573 }
574 }
575 this.request({
576 uri: path,
577 qs: query,
578 }, (err, resp) => {
579 if (err) {
580 callback(err, null, resp);
581 return;
582 }
583 let results;
584 if (resp.items) {
585 results = resp.items.map(this.makeAclObject_);
586 }
587 else {
588 results = this.makeAclObject_(resp);
589 }
590 callback(null, results, resp);
591 });
592 }
593 /**
594 * @typedef {array} UpdateAclResponse
595 * @property {object} 0 The updated Acl Objects.
596 * @property {object} 1 The full API response.
597 */
598 /**
599 * @callback UpdateAclCallback
600 * @param {?Error} err Request error, if any.
601 * @param {object} acl The updated Acl Objects.
602 * @param {object} apiResponse The full API response.
603 */
604 /**
605 * Update access controls on a {@link Bucket} or {@link File}.
606 *
607 * See {@link https://cloud.google.com/storage/docs/json_api/v1/bucketAccessControls/update| BucketAccessControls: update API Documentation}
608 * See {@link https://cloud.google.com/storage/docs/json_api/v1/objectAccessControls/update| ObjectAccessControls: update API Documentation}
609 *
610 * @param {object} options Configuration options.
611 * @param {string} options.entity Whose permissions will be updated.
612 * @param {string} options.role Permissions allowed for the defined entity.
613 * See {@link Storage.acl}.
614 * @param {number} [options.generation] **File Objects Only** Select a specific
615 * revision of this file (as opposed to the latest version, the default).
616 * @param {string} [options.userProject] The ID of the project which will be
617 * billed for the request.
618 * @param {UpdateAclCallback} [callback] Callback function.
619 * @returns {Promise<UpdateAclResponse>}
620 *
621 * @example
622 * ```
623 * const storage = require('@google-cloud/storage')();
624 * const myBucket = storage.bucket('my-bucket');
625 * const myFile = myBucket.file('my-file');
626 *
627 * const options = {
628 * entity: 'user-useremail@example.com',
629 * role: gcs.acl.WRITER_ROLE
630 * };
631 *
632 * myBucket.acl.update(options, function(err, aclObject, apiResponse) {});
633 *
634 * //-
635 * // For file ACL operations, you can also specify a `generation` property.
636 * //-
637 * myFile.acl.update({
638 * entity: 'user-useremail@example.com',
639 * role: gcs.acl.WRITER_ROLE,
640 * generation: 1
641 * }, function(err, aclObject, apiResponse) {});
642 *
643 * //-
644 * // If the callback is omitted, we'll return a Promise.
645 * //-
646 * myFile.acl.update(options).then(function(data) {
647 * const aclObject = data[0];
648 * const apiResponse = data[1];
649 * });
650 * ```
651 */
652 update(options, callback) {
653 const query = {};
654 if (options.generation) {
655 query.generation = options.generation;
656 }
657 if (options.userProject) {
658 query.userProject = options.userProject;
659 }
660 this.request({
661 method: 'PUT',
662 uri: '/' + encodeURIComponent(options.entity),
663 qs: query,
664 json: {
665 role: options.role.toUpperCase(),
666 },
667 }, (err, resp) => {
668 if (err) {
669 callback(err, null, resp);
670 return;
671 }
672 callback(null, this.makeAclObject_(resp), resp);
673 });
674 }
675 /**
676 * Transform API responses to a consistent object format.
677 *
678 * @private
679 */
680 makeAclObject_(accessControlObject) {
681 const obj = {
682 entity: accessControlObject.entity,
683 role: accessControlObject.role,
684 };
685 if (accessControlObject.projectTeam) {
686 obj.projectTeam = accessControlObject.projectTeam;
687 }
688 return obj;
689 }
690 /**
691 * Patch requests up to the bucket's request object.
692 *
693 * @private
694 *
695 * @param {string} method Action.
696 * @param {string} path Request path.
697 * @param {*} query Request query object.
698 * @param {*} body Request body contents.
699 * @param {function} callback Callback function.
700 */
701 request(reqOpts, callback) {
702 reqOpts.uri = this.pathPrefix + reqOpts.uri;
703 this.request_(reqOpts, callback);
704 }
705}
706/*! Developer Documentation
707 *
708 * All async methods (except for streams) will return a Promise in the event
709 * that a callback is omitted.
710 */
711promisifyAll(Acl, {
712 exclude: ['request'],
713});
714export { Acl, AclRoleAccessorMethods };