Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions docgen/content-sources/node/toc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,20 @@ toc:
- title: "ShaCertificate"
path: /docs/reference/admin/node/admin.projectManagement.ShaCertificate

- title: "admin.securityRules"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll defer to you and your other reviewers here, but we use FirebaseRules in the same way we use FirebaseAuth and FirebaseDatabase. The extra "security" word here breaks conventions. ((Consider: what the object will be called in the Java admin SDK?))

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Current naming convention used is: admin.securityRules(), SecurityRules, (Java: FirebaseSecurityRules).

It sounds like you're suggesting: admin.rules(), Rules, (Java: FirebaseRules)

Is that correct? I don't have preference. Let's see what @rachelmyers and others think. The existing documentation uses "Firebase Security Rules" pretty much everywhere: https://firebase.google.com/docs/rules

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a slight preference for admin.securityRules(), but since I think people will understand either, it's not a strong preference.

path: /docs/reference/admin/node/admin.securityRules
section:
- title: "RulesFile"
path: /docs/reference/admin/node/admin.securityRules.RulesFile
- title: "Ruleset"
path: /docs/reference/admin/node/admin.securityRules.Ruleset
- title: "RulesetMetadata"
path: /docs/reference/admin/node/admin.securityRules.RulesetMetadata
- title: "RulesetMetadataList"
path: /docs/reference/admin/node/admin.securityRules.RulesetMetadataList
- title: "SecurityRules"
path: /docs/reference/admin/node/admin.securityRules.SecurityRules

- title: "admin.storage"
path: /docs/reference/admin/node/admin.storage
section:
Expand Down
14 changes: 14 additions & 0 deletions src/firebase-app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {Firestore} from '@google-cloud/firestore';
import {FirestoreService} from './firestore/firestore';
import {InstanceId} from './instance-id/instance-id';
import {ProjectManagement} from './project-management/project-management';
import {SecurityRules} from './security-rules/security-rules';

import {Agent} from 'http';

Expand Down Expand Up @@ -366,6 +367,19 @@ export class FirebaseApp {
});
}

/**
* Returns the SecurityRules service instance associated with this app.
*
* @return {SecurityRules} The SecurityRules service instance of this app.
*/
public securityRules(): SecurityRules {
return this.ensureService_('security-rules', () => {
const securityRulesService: typeof SecurityRules =
require('./security-rules/security-rules').SecurityRules;
return new securityRulesService(this);
});
}

/**
* Returns the name of the FirebaseApp instance.
*
Expand Down
13 changes: 13 additions & 0 deletions src/firebase-namespace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {Database} from '@firebase/database';
import {Firestore} from '@google-cloud/firestore';
import {InstanceId} from './instance-id/instance-id';
import {ProjectManagement} from './project-management/project-management';
import { SecurityRules } from './security-rules/security-rules';

import * as validator from './utils/validator';

Expand Down Expand Up @@ -419,6 +420,18 @@ export class FirebaseNamespace {
return Object.assign(fn, {ProjectManagement: projectManagement});
}

/**
* Gets the `SecurityRules` service namespace. The returned namespace can be used to get the
* `SecurityRules` service for the default app or an explicitly specified app.
*/
get securityRules(): FirebaseServiceNamespace<SecurityRules> {
const fn: FirebaseServiceNamespace<SecurityRules> = (app?: FirebaseApp) => {
return this.ensureApp(app).securityRules();
};
const securityRules = require('./security-rules/security-rules').SecurityRules;
return Object.assign(fn, {SecurityRules: securityRules});
}

/**
* Initializes the FirebaseApp instance.
*
Expand Down
222 changes: 222 additions & 0 deletions src/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,37 @@ declare namespace admin {
* `ProjectManagement` service associated with the provided app.
*/
function projectManagement(app?: admin.app.App): admin.projectManagement.ProjectManagement;

/**
* Gets the {@link admin.securityRules.SecurityRules
* `SecurityRules`} service for the default app or a given app.
*
* `admin.securityRules()` can be called with no arguments to access the
* default app's {@link admin.securityRules.SecurityRules
* `SecurityRules`} service, or as `admin.securityRules(app)` to access
* the {@link admin.securityRules.SecurityRules `SecurityRules`}
* service associated with a specific app.
*
* @example
* ```javascript
* // Get the SecurityRules service for the default app
* var defaultSecurityRules = admin.securityRules();
* ```
*
* @example
* ```javascript
* // Get the SecurityRules service for a given app
* var otherSecurityRules = admin.securityRules(otherApp);
* ```
*
* @param app Optional app to return the `SecurityRules` service
* for. If not provided, the default `SecurityRules` service will
* be returned.
* @return The default `SecurityRules` service if no app is provided, or the
* `SecurityRules` service associated with the provided app.
*/
function securityRules(app?: admin.app.App): admin.securityRules.SecurityRules;

function initializeApp(options?: admin.AppOptions, name?: string): admin.app.App;
}

Expand Down Expand Up @@ -423,6 +454,7 @@ declare namespace admin.app {
instanceId(): admin.instanceId.InstanceId;
messaging(): admin.messaging.Messaging;
projectManagement(): admin.projectManagement.ProjectManagement;
securityRules(): admin.securityRules.SecurityRules;
storage(): admin.storage.Storage;

/**
Expand Down Expand Up @@ -5169,6 +5201,196 @@ declare namespace admin.projectManagement {
}
}

declare namespace admin.securityRules {
/**
* A source file containing some Firebase security rules. The content includes raw
* source code including text formatting, indentation and comments. Use the
* [`securityRules.createRulesFileFromSource()`](admin.securityRules.SecurityRules#createRulesFileFromSource)
* method to create new instances of this type.
*/
interface RulesFile {
readonly name: string;
readonly content: string;
}

/**
* Required metadata associated with a ruleset.
*/
interface RulesetMetadata {
/**
* Name of the `Ruleset` as a short string. This can be directly passed into APIs
* like [`securityRules.getRuleset()`](admin.securityRules.SecurityRules#getRuleset)
* and [`securityRules.deleteRuleset()`](admin.securityRules.SecurityRules#deleteRuleset).
*/
readonly name: string;

/**
* Creation time of the `Ruleset` as a UTC timestamp string.
*/
readonly createTime: string;
}

/**
* A set of Firebase security rules.
*/
interface Ruleset extends RulesetMetadata {
readonly source: RulesFile[];
}

interface RulesetMetadataList {
/**
* A batch of ruleset metadata.
*/
readonly rulesets: RulesetMetadata[];

/**
* The next page token if available. This is needed to retrieve the next batch.
*/
readonly nextPageToken?: string;
}

/**
* The Firebase `SecurityRules` service interface.
*
* Do not call this constructor directly. Instead, use
* [`admin.securityRules()`](admin.securityRules#securityRules).
*/
interface SecurityRules {
app: admin.app.App;

/**
* Creates a {@link admin.securityRules.RulesFile `RuleFile`} with the given name
* and source. Throws an error if any of the arguments are invalid. This is a local
* operation, and does not involve any network API calls.
*
* @example
* ```javascript
* const source = '// Some rules source';
* const rulesFile = admin.securityRules().createRulesFileFromSource(
* 'firestore.rules', source);
* ```
*
* @param name Name to assign to the rules file. This is usually a short file name that
* helps identify the file in a ruleset.
* @param source Contents of the rules file.
* @return A new rules file instance.
*/
createRulesFileFromSource(name: string, source: string | Buffer): RulesFile;

/**
* Creates a new {@link admin.securityRules.Ruleset `Ruleset`} from the given
* {@link admin.securityRules.RulesFile `RuleFile`}.
*
* @param file Rules file to include in the new `Ruleset`.
* @returns A promise that fulfills with the newly created `Ruleset`.
*/
createRuleset(file: RulesFile): Promise<Ruleset>;
Copy link

@rachelmyers rachelmyers Aug 23, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was createRuleset meant to allow multiple files, so createRuleset(files...: RulesFile): Promise<Ruleset>, instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In #607 we decided to only accept one RulesFile in this API (since the backend impl currently doesn't allow multiple files). In the future we can change this into a varargs if necessary, as a non-breaking change.


/**
* Gets the {@link admin.securityRules.Ruleset `Ruleset`} identified by the given
* name. The input name should be the short name string without the project ID
* prefix. For example, to retrieve the `projects/project-id/rulesets/my-ruleset`,
* pass the short name "my-ruleset". Rejects with a `not-found` error if the
* specified `Ruleset` cannot be found.
*
* @param name Name of the `Ruleset` to retrieve.
* @return A promise that fulfills with the specified `Ruleset`.
*/
getRuleset(name: string): Promise<Ruleset>;

/**
* Deletes the {@link admin.securityRules.Ruleset `Ruleset`} identified by the given
* name. The input name should be the short name string without the project ID
* prefix. For example, to delete the `projects/project-id/rulesets/my-ruleset`,
* pass the short name "my-ruleset". Rejects with a `not-found` error if the
* specified `Ruleset` cannot be found.
*
* @param name Name of the `Ruleset` to delete.
* @return A promise that fulfills when the `Ruleset` is deleted.
*/
deleteRuleset(name: string): Promise<void>;

/**
* Retrieves a page of ruleset metadata.
*
* @param pageSize The page size, 100 if undefined. This is also the maximum allowed
* limit.
* @param nextPageToken The next page token. If not specified, returns rulesets
* starting without any offset.
* @return A promise that fulfills a page of rulesets.
*/
listRulesetMetadata(
pageSize?: number, nextPageToken?: string): Promise<RulesetMetadataList>;

/**
* Gets the {@link admin.securityRules.Ruleset `Ruleset`} currently applied to
* Cloud Firestore. Rejects with a `not-found` error if no ruleset is applied
* on Firestore.
*
* @return A promise that fulfills with the Firestore ruleset.
*/
getFirestoreRuleset(): Promise<Ruleset>;

/**
* Creates a new {@link admin.securityRules.Ruleset `Ruleset`} from the given
* source, and applies it to Cloud Firestore.
*
* @param source Rules source to apply.
* @return A promise that fulfills when the ruleset is created and released.
*/
releaseFirestoreRulesetFromSource(source: string | Buffer): Promise<Ruleset>;

/**
* Applies the specified {@link admin.securityRules.Ruleset `Ruleset`} ruleset
* to Cloud Firestore.
*
* @param ruleset Name of the ruleset to apply or a `RulesetMetadata` object
* containing the name.
* @return A promise that fulfills when the ruleset is released.
*/
releaseFirestoreRuleset(ruleset: string | RulesetMetadata): Promise<void>;

/**
* Gets the {@link admin.securityRules.Ruleset `Ruleset`} currently applied to a
* Cloud Storage bucket. Rejects with a `not-found` error if no ruleset is applied
* on the bucket.
*
* @param bucket Optional name of the Cloud Storage bucket to be retrieved. If not
* specified, retrieves the ruleset applied on the default bucket configured via
* `AppOptions`.
* @return A promise that fulfills with the Cloud Storage ruleset.
*/
getStorageRuleset(bucket?: string): Promise<Ruleset>;

/**
* Creates a new {@link admin.securityRules.Ruleset `Ruleset`} from the given
* source, and applies it to a Cloud Storage bucket.
*
* @param source Rules source to apply.
* @param bucket Optional name of the Cloud Storage bucket to apply the rules on. If
* not specified, applies the ruleset on the default bucket configured via
* {@link admin.AppOptions `AppOptions`}.
* @return A promise that fulfills when the ruleset is created and released.
*/
releaseStorageRulesetFromSource(
source: string | Buffer, bucket?: string): Promise<Ruleset>;

/**
* Applies the specified {@link admin.securityRules.Ruleset `Ruleset`} ruleset
* to a Cloud Storage bucket.
*
* @param ruleset Name of the ruleset to apply or a `RulesetMetadata` object
* containing the name.
* @param bucket Optional name of the Cloud Storage bucket to apply the rules on. If
* not specified, applies the ruleset on the default bucket configured via
* {@link admin.AppOptions `AppOptions`}.
* @return A promise that fulfills when the ruleset is released.
*/
releaseStorageRuleset(
ruleset: string | RulesetMetadata, bucket?: string): Promise<void>;
}
}

declare module 'firebase-admin' {
}

Expand Down
27 changes: 27 additions & 0 deletions test/unit/firebase-app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {Firestore} from '@google-cloud/firestore';
import {Database} from '@firebase/database';
import {InstanceId} from '../../src/instance-id/instance-id';
import {ProjectManagement} from '../../src/project-management/project-management';
import { SecurityRules } from '../../src/security-rules/security-rules';
import { FirebaseAppError, AppErrorCodes } from '../../src/utils/error';

chai.should();
Expand Down Expand Up @@ -584,6 +585,32 @@ describe('FirebaseApp', () => {
});
});

describe('securityRules()', () => {
it('should throw if the app has already been deleted', () => {
const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName);

return app.delete().then(() => {
expect(() => {
return app.securityRules();
}).to.throw(`Firebase app named "${mocks.appName}" has already been deleted.`);
});
});

it('should return the securityRules client', () => {
const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName);

const securityRules: SecurityRules = app.securityRules();
expect(securityRules).to.not.be.null;
});

it('should return a cached version of SecurityRules on subsequent calls', () => {
const app = firebaseNamespace.initializeApp(mocks.appOptions, mocks.appName);
const service1: SecurityRules = app.securityRules();
const service2: SecurityRules = app.securityRules();
expect(service1).to.equal(service2);
});
});

describe('#[service]()', () => {
it('should throw if the app has already been deleted', () => {
firebaseNamespace.INTERNAL.registerService(mocks.serviceName, mockServiceFactory);
Expand Down
Loading