firebase-tools 10.4.2 → 10.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/bin/firebase.js +1 -1
- package/lib/command.js +4 -4
- package/lib/commands/deploy.js +1 -1
- package/lib/commands/emulators-start.js +13 -3
- package/lib/commands/ext-configure.js +15 -5
- package/lib/commands/ext-dev-emulators-start.js +5 -1
- package/lib/commands/ext-export.js +6 -5
- package/lib/commands/ext-install.js +28 -44
- package/lib/commands/ext-update.js +9 -1
- package/lib/commands/functions-delete.js +2 -5
- package/lib/commands/functions-secrets-destroy.js +23 -3
- package/lib/commands/functions-secrets-prune.js +15 -12
- package/lib/commands/functions-secrets-set.js +51 -4
- package/lib/commands/hosting-channel-deploy.js +2 -2
- package/lib/deploy/database/deploy.js +4 -0
- package/lib/deploy/database/index.js +1 -0
- package/lib/deploy/extensions/deploy.js +4 -4
- package/lib/deploy/extensions/deploymentSummary.js +8 -5
- package/lib/deploy/extensions/planner.js +36 -9
- package/lib/deploy/extensions/prepare.js +1 -1
- package/lib/deploy/extensions/secrets.js +2 -2
- package/lib/deploy/extensions/tasks.js +60 -21
- package/lib/deploy/functions/backend.js +17 -6
- package/lib/deploy/functions/build.js +162 -0
- package/lib/deploy/functions/checkIam.js +6 -5
- package/lib/deploy/functions/deploy.js +14 -15
- package/lib/deploy/functions/ensure.js +4 -4
- package/lib/deploy/functions/functionsDeployHelper.js +54 -23
- package/lib/deploy/functions/prepare.js +92 -39
- package/lib/deploy/functions/prepareFunctionsUpload.js +16 -21
- package/lib/deploy/functions/pricing.js +6 -3
- package/lib/deploy/functions/prompts.js +1 -7
- package/lib/deploy/functions/release/fabricator.js +44 -5
- package/lib/deploy/functions/release/index.js +31 -6
- package/lib/deploy/functions/release/planner.js +10 -8
- package/lib/deploy/functions/release/reporter.js +14 -11
- package/lib/deploy/functions/runtimes/discovery/parsing.js +12 -6
- package/lib/deploy/functions/runtimes/discovery/v1alpha1.js +61 -13
- package/lib/deploy/functions/runtimes/node/index.js +1 -1
- package/lib/deploy/functions/runtimes/node/parseRuntimeAndValidateSDK.js +3 -3
- package/lib/deploy/functions/runtimes/node/parseTriggers.js +29 -24
- package/lib/deploy/functions/runtimes/node/versioning.js +2 -2
- package/lib/deploy/functions/services/auth.js +95 -0
- package/lib/deploy/functions/services/index.js +41 -21
- package/lib/deploy/functions/services/storage.js +1 -6
- package/lib/deploy/functions/validate.js +8 -5
- package/lib/deploy/hosting/args.js +2 -0
- package/lib/deploy/hosting/convertConfig.js +37 -8
- package/lib/deploy/hosting/deploy.js +3 -3
- package/lib/deploy/hosting/prepare.js +2 -2
- package/lib/deploy/hosting/release.js +6 -2
- package/lib/deploy/index.js +82 -93
- package/lib/deploy/remoteconfig/deploy.js +4 -0
- package/lib/deploy/remoteconfig/index.js +3 -1
- package/lib/emulator/auth/operations.js +26 -20
- package/lib/emulator/auth/state.js +79 -43
- package/lib/emulator/auth/utils.js +3 -25
- package/lib/emulator/commandUtils.js +72 -2
- package/lib/emulator/controller.js +14 -5
- package/lib/emulator/downloadableEmulators.js +47 -24
- package/lib/emulator/extensions/postinstall.js +41 -0
- package/lib/emulator/extensions/validation.js +2 -2
- package/lib/emulator/extensionsEmulator.js +85 -21
- package/lib/emulator/functionsEmulator.js +79 -7
- package/lib/emulator/functionsEmulatorShared.js +36 -21
- package/lib/emulator/registry.js +34 -12
- package/lib/emulator/shared/request.js +19 -0
- package/lib/emulator/storage/apis/firebase.js +32 -35
- package/lib/emulator/storage/apis/gcloud.js +84 -66
- package/lib/emulator/storage/files.js +56 -52
- package/lib/emulator/storage/index.js +23 -3
- package/lib/emulator/storage/metadata.js +18 -8
- package/lib/emulator/storage/rules/manager.js +7 -17
- package/lib/emulator/storage/rules/utils.js +11 -3
- package/lib/emulator/storage/server.js +38 -12
- package/lib/ensureApiEnabled.js +8 -4
- package/lib/extensions/askUserForParam.js +14 -11
- package/lib/extensions/changelog.js +1 -1
- package/lib/extensions/emulator/optionsHelper.js +9 -10
- package/lib/extensions/emulator/specHelper.js +7 -1
- package/lib/extensions/emulator/triggerHelper.js +11 -14
- package/lib/extensions/extensionsApi.js +2 -1
- package/lib/extensions/extensionsHelper.js +30 -24
- package/lib/extensions/manifest.js +28 -8
- package/lib/extensions/paramHelper.js +19 -13
- package/lib/extensions/provisioningHelper.js +2 -2
- package/lib/extensions/warnings.js +3 -3
- package/lib/functions/env.js +10 -2
- package/lib/functions/events/index.js +7 -0
- package/lib/functions/events/v1.js +6 -0
- package/lib/functions/projectConfig.js +24 -3
- package/lib/functions/runtimeConfigExport.js +10 -6
- package/lib/functions/secrets.js +99 -6
- package/lib/gcp/cloudfunctions.js +37 -18
- package/lib/gcp/cloudfunctionsv2.js +41 -25
- package/lib/gcp/cloudtasks.js +5 -3
- package/lib/gcp/identityPlatform.js +44 -0
- package/lib/gcp/secretManager.js +2 -2
- package/lib/metaprogramming.js +2 -0
- package/lib/previews.js +1 -1
- package/lib/serve/hosting.js +25 -12
- package/lib/serve/index.js +6 -0
- package/lib/track.js +15 -21
- package/lib/utils.js +30 -1
- package/npm-shrinkwrap.json +44 -2
- package/package.json +4 -1
- package/schema/firebase-config.json +6 -0
- package/lib/emulator/storage/list.js +0 -18
|
@@ -11,33 +11,34 @@ const crc_1 = require("../crc");
|
|
|
11
11
|
const multipart_1 = require("../multipart");
|
|
12
12
|
const upload_1 = require("../upload");
|
|
13
13
|
const errors_1 = require("../errors");
|
|
14
|
+
const request_1 = require("../../shared/request");
|
|
14
15
|
function createCloudEndpoints(emulator) {
|
|
15
16
|
const gcloudStorageAPI = (0, express_1.Router)();
|
|
16
|
-
const {
|
|
17
|
+
const { adminStorageLayer, uploadService } = emulator;
|
|
17
18
|
gcloudStorageAPI.use(/.*\/b\/(.+?)\/.*/, (req, res, next) => {
|
|
18
|
-
|
|
19
|
+
adminStorageLayer.createBucket(req.params[0]);
|
|
19
20
|
next();
|
|
20
21
|
});
|
|
21
22
|
gcloudStorageAPI.get("/b", async (req, res) => {
|
|
22
23
|
res.json({
|
|
23
24
|
kind: "storage#buckets",
|
|
24
|
-
items: await
|
|
25
|
+
items: await adminStorageLayer.listBuckets(),
|
|
25
26
|
});
|
|
26
27
|
});
|
|
27
28
|
gcloudStorageAPI.get(["/b/:bucketId/o/:objectId", "/download/storage/v1/b/:bucketId/o/:objectId"], async (req, res) => {
|
|
28
29
|
let getObjectResponse;
|
|
29
30
|
try {
|
|
30
|
-
getObjectResponse = await
|
|
31
|
+
getObjectResponse = await adminStorageLayer.getObject({
|
|
31
32
|
bucketId: req.params.bucketId,
|
|
32
33
|
decodedObjectId: req.params.objectId,
|
|
33
|
-
}
|
|
34
|
+
});
|
|
34
35
|
}
|
|
35
36
|
catch (err) {
|
|
36
37
|
if (err instanceof errors_1.NotFoundError) {
|
|
37
38
|
return sendObjectNotFound(req, res);
|
|
38
39
|
}
|
|
39
40
|
if (err instanceof errors_1.ForbiddenError) {
|
|
40
|
-
|
|
41
|
+
return res.sendStatus(403);
|
|
41
42
|
}
|
|
42
43
|
throw err;
|
|
43
44
|
}
|
|
@@ -49,67 +50,67 @@ function createCloudEndpoints(emulator) {
|
|
|
49
50
|
gcloudStorageAPI.patch("/b/:bucketId/o/:objectId", async (req, res) => {
|
|
50
51
|
let updatedMetadata;
|
|
51
52
|
try {
|
|
52
|
-
updatedMetadata = await
|
|
53
|
+
updatedMetadata = await adminStorageLayer.updateObjectMetadata({
|
|
53
54
|
bucketId: req.params.bucketId,
|
|
54
55
|
decodedObjectId: req.params.objectId,
|
|
55
56
|
metadata: req.body,
|
|
56
|
-
}
|
|
57
|
+
});
|
|
57
58
|
}
|
|
58
59
|
catch (err) {
|
|
59
60
|
if (err instanceof errors_1.NotFoundError) {
|
|
60
61
|
return sendObjectNotFound(req, res);
|
|
61
62
|
}
|
|
62
63
|
if (err instanceof errors_1.ForbiddenError) {
|
|
63
|
-
|
|
64
|
+
return res.sendStatus(403);
|
|
64
65
|
}
|
|
65
66
|
throw err;
|
|
66
67
|
}
|
|
67
68
|
return res.json(new metadata_1.CloudStorageObjectMetadata(updatedMetadata));
|
|
68
69
|
});
|
|
69
|
-
gcloudStorageAPI.get("/b/:bucketId/o", (req, res) => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
70
|
+
gcloudStorageAPI.get("/b/:bucketId/o", async (req, res) => {
|
|
71
|
+
var _a;
|
|
72
|
+
let listResponse;
|
|
73
|
+
try {
|
|
74
|
+
listResponse = await adminStorageLayer.listObjects({
|
|
75
|
+
bucketId: req.params.bucketId,
|
|
76
|
+
prefix: req.query.prefix ? req.query.prefix.toString() : "",
|
|
77
|
+
delimiter: req.query.delimiter ? req.query.delimiter.toString() : "",
|
|
78
|
+
pageToken: req.query.pageToken ? req.query.pageToken.toString() : undefined,
|
|
79
|
+
maxResults: req.query.maxResults ? +req.query.maxResults.toString() : undefined,
|
|
80
|
+
authorization: req.header("authorization"),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
if (err instanceof errors_1.ForbiddenError) {
|
|
85
|
+
return res.sendStatus(403);
|
|
86
|
+
}
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
89
|
+
return res.status(200).json({
|
|
90
|
+
kind: "#storage/objects",
|
|
91
|
+
nextPageToken: listResponse.nextPageToken,
|
|
92
|
+
prefixes: listResponse.prefixes,
|
|
93
|
+
items: (_a = listResponse.items) === null || _a === void 0 ? void 0 : _a.map((item) => new metadata_1.CloudStorageObjectMetadata(item)),
|
|
94
|
+
});
|
|
79
95
|
});
|
|
80
96
|
gcloudStorageAPI.delete("/b/:bucketId/o/:objectId", async (req, res) => {
|
|
81
97
|
try {
|
|
82
|
-
await
|
|
98
|
+
await adminStorageLayer.deleteObject({
|
|
83
99
|
bucketId: req.params.bucketId,
|
|
84
100
|
decodedObjectId: req.params.objectId,
|
|
85
|
-
}
|
|
101
|
+
});
|
|
86
102
|
}
|
|
87
103
|
catch (err) {
|
|
88
104
|
if (err instanceof errors_1.NotFoundError) {
|
|
89
105
|
return sendObjectNotFound(req, res);
|
|
90
106
|
}
|
|
91
107
|
if (err instanceof errors_1.ForbiddenError) {
|
|
92
|
-
|
|
108
|
+
return res.sendStatus(403);
|
|
93
109
|
}
|
|
94
110
|
throw err;
|
|
95
111
|
}
|
|
96
112
|
return res.sendStatus(204);
|
|
97
113
|
});
|
|
98
|
-
const reqBodyToBuffer = async (req) => {
|
|
99
|
-
if (req.body instanceof Buffer) {
|
|
100
|
-
return Buffer.from(req.body);
|
|
101
|
-
}
|
|
102
|
-
const bufs = [];
|
|
103
|
-
req.on("data", (data) => {
|
|
104
|
-
bufs.push(data);
|
|
105
|
-
});
|
|
106
|
-
await new Promise((resolve) => {
|
|
107
|
-
req.on("end", () => {
|
|
108
|
-
resolve();
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
return Buffer.concat(bufs);
|
|
112
|
-
};
|
|
113
114
|
gcloudStorageAPI.put("/upload/storage/v1/b/:bucketId/o", async (req, res) => {
|
|
114
115
|
if (!req.query.upload_id) {
|
|
115
116
|
res.sendStatus(400);
|
|
@@ -118,7 +119,7 @@ function createCloudEndpoints(emulator) {
|
|
|
118
119
|
const uploadId = req.query.upload_id.toString();
|
|
119
120
|
let upload;
|
|
120
121
|
try {
|
|
121
|
-
uploadService.continueResumableUpload(uploadId, await reqBodyToBuffer(req));
|
|
122
|
+
uploadService.continueResumableUpload(uploadId, await (0, request_1.reqBodyToBuffer)(req));
|
|
122
123
|
upload = uploadService.finalizeResumableUpload(uploadId);
|
|
123
124
|
}
|
|
124
125
|
catch (err) {
|
|
@@ -132,11 +133,11 @@ function createCloudEndpoints(emulator) {
|
|
|
132
133
|
}
|
|
133
134
|
let metadata;
|
|
134
135
|
try {
|
|
135
|
-
metadata = await
|
|
136
|
+
metadata = await adminStorageLayer.uploadObject(upload);
|
|
136
137
|
}
|
|
137
138
|
catch (err) {
|
|
138
139
|
if (err instanceof errors_1.ForbiddenError) {
|
|
139
|
-
|
|
140
|
+
return res.sendStatus(403);
|
|
140
141
|
}
|
|
141
142
|
throw err;
|
|
142
143
|
}
|
|
@@ -147,17 +148,17 @@ function createCloudEndpoints(emulator) {
|
|
|
147
148
|
emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE).log("WARN_ONCE", "Cloud Storage ACLs are not supported in the Storage Emulator. All related methods will succeed, but have no effect.");
|
|
148
149
|
let getObjectResponse;
|
|
149
150
|
try {
|
|
150
|
-
getObjectResponse = await
|
|
151
|
+
getObjectResponse = await adminStorageLayer.getObject({
|
|
151
152
|
bucketId: req.params.bucketId,
|
|
152
153
|
decodedObjectId: req.params.objectId,
|
|
153
|
-
}
|
|
154
|
+
});
|
|
154
155
|
}
|
|
155
156
|
catch (err) {
|
|
156
157
|
if (err instanceof errors_1.NotFoundError) {
|
|
157
158
|
return sendObjectNotFound(req, res);
|
|
158
159
|
}
|
|
159
160
|
if (err instanceof errors_1.ForbiddenError) {
|
|
160
|
-
|
|
161
|
+
return res.sendStatus(403);
|
|
161
162
|
}
|
|
162
163
|
throw err;
|
|
163
164
|
}
|
|
@@ -199,22 +200,28 @@ function createCloudEndpoints(emulator) {
|
|
|
199
200
|
metadataRaw: JSON.stringify(req.body),
|
|
200
201
|
authorization: req.header("authorization"),
|
|
201
202
|
});
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
203
|
+
const uploadUrl = registry_1.EmulatorRegistry.url(types_1.Emulators.STORAGE, req);
|
|
204
|
+
uploadUrl.pathname = `/upload/storage/v1/b/${req.params.bucketId}/o`;
|
|
205
|
+
uploadUrl.searchParams.set("name", name);
|
|
206
|
+
uploadUrl.searchParams.set("uploadType", "resumable");
|
|
207
|
+
uploadUrl.searchParams.set("upload_id", upload.id);
|
|
208
|
+
return res.header("location", uploadUrl.toString()).sendStatus(200);
|
|
205
209
|
}
|
|
206
210
|
let metadataRaw;
|
|
207
211
|
let dataRaw;
|
|
208
212
|
try {
|
|
209
|
-
({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await reqBodyToBuffer(req)));
|
|
213
|
+
({ metadataRaw, dataRaw } = (0, multipart_1.parseObjectUploadMultipartRequest)(contentTypeHeader, await (0, request_1.reqBodyToBuffer)(req)));
|
|
210
214
|
}
|
|
211
215
|
catch (err) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
if (err instanceof Error) {
|
|
217
|
+
return res.status(400).json({
|
|
218
|
+
error: {
|
|
219
|
+
code: 400,
|
|
220
|
+
message: err.message,
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
throw err;
|
|
218
225
|
}
|
|
219
226
|
const upload = uploadService.multipartUpload({
|
|
220
227
|
bucketId: req.params.bucketId,
|
|
@@ -225,11 +232,11 @@ function createCloudEndpoints(emulator) {
|
|
|
225
232
|
});
|
|
226
233
|
let metadata;
|
|
227
234
|
try {
|
|
228
|
-
metadata = await
|
|
235
|
+
metadata = await adminStorageLayer.uploadObject(upload);
|
|
229
236
|
}
|
|
230
237
|
catch (err) {
|
|
231
238
|
if (err instanceof errors_1.ForbiddenError) {
|
|
232
|
-
|
|
239
|
+
return res.sendStatus(403);
|
|
233
240
|
}
|
|
234
241
|
throw err;
|
|
235
242
|
}
|
|
@@ -238,34 +245,45 @@ function createCloudEndpoints(emulator) {
|
|
|
238
245
|
gcloudStorageAPI.get("/:bucketId/:objectId(**)", async (req, res) => {
|
|
239
246
|
let getObjectResponse;
|
|
240
247
|
try {
|
|
241
|
-
getObjectResponse = await
|
|
248
|
+
getObjectResponse = await adminStorageLayer.getObject({
|
|
242
249
|
bucketId: req.params.bucketId,
|
|
243
250
|
decodedObjectId: req.params.objectId,
|
|
244
|
-
}
|
|
251
|
+
});
|
|
245
252
|
}
|
|
246
253
|
catch (err) {
|
|
247
254
|
if (err instanceof errors_1.NotFoundError) {
|
|
248
255
|
return sendObjectNotFound(req, res);
|
|
249
256
|
}
|
|
250
257
|
if (err instanceof errors_1.ForbiddenError) {
|
|
251
|
-
|
|
258
|
+
return res.sendStatus(403);
|
|
252
259
|
}
|
|
253
260
|
throw err;
|
|
254
261
|
}
|
|
255
262
|
return sendFileBytes(getObjectResponse.metadata, getObjectResponse.data, req, res);
|
|
256
263
|
});
|
|
257
264
|
gcloudStorageAPI.post("/b/:bucketId/o/:objectId/:method(rewriteTo|copyTo)/b/:destBucketId/o/:destObjectId", (req, res, next) => {
|
|
258
|
-
const md = storageLayer.getMetadata(req.params.bucketId, req.params.objectId);
|
|
259
|
-
if (!md) {
|
|
260
|
-
return sendObjectNotFound(req, res);
|
|
261
|
-
}
|
|
262
265
|
if (req.params.method === "rewriteTo" && req.query.rewriteToken) {
|
|
263
266
|
return next();
|
|
264
267
|
}
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
268
|
+
let metadata;
|
|
269
|
+
try {
|
|
270
|
+
metadata = adminStorageLayer.copyObject({
|
|
271
|
+
sourceBucket: req.params.bucketId,
|
|
272
|
+
sourceObject: req.params.objectId,
|
|
273
|
+
destinationBucket: req.params.destBucketId,
|
|
274
|
+
destinationObject: req.params.destObjectId,
|
|
275
|
+
incomingMetadata: req.body,
|
|
276
|
+
authorization: "Bearer owner",
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
catch (err) {
|
|
280
|
+
if (err instanceof errors_1.NotFoundError) {
|
|
281
|
+
return sendObjectNotFound(req, res);
|
|
282
|
+
}
|
|
283
|
+
if (err instanceof errors_1.ForbiddenError) {
|
|
284
|
+
return res.sendStatus(403);
|
|
285
|
+
}
|
|
286
|
+
throw err;
|
|
269
287
|
}
|
|
270
288
|
const resource = new metadata_1.CloudStorageObjectMetadata(metadata);
|
|
271
289
|
res.status(200);
|
|
@@ -9,12 +9,10 @@ var __asyncValues = (this && this.__asyncValues) || function (o) {
|
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.StorageLayer = exports.StoredFile = void 0;
|
|
11
11
|
const fs_1 = require("fs");
|
|
12
|
-
const list_1 = require("./list");
|
|
13
12
|
const metadata_1 = require("./metadata");
|
|
14
13
|
const errors_1 = require("./errors");
|
|
15
14
|
const path = require("path");
|
|
16
15
|
const fse = require("fs-extra");
|
|
17
|
-
const cloudFunctions_1 = require("./cloudFunctions");
|
|
18
16
|
const logger_1 = require("../../logger");
|
|
19
17
|
const adminSdkConfig_1 = require("../adminSdkConfig");
|
|
20
18
|
const types_1 = require("./rules/types");
|
|
@@ -39,17 +37,14 @@ class StoredFile {
|
|
|
39
37
|
}
|
|
40
38
|
exports.StoredFile = StoredFile;
|
|
41
39
|
class StorageLayer {
|
|
42
|
-
constructor(_projectId, _rulesValidator, _adminCredsValidator, _persistence) {
|
|
40
|
+
constructor(_projectId, _files, _buckets, _rulesValidator, _adminCredsValidator, _persistence, _cloudFunctions) {
|
|
43
41
|
this._projectId = _projectId;
|
|
42
|
+
this._files = _files;
|
|
43
|
+
this._buckets = _buckets;
|
|
44
44
|
this._rulesValidator = _rulesValidator;
|
|
45
45
|
this._adminCredsValidator = _adminCredsValidator;
|
|
46
46
|
this._persistence = _persistence;
|
|
47
|
-
this.
|
|
48
|
-
this._cloudFunctions = new cloudFunctions_1.StorageCloudFunctions(this._projectId);
|
|
49
|
-
}
|
|
50
|
-
reset() {
|
|
51
|
-
this._files = new Map();
|
|
52
|
-
this._buckets = new Map();
|
|
47
|
+
this._cloudFunctions = _cloudFunctions;
|
|
53
48
|
}
|
|
54
49
|
createBucket(id) {
|
|
55
50
|
if (!this._buckets.has(id)) {
|
|
@@ -66,11 +61,11 @@ class StorageLayer {
|
|
|
66
61
|
}
|
|
67
62
|
return [...this._buckets.values()];
|
|
68
63
|
}
|
|
69
|
-
async
|
|
64
|
+
async getObject(request) {
|
|
70
65
|
var _a;
|
|
71
66
|
const metadata = this.getMetadata(request.bucketId, request.decodedObjectId);
|
|
72
67
|
const hasValidDownloadToken = ((metadata === null || metadata === void 0 ? void 0 : metadata.downloadTokens) || []).includes((_a = request.downloadToken) !== null && _a !== void 0 ? _a : "");
|
|
73
|
-
let authorized =
|
|
68
|
+
let authorized = hasValidDownloadToken;
|
|
74
69
|
if (!authorized) {
|
|
75
70
|
authorized = await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.GET, { before: metadata === null || metadata === void 0 ? void 0 : metadata.asRulesResource() }, request.authorization);
|
|
76
71
|
}
|
|
@@ -99,10 +94,9 @@ class StorageLayer {
|
|
|
99
94
|
}
|
|
100
95
|
return undefined;
|
|
101
96
|
}
|
|
102
|
-
async
|
|
97
|
+
async deleteObject(request) {
|
|
103
98
|
const storedMetadata = this.getMetadata(request.bucketId, request.decodedObjectId);
|
|
104
|
-
const authorized =
|
|
105
|
-
(await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.DELETE, { before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource() }, request.authorization));
|
|
99
|
+
const authorized = await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.DELETE, { before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource() }, request.authorization);
|
|
106
100
|
if (!authorized) {
|
|
107
101
|
throw new errors_1.ForbiddenError();
|
|
108
102
|
}
|
|
@@ -131,13 +125,12 @@ class StorageLayer {
|
|
|
131
125
|
return true;
|
|
132
126
|
}
|
|
133
127
|
}
|
|
134
|
-
async
|
|
128
|
+
async updateObjectMetadata(request) {
|
|
135
129
|
const storedMetadata = this.getMetadata(request.bucketId, request.decodedObjectId);
|
|
136
|
-
const authorized =
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
}, request.authorization));
|
|
130
|
+
const authorized = await this._rulesValidator.validate(["b", request.bucketId, "o", request.decodedObjectId].join("/"), request.bucketId, types_1.RulesetOperationMethod.UPDATE, {
|
|
131
|
+
before: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource(),
|
|
132
|
+
after: storedMetadata === null || storedMetadata === void 0 ? void 0 : storedMetadata.asRulesResource(request.metadata),
|
|
133
|
+
}, request.authorization);
|
|
141
134
|
if (!authorized) {
|
|
142
135
|
throw new errors_1.ForbiddenError();
|
|
143
136
|
}
|
|
@@ -147,7 +140,7 @@ class StorageLayer {
|
|
|
147
140
|
storedMetadata.update(request.metadata);
|
|
148
141
|
return storedMetadata;
|
|
149
142
|
}
|
|
150
|
-
async
|
|
143
|
+
async uploadObject(upload) {
|
|
151
144
|
if (upload.status !== upload_1.UploadStatus.FINISHED) {
|
|
152
145
|
throw new Error(`Unexpected upload status encountered: ${upload.status}.`);
|
|
153
146
|
}
|
|
@@ -162,8 +155,8 @@ class StorageLayer {
|
|
|
162
155
|
cacheControl: upload.metadata.cacheControl,
|
|
163
156
|
customMetadata: upload.metadata.metadata,
|
|
164
157
|
}, this._cloudFunctions, this._persistence.readBytes(upload.path, upload.size));
|
|
165
|
-
|
|
166
|
-
|
|
158
|
+
metadata.update(upload.metadata, false);
|
|
159
|
+
const authorized = await this._rulesValidator.validate(["b", upload.bucketId, "o", upload.objectId].join("/"), upload.bucketId, types_1.RulesetOperationMethod.CREATE, { after: metadata === null || metadata === void 0 ? void 0 : metadata.asRulesResource() }, upload.authorization);
|
|
167
160
|
if (!authorized) {
|
|
168
161
|
this._persistence.deleteFile(upload.path);
|
|
169
162
|
throw new errors_1.ForbiddenError();
|
|
@@ -174,17 +167,24 @@ class StorageLayer {
|
|
|
174
167
|
this._cloudFunctions.dispatch("finalize", new metadata_1.CloudStorageObjectMetadata(metadata));
|
|
175
168
|
return metadata;
|
|
176
169
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
this.
|
|
182
|
-
|
|
183
|
-
|
|
170
|
+
copyObject({ sourceBucket, sourceObject, destinationBucket, destinationObject, incomingMetadata, authorization, }) {
|
|
171
|
+
if (!this._adminCredsValidator.validate(authorization)) {
|
|
172
|
+
throw new errors_1.ForbiddenError();
|
|
173
|
+
}
|
|
174
|
+
const sourceMetadata = this.getMetadata(sourceBucket, sourceObject);
|
|
175
|
+
if (!sourceMetadata) {
|
|
176
|
+
throw new errors_1.NotFoundError();
|
|
177
|
+
}
|
|
178
|
+
const sourceBytes = this.getBytes(sourceBucket, sourceObject);
|
|
179
|
+
const destinationFilePath = this.path(destinationBucket, destinationObject);
|
|
180
|
+
this._persistence.deleteFile(destinationFilePath, true);
|
|
181
|
+
this._persistence.appendBytes(destinationFilePath, sourceBytes);
|
|
182
|
+
const newMetadata = Object.assign(Object.assign(Object.assign({}, sourceMetadata), { metadata: sourceMetadata.customMetadata }), incomingMetadata);
|
|
183
|
+
if (sourceMetadata.downloadTokens.length &&
|
|
184
184
|
!((incomingMetadata === null || incomingMetadata === void 0 ? void 0 : incomingMetadata.metadata) && Object.keys(incomingMetadata === null || incomingMetadata === void 0 ? void 0 : incomingMetadata.metadata).length)) {
|
|
185
185
|
if (!newMetadata.metadata)
|
|
186
186
|
newMetadata.metadata = {};
|
|
187
|
-
newMetadata.metadata.firebaseStorageDownloadTokens =
|
|
187
|
+
newMetadata.metadata.firebaseStorageDownloadTokens = sourceMetadata.downloadTokens.join(",");
|
|
188
188
|
}
|
|
189
189
|
if (newMetadata.metadata) {
|
|
190
190
|
for (const [k, v] of Object.entries(newMetadata.metadata)) {
|
|
@@ -201,27 +201,23 @@ class StorageLayer {
|
|
|
201
201
|
contentLanguage: newMetadata.contentLanguage,
|
|
202
202
|
cacheControl: newMetadata.cacheControl,
|
|
203
203
|
customMetadata: newMetadata.metadata,
|
|
204
|
-
}, this._cloudFunctions,
|
|
205
|
-
const file = new StoredFile(copiedFileMetadata, this._persistence.getDiskPath(
|
|
206
|
-
this._files.set(
|
|
204
|
+
}, this._cloudFunctions, sourceBytes, incomingMetadata);
|
|
205
|
+
const file = new StoredFile(copiedFileMetadata, this._persistence.getDiskPath(destinationFilePath));
|
|
206
|
+
this._files.set(destinationFilePath, file);
|
|
207
207
|
this._cloudFunctions.dispatch("finalize", new metadata_1.CloudStorageObjectMetadata(file.metadata));
|
|
208
208
|
return file.metadata;
|
|
209
209
|
}
|
|
210
|
-
async
|
|
211
|
-
var _a
|
|
212
|
-
const
|
|
213
|
-
|
|
210
|
+
async listObjects(request) {
|
|
211
|
+
var _a;
|
|
212
|
+
const { bucketId, prefix, delimiter, pageToken, authorization } = request;
|
|
213
|
+
const authorized = await this._rulesValidator.validate(["b", bucketId, "o", prefix].join("/"), bucketId, types_1.RulesetOperationMethod.LIST, {}, authorization);
|
|
214
214
|
if (!authorized) {
|
|
215
215
|
throw new errors_1.ForbiddenError();
|
|
216
216
|
}
|
|
217
|
-
const itemsResults = this.listItems(request.bucketId, request.prefix, request.delimiter, request.pageToken, request.maxResults);
|
|
218
|
-
return new list_1.ListResponse((_a = itemsResults.prefixes) !== null && _a !== void 0 ? _a : [], (_c = (_b = itemsResults.items) === null || _b === void 0 ? void 0 : _b.map((i) => new list_1.ListItem(i.name, i.bucket))) !== null && _c !== void 0 ? _c : [], itemsResults.nextPageToken);
|
|
219
|
-
}
|
|
220
|
-
listItems(bucket, prefix, delimiter, pageToken, maxResults) {
|
|
221
217
|
let items = [];
|
|
222
218
|
const prefixes = new Set();
|
|
223
219
|
for (const [, file] of this._files) {
|
|
224
|
-
if (file.metadata.bucket !==
|
|
220
|
+
if (file.metadata.bucket !== bucketId) {
|
|
225
221
|
continue;
|
|
226
222
|
}
|
|
227
223
|
const name = file.metadata.name;
|
|
@@ -258,9 +254,7 @@ class StorageLayer {
|
|
|
258
254
|
items = items.slice(idx);
|
|
259
255
|
}
|
|
260
256
|
}
|
|
261
|
-
|
|
262
|
-
maxResults = 1000;
|
|
263
|
-
}
|
|
257
|
+
const maxResults = (_a = request.maxResults) !== null && _a !== void 0 ? _a : 1000;
|
|
264
258
|
let nextPageToken = undefined;
|
|
265
259
|
if (items.length > maxResults) {
|
|
266
260
|
nextPageToken = items[maxResults].name;
|
|
@@ -269,10 +263,10 @@ class StorageLayer {
|
|
|
269
263
|
return {
|
|
270
264
|
nextPageToken,
|
|
271
265
|
prefixes: prefixes.size > 0 ? [...prefixes].sort() : undefined,
|
|
272
|
-
items: items.length > 0 ? items
|
|
266
|
+
items: items.length > 0 ? items : undefined,
|
|
273
267
|
};
|
|
274
268
|
}
|
|
275
|
-
|
|
269
|
+
createDownloadToken(request) {
|
|
276
270
|
if (!this._adminCredsValidator.validate(request.authorization)) {
|
|
277
271
|
throw new errors_1.ForbiddenError();
|
|
278
272
|
}
|
|
@@ -283,7 +277,7 @@ class StorageLayer {
|
|
|
283
277
|
metadata.addDownloadToken();
|
|
284
278
|
return metadata;
|
|
285
279
|
}
|
|
286
|
-
|
|
280
|
+
deleteDownloadToken(request) {
|
|
287
281
|
if (!this._adminCredsValidator.validate(request.authorization)) {
|
|
288
282
|
throw new errors_1.ForbiddenError();
|
|
289
283
|
}
|
|
@@ -354,10 +348,16 @@ class StorageLayer {
|
|
|
354
348
|
logger_1.logger.warn(`Could not find file "${blobPath}" in storage export.`);
|
|
355
349
|
continue;
|
|
356
350
|
}
|
|
357
|
-
|
|
358
|
-
|
|
351
|
+
let decodedBlobPath = decodeURIComponent(blobPath);
|
|
352
|
+
const decodedBlobPathSep = getPathSep(decodedBlobPath);
|
|
353
|
+
if (decodedBlobPathSep !== path.sep) {
|
|
354
|
+
decodedBlobPath = decodedBlobPath.split(decodedBlobPathSep).join(path.sep);
|
|
355
|
+
}
|
|
356
|
+
const blobDiskPath = this._persistence.getDiskPath(decodedBlobPath);
|
|
357
|
+
const file = new StoredFile(metadata, blobDiskPath);
|
|
358
|
+
this._files.set(decodedBlobPath, file);
|
|
359
|
+
fse.copyFileSync(blobAbsPath, blobDiskPath);
|
|
359
360
|
}
|
|
360
|
-
fse.copySync(blobsDir, this.dirPath);
|
|
361
361
|
}
|
|
362
362
|
*walkDirSync(dir) {
|
|
363
363
|
const files = (0, fs_1.readdirSync)(dir);
|
|
@@ -373,3 +373,7 @@ class StorageLayer {
|
|
|
373
373
|
}
|
|
374
374
|
}
|
|
375
375
|
exports.StorageLayer = StorageLayer;
|
|
376
|
+
function getPathSep(decodedPath) {
|
|
377
|
+
const firstSepIndex = decodedPath.search(/[^a-z0-9-_.]/g);
|
|
378
|
+
return decodedPath[firstSepIndex];
|
|
379
|
+
}
|
|
@@ -13,19 +13,30 @@ const runtime_1 = require("./rules/runtime");
|
|
|
13
13
|
const utils_1 = require("./rules/utils");
|
|
14
14
|
const persistence_1 = require("./persistence");
|
|
15
15
|
const upload_1 = require("./upload");
|
|
16
|
+
const cloudFunctions_1 = require("./cloudFunctions");
|
|
16
17
|
class StorageEmulator {
|
|
17
18
|
constructor(args) {
|
|
18
19
|
this.args = args;
|
|
19
20
|
this._logger = emulatorLogger_1.EmulatorLogger.forEmulator(types_1.Emulators.STORAGE);
|
|
21
|
+
this._files = new Map();
|
|
22
|
+
this._buckets = new Map();
|
|
20
23
|
this._rulesRuntime = new runtime_1.StorageRulesRuntime();
|
|
21
|
-
this._rulesManager =
|
|
24
|
+
this._rulesManager = this.createRulesManager(this.args.rules);
|
|
25
|
+
this._cloudFunctions = new cloudFunctions_1.StorageCloudFunctions(args.projectId);
|
|
22
26
|
this._persistence = new persistence_1.Persistence(this.getPersistenceTmpDir());
|
|
23
|
-
this._storageLayer = new files_1.StorageLayer(args.projectId, (0, utils_1.getRulesValidator)((resource) => this._rulesManager.getRuleset(resource)), (0, utils_1.getAdminCredentialValidator)(), this._persistence);
|
|
24
27
|
this._uploadService = new upload_1.UploadService(this._persistence);
|
|
28
|
+
const createStorageLayer = (rulesValidator) => {
|
|
29
|
+
return new files_1.StorageLayer(args.projectId, this._files, this._buckets, rulesValidator, (0, utils_1.getAdminCredentialValidator)(), this._persistence, this._cloudFunctions);
|
|
30
|
+
};
|
|
31
|
+
this._storageLayer = createStorageLayer((0, utils_1.getFirebaseRulesValidator)((resource) => this._rulesManager.getRuleset(resource)));
|
|
32
|
+
this._adminStorageLayer = createStorageLayer((0, utils_1.getAdminOnlyFirebaseRulesValidator)());
|
|
25
33
|
}
|
|
26
34
|
get storageLayer() {
|
|
27
35
|
return this._storageLayer;
|
|
28
36
|
}
|
|
37
|
+
get adminStorageLayer() {
|
|
38
|
+
return this._adminStorageLayer;
|
|
39
|
+
}
|
|
29
40
|
get uploadService() {
|
|
30
41
|
return this._uploadService;
|
|
31
42
|
}
|
|
@@ -36,7 +47,8 @@ class StorageEmulator {
|
|
|
36
47
|
return this._logger;
|
|
37
48
|
}
|
|
38
49
|
reset() {
|
|
39
|
-
this.
|
|
50
|
+
this._files.clear();
|
|
51
|
+
this._buckets.clear();
|
|
40
52
|
this._persistence.reset(this.getPersistenceTmpDir());
|
|
41
53
|
this._uploadService.reset();
|
|
42
54
|
}
|
|
@@ -70,6 +82,14 @@ class StorageEmulator {
|
|
|
70
82
|
getApp() {
|
|
71
83
|
return this._app;
|
|
72
84
|
}
|
|
85
|
+
async replaceRules(rules) {
|
|
86
|
+
await this._rulesManager.stop();
|
|
87
|
+
this._rulesManager = this.createRulesManager(rules);
|
|
88
|
+
return this._rulesManager.start();
|
|
89
|
+
}
|
|
90
|
+
createRulesManager(rules) {
|
|
91
|
+
return (0, manager_1.createStorageRulesManager)(rules, this._rulesRuntime);
|
|
92
|
+
}
|
|
73
93
|
getPersistenceTmpDir() {
|
|
74
94
|
return `${(0, os_1.tmpdir)()}/firebase/storage/blobs`;
|
|
75
95
|
}
|
|
@@ -44,7 +44,7 @@ class StoredFileMetadata {
|
|
|
44
44
|
throw new Error("Must pass bytes array or opts object with size, md5hash, and crc32c");
|
|
45
45
|
}
|
|
46
46
|
if (incomingMetadata) {
|
|
47
|
-
this.update(incomingMetadata);
|
|
47
|
+
this.update(incomingMetadata, false);
|
|
48
48
|
}
|
|
49
49
|
this.deleteFieldsSetAsNull();
|
|
50
50
|
this.setDownloadTokensFromCustomMetadata();
|
|
@@ -86,8 +86,10 @@ class StoredFileMetadata {
|
|
|
86
86
|
}
|
|
87
87
|
if (this.customMetadata.firebaseStorageDownloadTokens) {
|
|
88
88
|
this.downloadTokens = [
|
|
89
|
-
...
|
|
90
|
-
|
|
89
|
+
...new Set([
|
|
90
|
+
...this.downloadTokens,
|
|
91
|
+
...this.customMetadata.firebaseStorageDownloadTokens.split(","),
|
|
92
|
+
]),
|
|
91
93
|
];
|
|
92
94
|
delete this.customMetadata.firebaseStorageDownloadTokens;
|
|
93
95
|
}
|
|
@@ -115,7 +117,7 @@ class StoredFileMetadata {
|
|
|
115
117
|
});
|
|
116
118
|
}
|
|
117
119
|
}
|
|
118
|
-
update(incoming) {
|
|
120
|
+
update(incoming, shouldTrigger = true) {
|
|
119
121
|
if (incoming.contentDisposition) {
|
|
120
122
|
this.contentDisposition = incoming.contentDisposition;
|
|
121
123
|
}
|
|
@@ -143,15 +145,17 @@ class StoredFileMetadata {
|
|
|
143
145
|
}
|
|
144
146
|
this.setDownloadTokensFromCustomMetadata();
|
|
145
147
|
this.deleteFieldsSetAsNull();
|
|
146
|
-
|
|
148
|
+
if (shouldTrigger) {
|
|
149
|
+
this._cloudFunctions.dispatch("metadataUpdate", new CloudStorageObjectMetadata(this));
|
|
150
|
+
}
|
|
147
151
|
}
|
|
148
|
-
addDownloadToken() {
|
|
152
|
+
addDownloadToken(shouldTrigger = true) {
|
|
149
153
|
if (!this.downloadTokens.length) {
|
|
150
154
|
this.downloadTokens.push(uuid.v4());
|
|
151
155
|
return;
|
|
152
156
|
}
|
|
153
157
|
this.downloadTokens = [...this.downloadTokens, uuid.v4()];
|
|
154
|
-
this.update({});
|
|
158
|
+
this.update({}, shouldTrigger);
|
|
155
159
|
}
|
|
156
160
|
deleteDownloadToken(token) {
|
|
157
161
|
if (!this.downloadTokens.length) {
|
|
@@ -160,7 +164,7 @@ class StoredFileMetadata {
|
|
|
160
164
|
const remainingTokens = this.downloadTokens.filter((t) => t !== token);
|
|
161
165
|
this.downloadTokens = remainingTokens;
|
|
162
166
|
if (remainingTokens.length === 0) {
|
|
163
|
-
this.addDownloadToken();
|
|
167
|
+
this.addDownloadToken(false);
|
|
164
168
|
}
|
|
165
169
|
this.update({});
|
|
166
170
|
}
|
|
@@ -265,6 +269,12 @@ class CloudStorageObjectMetadata {
|
|
|
265
269
|
if (metadata.cacheControl) {
|
|
266
270
|
this.cacheControl = metadata.cacheControl;
|
|
267
271
|
}
|
|
272
|
+
if (metadata.contentDisposition) {
|
|
273
|
+
this.contentDisposition = metadata.contentDisposition;
|
|
274
|
+
}
|
|
275
|
+
if (metadata.contentEncoding) {
|
|
276
|
+
this.contentEncoding = metadata.contentEncoding;
|
|
277
|
+
}
|
|
268
278
|
if (metadata.customTime) {
|
|
269
279
|
this.customTime = toSerializedDate(metadata.customTime);
|
|
270
280
|
}
|