Skip to content

Commit

Permalink
Added the getStorageRuleset() API (#613)
Browse files Browse the repository at this point in the history
* Implemented the API for releasing rulesets

* Removed createRelease logic

* Added getStorageRules() API

* Removed some redundant tests
  • Loading branch information
hiranya911 authored Aug 7, 2019
1 parent d6c7af9 commit 77a26fb
Show file tree
Hide file tree
Showing 2 changed files with 124 additions and 0 deletions.
33 changes: 33 additions & 0 deletions src/security-rules/security-rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export class Ruleset implements RulesetMetadata {
export class SecurityRules implements FirebaseServiceInterface {

private static readonly CLOUD_FIRESTORE = 'cloud.firestore';
private static readonly FIREBASE_STORAGE = 'firebase.storage';

public readonly INTERNAL = new SecurityRulesInternals();

Expand Down Expand Up @@ -126,6 +127,24 @@ export class SecurityRules implements FirebaseServiceInterface {
return this.releaseRuleset(ruleset, SecurityRules.CLOUD_FIRESTORE);
}

/**
* Gets the Ruleset currently applied to a Cloud Storage bucket. Rejects with a `not-found` error if no Ruleset is
* applied on the bucket.
*
* @param {string=} bucket Optional name of the Cloud Storage bucket to be retrieved. If name is not specified,
* retrieves the ruleset applied on the default bucket configured via `AppOptions`.
* @returns {Promise<Ruleset>} A promise that fulfills with the Cloud Storage Ruleset.
*/
public getStorageRuleset(bucket?: string): Promise<Ruleset> {
return Promise.resolve()
.then(() => {
return this.getBucketName(bucket);
})
.then((bucketName) => {
return this.getRulesetForRelease(`${SecurityRules.FIREBASE_STORAGE}/${bucketName}`);
});
}

/**
* Creates a `RulesFile` with the given name and source. Throws if any of the arguments are invalid. This is a
* local operation, and does not involve any network API calls.
Expand Down Expand Up @@ -214,6 +233,20 @@ export class SecurityRules implements FirebaseServiceInterface {
return;
});
}

private getBucketName(bucket?: string): string {
const bucketName = (typeof bucket !== 'undefined') ? bucket : this.app.options.storageBucket;
if (!validator.isNonEmptyString(bucketName)) {
throw new FirebaseSecurityRulesError(
'invalid-argument',
'Bucket name not specified or invalid. Specify a default bucket name via the ' +
'storageBucket option when initializing the app, or specify the bucket name ' +
'explicitly when calling the rules API.',
);
}

return bucketName;
}
}

class SecurityRulesInternals implements FirebaseServiceInternalsInterface {
Expand Down
91 changes: 91 additions & 0 deletions test/unit/security-rules/security-rules.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,97 @@ describe('SecurityRules', () => {
const file = ruleset.source[0];
expect(file.name).equals('firestore.rules');
expect(file.content).equals('service cloud.firestore{\n}\n');

expect(getRelease).to.have.been.calledOnce.and.calledWith(
'cloud.firestore');
});
});
});

describe('getStorageRuleset', () => {
const invalidBucketNames: any[] = [null, '', true, false, 1, 0, {}, []];
const invalidBucketError = new FirebaseSecurityRulesError(
'invalid-argument',
'Bucket name not specified or invalid. Specify a default bucket name via the ' +
'storageBucket option when initializing the app, or specify the bucket name ' +
'explicitly when calling the rules API.',
);
invalidBucketNames.forEach((bucketName) => {
it(`should reject when called with: ${JSON.stringify(bucketName)}`, () => {
return securityRules.getStorageRuleset(bucketName)
.should.eventually.be.rejected.and.deep.equal(invalidBucketError);
});
});

it('should propagate API errors', () => {
const stub = sinon
.stub(SecurityRulesApiClient.prototype, 'getRelease')
.rejects(EXPECTED_ERROR);
stubs.push(stub);
return securityRules.getStorageRuleset()
.should.eventually.be.rejected.and.deep.equal(EXPECTED_ERROR);
});

it('should reject when getRelease response is invalid', () => {
const stub = sinon
.stub(SecurityRulesApiClient.prototype, 'getRelease')
.resolves({});
stubs.push(stub);

return securityRules.getStorageRuleset()
.should.eventually.be.rejected.and.have.property(
'message', 'Ruleset name not found for firebase.storage/bucketName.appspot.com.');
});

it('should resolve with Ruleset for the default bucket on success', () => {
const getRelease = sinon
.stub(SecurityRulesApiClient.prototype, 'getRelease')
.resolves({
rulesetName: 'projects/test-project/rulesets/foo',
});
const getRuleset = sinon
.stub(SecurityRulesApiClient.prototype, 'getRuleset')
.resolves(FIRESTORE_RULESET_RESPONSE);
stubs.push(getRelease, getRuleset);

return securityRules.getStorageRuleset()
.then((ruleset) => {
expect(ruleset.name).to.equal('foo');
expect(ruleset.createTime).to.equal(CREATE_TIME_UTC);
expect(ruleset.source.length).to.equal(1);

const file = ruleset.source[0];
expect(file.name).equals('firestore.rules');
expect(file.content).equals('service cloud.firestore{\n}\n');

expect(getRelease).to.have.been.calledOnce.and.calledWith(
'firebase.storage/bucketName.appspot.com');
});
});

it('should resolve with Ruleset for the specified bucket on success', () => {
const getRelease = sinon
.stub(SecurityRulesApiClient.prototype, 'getRelease')
.resolves({
rulesetName: 'projects/test-project/rulesets/foo',
});
const getRuleset = sinon
.stub(SecurityRulesApiClient.prototype, 'getRuleset')
.resolves(FIRESTORE_RULESET_RESPONSE);
stubs.push(getRelease, getRuleset);

return securityRules.getStorageRuleset('other.appspot.com')
.then((ruleset) => {
expect(ruleset.name).to.equal('foo');
expect(ruleset.createTime).to.equal(CREATE_TIME_UTC);
expect(ruleset.source.length).to.equal(1);

const file = ruleset.source[0];
expect(file.name).equals('firestore.rules');
expect(file.content).equals('service cloud.firestore{\n}\n');

expect(getRelease).to.have.been.calledOnce.and.calledWith(
'firebase.storage/other.appspot.com');
});
});
});
Expand Down

0 comments on commit 77a26fb

Please sign in to comment.