security-rules.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. /*! firebase-admin v12.1.1 */
  2. "use strict";
  3. /*!
  4. * Copyright 2019 Google Inc.
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. Object.defineProperty(exports, "__esModule", { value: true });
  19. exports.SecurityRules = exports.Ruleset = exports.RulesetMetadataList = void 0;
  20. const validator = require("../utils/validator");
  21. const security_rules_api_client_internal_1 = require("./security-rules-api-client-internal");
  22. const security_rules_internal_1 = require("./security-rules-internal");
  23. /**
  24. * A page of ruleset metadata.
  25. */
  26. class RulesetMetadataList {
  27. /**
  28. * @internal
  29. */
  30. constructor(response) {
  31. if (!validator.isNonNullObject(response) || !validator.isArray(response.rulesets)) {
  32. throw new security_rules_internal_1.FirebaseSecurityRulesError('invalid-argument', `Invalid ListRulesets response: ${JSON.stringify(response)}`);
  33. }
  34. this.rulesets = response.rulesets.map((rs) => {
  35. return {
  36. name: stripProjectIdPrefix(rs.name),
  37. createTime: new Date(rs.createTime).toUTCString(),
  38. };
  39. });
  40. if (response.nextPageToken) {
  41. this.nextPageToken = response.nextPageToken;
  42. }
  43. }
  44. }
  45. exports.RulesetMetadataList = RulesetMetadataList;
  46. /**
  47. * A set of Firebase security rules.
  48. */
  49. class Ruleset {
  50. /**
  51. * @internal
  52. */
  53. constructor(ruleset) {
  54. if (!validator.isNonNullObject(ruleset) ||
  55. !validator.isNonEmptyString(ruleset.name) ||
  56. !validator.isNonEmptyString(ruleset.createTime) ||
  57. !validator.isNonNullObject(ruleset.source)) {
  58. throw new security_rules_internal_1.FirebaseSecurityRulesError('invalid-argument', `Invalid Ruleset response: ${JSON.stringify(ruleset)}`);
  59. }
  60. this.name = stripProjectIdPrefix(ruleset.name);
  61. this.createTime = new Date(ruleset.createTime).toUTCString();
  62. this.source = ruleset.source.files || [];
  63. }
  64. }
  65. exports.Ruleset = Ruleset;
  66. /**
  67. * The Firebase `SecurityRules` service interface.
  68. */
  69. class SecurityRules {
  70. /**
  71. * @param app - The app for this SecurityRules service.
  72. * @constructor
  73. * @internal
  74. */
  75. constructor(app) {
  76. this.app = app;
  77. this.client = new security_rules_api_client_internal_1.SecurityRulesApiClient(app);
  78. }
  79. /**
  80. * Gets the {@link Ruleset} identified by the given
  81. * name. The input name should be the short name string without the project ID
  82. * prefix. For example, to retrieve the `projects/project-id/rulesets/my-ruleset`,
  83. * pass the short name "my-ruleset". Rejects with a `not-found` error if the
  84. * specified `Ruleset` cannot be found.
  85. *
  86. * @param name - Name of the `Ruleset` to retrieve.
  87. * @returns A promise that fulfills with the specified `Ruleset`.
  88. */
  89. getRuleset(name) {
  90. return this.client.getRuleset(name)
  91. .then((rulesetResponse) => {
  92. return new Ruleset(rulesetResponse);
  93. });
  94. }
  95. /**
  96. * Gets the {@link Ruleset} currently applied to
  97. * Cloud Firestore. Rejects with a `not-found` error if no ruleset is applied
  98. * on Firestore.
  99. *
  100. * @returns A promise that fulfills with the Firestore ruleset.
  101. */
  102. getFirestoreRuleset() {
  103. return this.getRulesetForRelease(SecurityRules.CLOUD_FIRESTORE);
  104. }
  105. /**
  106. * Creates a new {@link Ruleset} from the given
  107. * source, and applies it to Cloud Firestore.
  108. *
  109. * @param source - Rules source to apply.
  110. * @returns A promise that fulfills when the ruleset is created and released.
  111. */
  112. releaseFirestoreRulesetFromSource(source) {
  113. return Promise.resolve()
  114. .then(() => {
  115. const rulesFile = this.createRulesFileFromSource('firestore.rules', source);
  116. return this.createRuleset(rulesFile);
  117. })
  118. .then((ruleset) => {
  119. return this.releaseFirestoreRuleset(ruleset)
  120. .then(() => {
  121. return ruleset;
  122. });
  123. });
  124. }
  125. /**
  126. * Applies the specified {@link Ruleset} ruleset
  127. * to Cloud Firestore.
  128. *
  129. * @param ruleset - Name of the ruleset to apply or a `RulesetMetadata` object
  130. * containing the name.
  131. * @returns A promise that fulfills when the ruleset is released.
  132. */
  133. releaseFirestoreRuleset(ruleset) {
  134. return this.releaseRuleset(ruleset, SecurityRules.CLOUD_FIRESTORE);
  135. }
  136. /**
  137. * Gets the {@link Ruleset} currently applied to a
  138. * Cloud Storage bucket. Rejects with a `not-found` error if no ruleset is applied
  139. * on the bucket.
  140. *
  141. * @param bucket - Optional name of the Cloud Storage bucket to be retrieved. If not
  142. * specified, retrieves the ruleset applied on the default bucket configured via
  143. * `AppOptions`.
  144. * @returns A promise that fulfills with the Cloud Storage ruleset.
  145. */
  146. getStorageRuleset(bucket) {
  147. return Promise.resolve()
  148. .then(() => {
  149. return this.getBucketName(bucket);
  150. })
  151. .then((bucketName) => {
  152. return this.getRulesetForRelease(`${SecurityRules.FIREBASE_STORAGE}/${bucketName}`);
  153. });
  154. }
  155. /**
  156. * Creates a new {@link Ruleset} from the given
  157. * source, and applies it to a Cloud Storage bucket.
  158. *
  159. * @param source - Rules source to apply.
  160. * @param bucket - Optional name of the Cloud Storage bucket to apply the rules on. If
  161. * not specified, applies the ruleset on the default bucket configured via
  162. * {@link firebase-admin.app#AppOptions}.
  163. * @returns A promise that fulfills when the ruleset is created and released.
  164. */
  165. releaseStorageRulesetFromSource(source, bucket) {
  166. return Promise.resolve()
  167. .then(() => {
  168. // Bucket name is not required until the last step. But since there's a createRuleset step
  169. // before then, make sure to run this check and fail early if the bucket name is invalid.
  170. this.getBucketName(bucket);
  171. const rulesFile = this.createRulesFileFromSource('storage.rules', source);
  172. return this.createRuleset(rulesFile);
  173. })
  174. .then((ruleset) => {
  175. return this.releaseStorageRuleset(ruleset, bucket)
  176. .then(() => {
  177. return ruleset;
  178. });
  179. });
  180. }
  181. /**
  182. * Applies the specified {@link Ruleset} ruleset
  183. * to a Cloud Storage bucket.
  184. *
  185. * @param ruleset - Name of the ruleset to apply or a `RulesetMetadata` object
  186. * containing the name.
  187. * @param bucket - Optional name of the Cloud Storage bucket to apply the rules on. If
  188. * not specified, applies the ruleset on the default bucket configured via
  189. * {@link firebase-admin.app#AppOptions}.
  190. * @returns A promise that fulfills when the ruleset is released.
  191. */
  192. releaseStorageRuleset(ruleset, bucket) {
  193. return Promise.resolve()
  194. .then(() => {
  195. return this.getBucketName(bucket);
  196. })
  197. .then((bucketName) => {
  198. return this.releaseRuleset(ruleset, `${SecurityRules.FIREBASE_STORAGE}/${bucketName}`);
  199. });
  200. }
  201. /**
  202. * Creates a {@link RulesFile} with the given name
  203. * and source. Throws an error if any of the arguments are invalid. This is a local
  204. * operation, and does not involve any network API calls.
  205. *
  206. * @example
  207. * ```javascript
  208. * const source = '// Some rules source';
  209. * const rulesFile = admin.securityRules().createRulesFileFromSource(
  210. * 'firestore.rules', source);
  211. * ```
  212. *
  213. * @param name - Name to assign to the rules file. This is usually a short file name that
  214. * helps identify the file in a ruleset.
  215. * @param source - Contents of the rules file.
  216. * @returns A new rules file instance.
  217. */
  218. createRulesFileFromSource(name, source) {
  219. if (!validator.isNonEmptyString(name)) {
  220. throw new security_rules_internal_1.FirebaseSecurityRulesError('invalid-argument', 'Name must be a non-empty string.');
  221. }
  222. let content;
  223. if (validator.isNonEmptyString(source)) {
  224. content = source;
  225. }
  226. else if (validator.isBuffer(source)) {
  227. content = source.toString('utf-8');
  228. }
  229. else {
  230. throw new security_rules_internal_1.FirebaseSecurityRulesError('invalid-argument', 'Source must be a non-empty string or a Buffer.');
  231. }
  232. return {
  233. name,
  234. content,
  235. };
  236. }
  237. /**
  238. * Creates a new {@link Ruleset} from the given {@link RulesFile}.
  239. *
  240. * @param file - Rules file to include in the new `Ruleset`.
  241. * @returns A promise that fulfills with the newly created `Ruleset`.
  242. */
  243. createRuleset(file) {
  244. const ruleset = {
  245. source: {
  246. files: [file],
  247. },
  248. };
  249. return this.client.createRuleset(ruleset)
  250. .then((rulesetResponse) => {
  251. return new Ruleset(rulesetResponse);
  252. });
  253. }
  254. /**
  255. * Deletes the {@link Ruleset} identified by the given
  256. * name. The input name should be the short name string without the project ID
  257. * prefix. For example, to delete the `projects/project-id/rulesets/my-ruleset`,
  258. * pass the short name "my-ruleset". Rejects with a `not-found` error if the
  259. * specified `Ruleset` cannot be found.
  260. *
  261. * @param name - Name of the `Ruleset` to delete.
  262. * @returns A promise that fulfills when the `Ruleset` is deleted.
  263. */
  264. deleteRuleset(name) {
  265. return this.client.deleteRuleset(name);
  266. }
  267. /**
  268. * Retrieves a page of ruleset metadata.
  269. *
  270. * @param pageSize - The page size, 100 if undefined. This is also the maximum allowed
  271. * limit.
  272. * @param nextPageToken - The next page token. If not specified, returns rulesets
  273. * starting without any offset.
  274. * @returns A promise that fulfills with a page of rulesets.
  275. */
  276. listRulesetMetadata(pageSize = 100, nextPageToken) {
  277. return this.client.listRulesets(pageSize, nextPageToken)
  278. .then((response) => {
  279. return new RulesetMetadataList(response);
  280. });
  281. }
  282. getRulesetForRelease(releaseName) {
  283. return this.client.getRelease(releaseName)
  284. .then((release) => {
  285. const rulesetName = release.rulesetName;
  286. if (!validator.isNonEmptyString(rulesetName)) {
  287. throw new security_rules_internal_1.FirebaseSecurityRulesError('not-found', `Ruleset name not found for ${releaseName}.`);
  288. }
  289. return this.getRuleset(stripProjectIdPrefix(rulesetName));
  290. });
  291. }
  292. releaseRuleset(ruleset, releaseName) {
  293. if (!validator.isNonEmptyString(ruleset) &&
  294. (!validator.isNonNullObject(ruleset) || !validator.isNonEmptyString(ruleset.name))) {
  295. const err = new security_rules_internal_1.FirebaseSecurityRulesError('invalid-argument', 'ruleset must be a non-empty name or a RulesetMetadata object.');
  296. return Promise.reject(err);
  297. }
  298. const rulesetName = validator.isString(ruleset) ? ruleset : ruleset.name;
  299. return this.client.updateOrCreateRelease(releaseName, rulesetName)
  300. .then(() => {
  301. return;
  302. });
  303. }
  304. getBucketName(bucket) {
  305. const bucketName = (typeof bucket !== 'undefined') ? bucket : this.app.options.storageBucket;
  306. if (!validator.isNonEmptyString(bucketName)) {
  307. throw new security_rules_internal_1.FirebaseSecurityRulesError('invalid-argument', 'Bucket name not specified or invalid. Specify a default bucket name via the ' +
  308. 'storageBucket option when initializing the app, or specify the bucket name ' +
  309. 'explicitly when calling the rules API.');
  310. }
  311. return bucketName;
  312. }
  313. }
  314. exports.SecurityRules = SecurityRules;
  315. SecurityRules.CLOUD_FIRESTORE = 'cloud.firestore';
  316. SecurityRules.FIREBASE_STORAGE = 'firebase.storage';
  317. function stripProjectIdPrefix(name) {
  318. return name.split('/').pop();
  319. }