or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

access-control.mdauthentication.mdbucket-operations.mdfile-operations.mdindex.mdnotifications.mdstorage-client.mdtransfer-manager.mdutilities.md
tile.json

access-control.mddocs/

Access Control

Google Cloud Storage provides two access control systems: Access Control Lists (ACLs) for fine-grained permissions and Identity and Access Management (IAM) for resource-level permissions.

ACL (Access Control Lists)

Acl Class

class Acl {
  constructor(options: AclOptions);
  
  // Properties
  pathPrefix: string;
  
  // Dynamic role accessors
  owners: AclRoleAccessors;
  readers: AclRoleAccessors;
  writers: AclRoleAccessors;
  
  // Core methods
  add(options: AddAclOptions): Promise<AddAclResponse>;
  get(options: GetAclOptions): Promise<GetAclResponse>;
  update(options: UpdateAclOptions): Promise<UpdateAclResponse>;
  remove(options: RemoveAclOptions): Promise<RemoveAclResponse>;
}

interface AclOptions {
  pathPrefix: string;
  request: (reqOpts: object, callback: Function) => void;
}

ACL Types and Interfaces

interface AclMetadata {
  entity?: string;
  role?: 'OWNER' | 'READER' | 'WRITER' | 'FULL_CONTROL';
  projectTeam?: {
    projectNumber?: string;
    team?: 'editors' | 'owners' | 'viewers';
  };
}

interface AccessControlObject {
  entity: string;
  role: string;
  projectTeam?: string;
}

// Entity types
type AclEntity = 
  | `user-${string}`           // user-email@example.com
  | `group-${string}`          // group-groupname@domain.com
  | `domain-${string}`         // domain-example.com
  | `project-editors-${string}` // project-editors-123456789
  | `project-owners-${string}`  // project-owners-123456789
  | `project-viewers-${string}` // project-viewers-123456789
  | 'allUsers'                 // All users (public)
  | 'allAuthenticatedUsers';   // All authenticated users

// Predefined ACL values
type PredefinedAcl = 
  | 'authenticatedRead'
  | 'bucketOwnerFullControl'
  | 'bucketOwnerRead'
  | 'private'
  | 'projectPrivate'
  | 'publicRead'
  | 'publicReadWrite';

ACL Operations

add(options: AddAclOptions): Promise<AddAclResponse>
get(options?: GetAclOptions): Promise<GetAclResponse>
update(options: UpdateAclOptions): Promise<UpdateAclResponse>
remove(options: RemoveAclOptions): Promise<RemoveAclResponse>

interface AddAclOptions {
  entity: string;
  role: string;
  generation?: number;
  userProject?: string;
}

interface GetAclOptions {
  entity?: string;
  generation?: number;
  userProject?: string;
}

interface UpdateAclOptions {
  entity: string;
  role: string;
  generation?: number;
  userProject?: string;
}

interface RemoveAclOptions {
  entity: string;
  generation?: number;
  userProject?: string;
}

type AddAclResponse = [AccessControlObject, unknown]; // [acl, apiResponse]
type GetAclResponse = [AccessControlObject[], unknown]; // [acls, apiResponse]
type UpdateAclResponse = [AccessControlObject, unknown]; // [acl, apiResponse]  
type RemoveAclResponse = [unknown]; // [apiResponse]

// Add ACL entry
await bucket.acl.add({
  entity: 'user-john@example.com',
  role: 'READER'
});

// Add group access
await file.acl.add({
  entity: 'group-developers@company.com',
  role: 'WRITER'
});

// Add domain access
await bucket.acl.add({
  entity: 'domain-example.com',
  role: 'READER'
});

// Add project team access
await bucket.acl.add({
  entity: 'project-editors-123456789',
  role: 'WRITER'
});

// Make public
await file.acl.add({
  entity: 'allUsers',
  role: 'READER'
});

// Get all ACL entries
const [acls] = await bucket.acl.get();
acls.forEach(acl => {
  console.log(`Entity: ${acl.entity}, Role: ${acl.role}`);
});

// Get specific entity ACL
const [acls] = await file.acl.get({
  entity: 'user-john@example.com'
});

// Update ACL entry
await bucket.acl.update({
  entity: 'user-john@example.com',
  role: 'OWNER'
});

// Remove ACL entry
await file.acl.remove({
  entity: 'user-john@example.com'
});

Role-based ACL Methods

Each role (owners, readers, writers) provides convenient methods:

interface AclRoleAccessors {
  addUser(entity: string): Promise<AddAclResponse>;
  deleteUser(entity: string): Promise<RemoveAclResponse>;
  addGroup(entity: string): Promise<AddAclResponse>;
  deleteGroup(entity: string): Promise<RemoveAclResponse>;
  addDomain(entity: string): Promise<AddAclResponse>;
  deleteDomain(entity: string): Promise<RemoveAclResponse>;
  addProject(entity: string): Promise<AddAclResponse>;
  deleteProject(entity: string): Promise<RemoveAclResponse>;
  addAllUsers(): Promise<AddAclResponse>;
  deleteAllUsers(): Promise<RemoveAclResponse>;
  addAllAuthenticatedUsers(): Promise<AddAclResponse>;
  deleteAllAuthenticatedUsers(): Promise<RemoveAclResponse>;
}

// Owners (OWNER role)
await bucket.acl.owners.addUser('owner@example.com');
await bucket.acl.owners.addGroup('owners@company.com');
await bucket.acl.owners.deleteUser('former-owner@example.com');

// Readers (READER role)  
await file.acl.readers.addUser('reader@example.com');
await file.acl.readers.addDomain('partner.com');
await file.acl.readers.addAllUsers(); // Make public read
await file.acl.readers.deleteAllUsers(); // Remove public access

// Writers (WRITER role)
await bucket.acl.writers.addUser('editor@example.com');
await bucket.acl.writers.addProject('project-editors-123456789');
await bucket.acl.writers.deleteGroup('old-editors@company.com');

// Authenticated users access
await file.acl.readers.addAllAuthenticatedUsers();
await file.acl.readers.deleteAllAuthenticatedUsers();

Bucket vs File ACLs

// Bucket ACLs control bucket-level permissions
const bucket = storage.bucket('my-bucket');

// Who can list files in the bucket
await bucket.acl.readers.addUser('lister@example.com');

// Who can add/delete files in the bucket
await bucket.acl.writers.addGroup('editors@company.com');

// Who has full control over the bucket
await bucket.acl.owners.addUser('admin@example.com');

// File ACLs control individual file permissions
const file = bucket.file('document.pdf');

// Who can read this specific file
await file.acl.readers.addUser('viewer@example.com');

// Who can overwrite this specific file
await file.acl.writers.addUser('editor@example.com');

IAM (Identity and Access Management)

Iam Class

class Iam {
  constructor(bucket: Bucket);
  
  // Methods
  getPolicy(options?: GetPolicyOptions): Promise<GetPolicyResponse>;
  setPolicy(policy: Policy, options?: SetPolicyOptions): Promise<SetPolicyResponse>;
  testIamPermissions(permissions: string | string[], options?: TestIamPermissionsOptions): Promise<TestIamPermissionsResponse>;
}

IAM Types

interface Policy {
  bindings?: PolicyBinding[];
  etag?: string;
  version?: number;
}

interface PolicyBinding {
  role?: string;
  members?: string[];
  condition?: Expr;
}

interface Expr {
  expression?: string;
  title?: string;
  description?: string;
  location?: string;
}

// Common IAM roles for Cloud Storage
type StorageRole = 
  | 'roles/storage.admin'
  | 'roles/storage.objectAdmin'
  | 'roles/storage.objectCreator'
  | 'roles/storage.objectViewer'
  | 'roles/storage.legacyBucketOwner'
  | 'roles/storage.legacyBucketReader'
  | 'roles/storage.legacyBucketWriter'
  | 'roles/storage.legacyObjectOwner'
  | 'roles/storage.legacyObjectReader';

// Member types
type IamMember = 
  | `user:${string}`                    // user:john@example.com
  | `group:${string}`                   // group:team@company.com
  | `serviceAccount:${string}`          // serviceAccount:service@project.iam.gserviceaccount.com
  | `domain:${string}`                  // domain:example.com
  | `projectEditor:${string}`           // projectEditor:project-id
  | `projectOwner:${string}`            // projectOwner:project-id
  | `projectViewer:${string}`           // projectViewer:project-id
  | 'allUsers'                          // All users
  | 'allAuthenticatedUsers';            // All authenticated users

IAM Policy Operations

getPolicy(options?: GetPolicyOptions): Promise<GetPolicyResponse>
setPolicy(policy: Policy, options?: SetPolicyOptions): Promise<SetPolicyResponse>
testIamPermissions(permissions: string | string[], options?: TestIamPermissionsOptions): Promise<TestIamPermissionsResponse>

interface GetPolicyOptions {
  userProject?: string;
  requestedPolicyVersion?: number;
}

interface SetPolicyOptions {
  userProject?: string;
}

interface TestIamPermissionsOptions {
  userProject?: string;
}

type GetPolicyResponse = [Policy, unknown]; // [policy, apiResponse]
type SetPolicyResponse = [Policy, unknown]; // [policy, apiResponse]
type TestIamPermissionsResponse = [string[], unknown]; // [permissions, apiResponse]

// Get current IAM policy
const [policy] = await bucket.iam.getPolicy();
console.log('Current policy:', policy);

// Create a new policy
const newPolicy: Policy = {
  bindings: [
    {
      role: 'roles/storage.objectViewer',
      members: [
        'user:viewer@example.com',
        'group:readers@company.com'
      ]
    },
    {
      role: 'roles/storage.objectAdmin',
      members: [
        'user:admin@example.com',
        'serviceAccount:app@project.iam.gserviceaccount.com'
      ]
    }
  ]
};

// Set the policy
await bucket.iam.setPolicy(newPolicy);

// Test permissions
const [permissions] = await bucket.iam.testIamPermissions([
  'storage.objects.get',
  'storage.objects.create',
  'storage.objects.delete'
]);

console.log('Allowed permissions:', permissions);

IAM Policy Management

// Add a binding to existing policy
const [currentPolicy] = await bucket.iam.getPolicy();

// Find existing binding or create new one
let binding = currentPolicy.bindings?.find(b => b.role === 'roles/storage.objectViewer');
if (!binding) {
  binding = {
    role: 'roles/storage.objectViewer',
    members: []
  };
  currentPolicy.bindings = currentPolicy.bindings || [];
  currentPolicy.bindings.push(binding);
}

// Add member to binding
if (!binding.members?.includes('user:newuser@example.com')) {
  binding.members = binding.members || [];
  binding.members.push('user:newuser@example.com');
}

// Update policy
await bucket.iam.setPolicy(currentPolicy);

// Remove a member
const updatedBindings = currentPolicy.bindings?.map(binding => {
  if (binding.role === 'roles/storage.objectViewer') {
    binding.members = binding.members?.filter(member => 
      member !== 'user:olduser@example.com'
    );
  }
  return binding;
}).filter(binding => binding.members && binding.members.length > 0);

await bucket.iam.setPolicy({
  ...currentPolicy,
  bindings: updatedBindings
});

Conditional IAM Policies

// Policy with conditions
const conditionalPolicy: Policy = {
  bindings: [
    {
      role: 'roles/storage.objectViewer',
      members: ['user:contractor@example.com'],
      condition: {
        title: 'Temporary Access',
        description: 'Access expires at end of project',
        expression: 'request.time < timestamp("2024-12-31T23:59:59Z")'
      }
    },
    {
      role: 'roles/storage.objectCreator',
      members: ['user:uploader@example.com'],
      condition: {
        title: 'Upload to specific prefix',
        description: 'Can only upload to uploads/ prefix',
        expression: 'resource.name.startsWith("projects/_/buckets/my-bucket/objects/uploads/")'
      }
    }
  ]
};

await bucket.iam.setPolicy(conditionalPolicy);

// Time-based access
const timeBasedPolicy: Policy = {
  bindings: [
    {
      role: 'roles/storage.objectViewer',
      members: ['user:timeuser@example.com'],
      condition: {
        expression: `
          request.time.getHours() >= 9 && 
          request.time.getHours() <= 17 &&
          request.time.getDayOfWeek() >= 2 && 
          request.time.getDayOfWeek() <= 6
        `.replace(/\s+/g, ' ').trim()
      }
    }
  ]
};

Common Access Control Patterns

Public Access

// Make bucket public for reading
await bucket.acl.readers.addAllUsers();

// Make specific file public
await file.acl.readers.addAllUsers();

// Public read using IAM
await bucket.iam.setPolicy({
  bindings: [
    {
      role: 'roles/storage.objectViewer',
      members: ['allUsers']
    }
  ]
});

// Using predefined ACLs
await bucket.upload('/local/file.txt', {
  predefinedAcl: 'publicRead'
});

Authenticated Users Only

// Authenticated users can read via ACL
await bucket.acl.readers.addAllAuthenticatedUsers();

// Authenticated users via IAM
await bucket.iam.setPolicy({
  bindings: [
    {
      role: 'roles/storage.objectViewer', 
      members: ['allAuthenticatedUsers']
    }
  ]
});

Team Access

// Team access via groups
await bucket.acl.readers.addGroup('team-viewers@company.com');
await bucket.acl.writers.addGroup('team-editors@company.com');
await bucket.acl.owners.addGroup('team-admins@company.com');

// Team access via IAM
await bucket.iam.setPolicy({
  bindings: [
    {
      role: 'roles/storage.objectViewer',
      members: [
        'group:team-viewers@company.com',
        'group:all-employees@company.com'
      ]
    },
    {
      role: 'roles/storage.objectCreator',
      members: [
        'group:team-editors@company.com'
      ]
    },
    {
      role: 'roles/storage.admin',
      members: [
        'group:team-admins@company.com'
      ]
    }
  ]
});

Service Account Access

// Service account access via ACL
await bucket.acl.writers.addUser('service@project.iam.gserviceaccount.com');

// Service account access via IAM
await bucket.iam.setPolicy({
  bindings: [
    {
      role: 'roles/storage.objectAdmin',
      members: [
        'serviceAccount:app-backend@project.iam.gserviceaccount.com',
        'serviceAccount:data-processor@project.iam.gserviceaccount.com'
      ]
    }
  ]
});

Project Team Access

// Project team access via ACL
await bucket.acl.readers.addProject('project-viewers-123456789');
await bucket.acl.writers.addProject('project-editors-123456789');

// Project team access via IAM  
await bucket.iam.setPolicy({
  bindings: [
    {
      role: 'roles/storage.objectViewer',
      members: ['projectViewer:my-project-id']
    },
    {
      role: 'roles/storage.objectCreator', 
      members: ['projectEditor:my-project-id']
    },
    {
      role: 'roles/storage.admin',
      members: ['projectOwner:my-project-id']
    }
  ]
});

ACL vs IAM Comparison

When to Use ACLs

// Use ACLs for:
// - Fine-grained, object-level permissions
// - Simple read/write/owner permissions
// - Legacy applications expecting ACL semantics
// - When you need different permissions per file

// Example: Different access per file
await publicFile.acl.readers.addAllUsers();
await privateFile.acl.readers.addUser('specific-user@example.com');
await internalFile.acl.readers.addGroup('internal@company.com');

When to Use IAM

// Use IAM for:
// - Resource-level permissions
// - Complex conditions and constraints
// - Integration with Google Cloud IAM
// - Centralized access management

// Example: Bucket-wide policies
await bucket.iam.setPolicy({
  bindings: [
    {
      role: 'roles/storage.objectViewer',
      members: ['group:all-employees@company.com']
    },
    {
      role: 'roles/storage.objectCreator',
      members: ['group:content-creators@company.com'],
      condition: {
        expression: 'request.time.getHours() >= 9 && request.time.getHours() <= 17'
      }
    }
  ]
});

Error Handling

import { ApiError } from '@google-cloud/storage';

try {
  await bucket.acl.add({
    entity: 'user-invalid@example.com',
    role: 'READER'
  });
} catch (error) {
  if (error instanceof ApiError) {
    if (error.code === 400) {
      console.log('Invalid entity or role');
    } else if (error.code === 403) {
      console.log('Permission denied');
    } else {
      console.error(`ACL Error ${error.code}: ${error.message}`);
    }
  }
}

// Test permissions before operations
const [permissions] = await bucket.iam.testIamPermissions([
  'storage.objects.create'
]);

if (permissions.includes('storage.objects.create')) {
  // User can create objects
  await bucket.upload('/local/file.txt');
} else {
  console.log('User cannot create objects in this bucket');
}

Callback Support

// Promise pattern (recommended)
const [acls] = await bucket.acl.get();

// Callback pattern
bucket.acl.get((err, acls, apiResponse) => {
  if (err) {
    console.error('Error:', err);
    return;
  }
  console.log('ACL entries:', acls);
});

// Callback types
interface GetAclCallback {
  (err: Error | null, acls?: AccessControlObject[], apiResponse?: unknown): void;
}

interface AddAclCallback {
  (err: Error | null, acl?: AccessControlObject, apiResponse?: unknown): void;
}

interface GetPolicyCallback {
  (err: Error | null, policy?: Policy, apiResponse?: unknown): void;
}

interface SetPolicyCallback {
  (err: Error | null, policy?: Policy, apiResponse?: unknown): void;
}