Convert the permissions tests over to mocha & chai.
Fixes #3188 Nothing of substance has really changed in the bodies of the tests themselves; they are the same except for some find & replace changes like: s/test.isTrue/chai.assert.isOk/g ...but they pass now. This also adds explicit imports & exports for the `SandstormPermissions` global, and adds a side-effect only import of another module to the tests. This was done now because: * Unless we pass `--full-app` to `meteor test`, meteor does not implicitly load all of our modules. * Passing `--full-app` runs into problems trying to start the smtp server on startup, because our code is written to assume it was passed FDs by the gateway, but `meteor test` is running it in a more conventional fashion. * It was easier to just fix the imports (which is what we want to do long term anyway) than to fight with `--full-app`. This also turns the tests back on in CI.
This commit is contained in:
1 parent
d366557c18
commit
010dcce62d
16 files changed
+884
-811
No files matched your search
@@ -4,8 +4,6 @@ on: [push, pull_request]
|
||||
|
||||
env:
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache"
|
||||
# TODO: remove this. See https://github.com/sandstorm-io/sandstorm/issues/3188
|
||||
SKIP_UNITTESTS: 1
|
||||
jobs:
|
||||
build_and_test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -43,3 +43,4 @@ google-oauth@1.3.0
|
||||
dynamic-import@0.5.1
|
||||
tap:i18n
|
||||
typescript@3.7.5
|
||||
meteortesting:mocha
|
||||
@@ -64,6 +64,9 @@ mdg:validation-error@0.5.1
|
||||
meteor@1.9.3
|
||||
meteor-base@1.4.0
|
||||
meteorspark:util@0.2.0
|
||||
meteortesting:browser-tests@1.3.3
|
||||
meteortesting:mocha@2.0.0
|
||||
meteortesting:mocha-core@7.0.1
|
||||
minifier-css@1.5.0
|
||||
minifier-js@2.6.0
|
||||
minimongo@1.5.0
|
||||
|
||||
@@ -1,796 +0,0 @@
|
||||
// Sandstorm - Personal Cloud Sandbox
|
||||
// Copyright (c) 2015 Sandstorm Development Group, Inc. and contributors
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// TODO(cleanup): These tests are no longer run in any automated fashion.
|
||||
// Figure out a new strategy for making sure we run tests.
|
||||
|
||||
import Crypto from "crypto";
|
||||
import { Meteor } from "meteor/meteor";
|
||||
import { Match, check } from "meteor/check";
|
||||
|
||||
const globalDb = new SandstormDb();
|
||||
// TODO(cleanup): Use a lightweight fake (minimongo-based?) database here and construct a clean
|
||||
// instance at the start of each test case.
|
||||
|
||||
const localizedTextPattern = {
|
||||
defaultText: String,
|
||||
localizations: Match.Optional([{ locale: String, text: String }]),
|
||||
};
|
||||
|
||||
const roleDefPattern = {
|
||||
title: Match.Optional(localizedTextPattern),
|
||||
verbPhrase: Match.Optional(localizedTextPattern),
|
||||
description: Match.Optional(localizedTextPattern),
|
||||
permissions: [Boolean],
|
||||
obsolete: Match.Optional(Boolean),
|
||||
default: Match.Optional(Boolean),
|
||||
};
|
||||
|
||||
const permissionDefPattern = {
|
||||
name: String,
|
||||
title: Match.Optional(localizedTextPattern),
|
||||
description: Match.Optional(localizedTextPattern),
|
||||
obsolete: Match.Optional(Boolean),
|
||||
};
|
||||
|
||||
const viewInfoPattern = {
|
||||
permissions: Match.Optional([permissionDefPattern]),
|
||||
roles: Match.Optional([roleDefPattern]),
|
||||
deniedPermissions: Match.Optional([Boolean]),
|
||||
matchRequests: Match.Optional(Object), // TODO
|
||||
matchOffers: Match.Optional(Object), // TODO
|
||||
};
|
||||
|
||||
class Grain {
|
||||
constructor(db, account, viewInfo, isPublic) {
|
||||
check(db, SandstormDb);
|
||||
check(account, Account);
|
||||
check(viewInfo, viewInfoPattern);
|
||||
this.viewInfo = viewInfo;
|
||||
this.id = Crypto.randomBytes(10).toString("hex");
|
||||
db.collections.grains.insert({
|
||||
_id: this.id,
|
||||
packageId: "mock-package-id",
|
||||
appId: "mock-app-id",
|
||||
appVersion: 0,
|
||||
userId: account.id,
|
||||
identityId: Crypto.randomBytes(10).toString("hex"),
|
||||
title: "mock-grain-title",
|
||||
cachedViewInfo: this.viewInfo,
|
||||
private: !isPublic,
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class Account {
|
||||
constructor(db, isAdmin) {
|
||||
check(db, SandstormDb);
|
||||
check(isAdmin, Boolean);
|
||||
|
||||
const name = Crypto.randomBytes(10).toString("hex");
|
||||
this.db = db;
|
||||
|
||||
this.id = Accounts.insertUserDoc(
|
||||
{ profile: { name: name }, },
|
||||
{ type: "account", loginCredentials: [], nonloginCredentials: [], isAdmin: isAdmin, });
|
||||
}
|
||||
|
||||
mayOpenGrain(grain) {
|
||||
check(grain, Grain);
|
||||
return SandstormPermissions.mayOpenGrain(globalDb, { grain: { _id: grain.id,
|
||||
accountId: this.id, }, });
|
||||
}
|
||||
|
||||
grainPermissions(grain) {
|
||||
check(grain, Grain);
|
||||
return SandstormPermissions.grainPermissions(globalDb,
|
||||
{ grain: { _id: grain.id,
|
||||
accountId: this.id, }, },
|
||||
grain.viewInfo).permissions;
|
||||
}
|
||||
|
||||
_shareTo(grainId, owner, roleAssignment, membraneRequirements) {
|
||||
return createNewTokenHelper(this.db, grainId, { accountId: this.id },
|
||||
owner, roleAssignment, membraneRequirements);
|
||||
}
|
||||
|
||||
shareToAccount(grain, recipient, roleAssignment, membraneRequirements) {
|
||||
check(grain, Grain);
|
||||
check(recipient, Account);
|
||||
return this._shareTo(grain.id, { user: { accountId: recipient.id, title: "share" } },
|
||||
roleAssignment, membraneRequirements);
|
||||
}
|
||||
|
||||
shareToWebkey(grain, roleAssignment, membraneRequirements) {
|
||||
check(grain, Grain);
|
||||
const result = this._shareTo(grain.id, { webkey: { forSharing: true } },
|
||||
roleAssignment, membraneRequirements);
|
||||
return new Webkey(this.db, result.token, result.id, grain);
|
||||
}
|
||||
}
|
||||
|
||||
function createNewTokenHelper(db, grainId, provider, owner, roleAssignment, membraneRequirements) {
|
||||
check(db, SandstormDb);
|
||||
check(grainId, String);
|
||||
check(roleAssignment, db.roleAssignmentPattern);
|
||||
const result = SandstormPermissions.createNewApiToken(db, provider, grainId, "<petname>",
|
||||
roleAssignment, owner);
|
||||
if (membraneRequirements) {
|
||||
membraneRequirements.forEach((requirement) => {
|
||||
db.collections.apiTokens.update(
|
||||
{ _id: result.id },
|
||||
{ $push: { requirements: requirement } });
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
class Webkey {
|
||||
constructor(db, rawToken, hashedToken, grain) {
|
||||
check(db, SandstormDb);
|
||||
check(rawToken, String);
|
||||
check(hashedToken, String);
|
||||
check(grain, Grain);
|
||||
this.db = db;
|
||||
this.rawToken = rawToken;
|
||||
this.hashedToken = hashedToken;
|
||||
this.grain = grain;
|
||||
}
|
||||
|
||||
mayOpenGrain() {
|
||||
return SandstormPermissions.mayOpenGrain(globalDb, { token: { _id: this.hashedToken,
|
||||
grainId: this.grain.id, }, });
|
||||
}
|
||||
|
||||
grainPermissions() {
|
||||
return SandstormPermissions.grainPermissions(globalDb,
|
||||
{ token: { _id: this.hashedToken,
|
||||
grainId: this.grain.id, }, },
|
||||
this.grain.viewInfo).permissions;
|
||||
}
|
||||
|
||||
_shareTo(owner, roleAssignment, membraneRequirements) {
|
||||
return createNewTokenHelper(this.db, this.grain.id, { rawParentToken: this.rawToken }, owner,
|
||||
roleAssignment, membraneRequirements);
|
||||
}
|
||||
|
||||
shareToAccount(recipient, roleAssignment, membraneRequirements) {
|
||||
check(recipient, Account);
|
||||
return this._shareTo({ user: { accountId: recipient.id, title: "share" } },
|
||||
roleAssignment, membraneRequirements);
|
||||
}
|
||||
|
||||
shareToWebkey(roleAssignment, membraneRequirements) {
|
||||
const result = this._shareTo({ webkey: { forSharing: true } },
|
||||
roleAssignment, membraneRequirements);
|
||||
return new Webkey(this.db, result.token, result.id, this.grain);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const commonViewInfo = {
|
||||
permissions: [{ name: "one" }, { name: "two" }, { name: "three" }],
|
||||
roles: [{ permissions: [true, true, true] }, // 0
|
||||
{ permissions: [true, false, false], default: true }, // 1
|
||||
{ permissions: [false, false, true] }, // 2
|
||||
{ permissions: [false, false, false] }, // 3
|
||||
{ permissions: [true, true, false] }, // 4
|
||||
{ permissions: [true, false, true] }, // 5
|
||||
{ permissions: [false, true, true] }, // 6
|
||||
],
|
||||
};
|
||||
|
||||
Tinytest.add("permissions: legacy public grain", function (test) {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
const grain = new Grain(globalDb, alice, commonViewInfo, true);
|
||||
|
||||
test.isTrue(alice.mayOpenGrain(grain));
|
||||
test.isTrue(bob.mayOpenGrain(grain));
|
||||
|
||||
// anonymous
|
||||
test.isTrue(
|
||||
SandstormPermissions.mayOpenGrain(globalDb, {
|
||||
grain: {
|
||||
_id: grain.id,
|
||||
accountId: null,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
test.equal(alice.grainPermissions(grain), [true, true, true]);
|
||||
test.equal(bob.grainPermissions(grain), [true, false, false]);
|
||||
|
||||
// anonymous
|
||||
test.equal(SandstormPermissions.grainPermissions(globalDb,
|
||||
{ grain: { _id: grain.id,
|
||||
accountId: null, }, },
|
||||
commonViewInfo).permissions,
|
||||
[true, false, false]);
|
||||
});
|
||||
|
||||
Tinytest.add("permissions: only owner may open private non-shared grain", function (test) {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
const carol = new Account(globalDb, false);
|
||||
const grain = new Grain(globalDb, alice, {});
|
||||
|
||||
test.isTrue(alice.mayOpenGrain(grain));
|
||||
test.isFalse(bob.mayOpenGrain(grain));
|
||||
test.isFalse(carol.mayOpenGrain(grain));
|
||||
});
|
||||
|
||||
Tinytest.add("permissions: owner gets all permissions", function (test) {
|
||||
const alice = new Account(globalDb, false);
|
||||
const grain = new Grain(globalDb, alice, commonViewInfo);
|
||||
|
||||
test.equal(alice.grainPermissions(grain), [true, true, true]);
|
||||
});
|
||||
|
||||
Tinytest.add("permissions: default role", function (test) {
|
||||
const alice = new Account(globalDb, false);
|
||||
const grain = new Grain(globalDb, alice, commonViewInfo);
|
||||
|
||||
const webkey = alice.shareToWebkey(grain, { none: null }, []);
|
||||
|
||||
test.isTrue(webkey.mayOpenGrain());
|
||||
test.equal(webkey.grainPermissions(), [true, false, false]);
|
||||
});
|
||||
|
||||
Tinytest.add("permissions: parentToken", function (test) {
|
||||
const alice = new Account(globalDb, false);
|
||||
const grain = new Grain(globalDb, alice, commonViewInfo);
|
||||
|
||||
const parent = alice.shareToWebkey(grain, { allAccess: null });
|
||||
|
||||
test.isTrue(parent.mayOpenGrain());
|
||||
test.equal(parent.grainPermissions(), [true, true, true]);
|
||||
|
||||
const child = parent.shareToWebkey({ roleId: 2 });
|
||||
|
||||
test.isTrue(child.mayOpenGrain());
|
||||
test.equal(child.grainPermissions(), [false, false, true]);
|
||||
|
||||
globalDb.collections.apiTokens.update(parent.hashedToken, { $set: { revoked: true } });
|
||||
|
||||
test.isFalse(parent.mayOpenGrain());
|
||||
test.isFalse(child.mayOpenGrain());
|
||||
});
|
||||
|
||||
Tinytest.add("permissions: merge user permissions", function (test) {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
const grain = new Grain(globalDb, alice, commonViewInfo);
|
||||
|
||||
const parent1 = alice.shareToWebkey(grain, { allAccess: null });
|
||||
parent1.shareToAccount(bob, { roleId: 1 });
|
||||
const parent2 = alice.shareToWebkey(grain, { allAccess: null });
|
||||
parent2.shareToAccount(bob, { roleId: 2 });
|
||||
|
||||
test.isTrue(bob.mayOpenGrain(grain));
|
||||
test.equal(bob.grainPermissions(grain), [true, false, true]);
|
||||
});
|
||||
|
||||
Tinytest.add("permissions: membrane requirements", function (test) {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
const carol = new Account(globalDb, false);
|
||||
const aliceGrain = new Grain(globalDb, alice, commonViewInfo);
|
||||
const bobGrain = new Grain(globalDb, bob, commonViewInfo);
|
||||
|
||||
const requirement = {
|
||||
permissionsHeld: {
|
||||
grainId: bobGrain.id,
|
||||
accountId: carol.id,
|
||||
permissions: [true, false, false],
|
||||
},
|
||||
};
|
||||
|
||||
const webkey = alice.shareToWebkey(aliceGrain, { allAccess: null }, [requirement]);
|
||||
|
||||
test.isFalse(webkey.mayOpenGrain());
|
||||
|
||||
const result = bob.shareToAccount(bobGrain, carol, { roleId: 1 });
|
||||
|
||||
test.isTrue(carol.mayOpenGrain(bobGrain));
|
||||
test.isTrue(webkey.mayOpenGrain());
|
||||
|
||||
globalDb.collections.apiTokens.update(result.id, { $set: { revoked: true } });
|
||||
|
||||
test.isFalse(webkey.mayOpenGrain());
|
||||
|
||||
const requirement1 = {
|
||||
permissionsHeld: {
|
||||
grainId: bobGrain.id,
|
||||
accountId: alice.id,
|
||||
permissions: [true, false, false],
|
||||
},
|
||||
};
|
||||
|
||||
bob.shareToAccount(bobGrain, carol, { roleId: 1 }, [requirement1]);
|
||||
|
||||
test.isFalse(webkey.mayOpenGrain());
|
||||
bob.shareToAccount(bobGrain, alice, { roleId: 1 });
|
||||
|
||||
test.isTrue(webkey.mayOpenGrain());
|
||||
});
|
||||
|
||||
Tinytest.add("permissions: membrane requirements sequence", function (test) {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
const carol = new Account(globalDb, false);
|
||||
const aliceGrain = new Grain(globalDb, alice, commonViewInfo);
|
||||
const bobGrain = new Grain(globalDb, bob, commonViewInfo);
|
||||
|
||||
const parentRequirement = {
|
||||
permissionsHeld: {
|
||||
grainId: bobGrain.id,
|
||||
accountId: carol.id,
|
||||
permissions: [true, false, false],
|
||||
},
|
||||
};
|
||||
|
||||
const parent = alice.shareToWebkey(aliceGrain, { allAccess: null }, [parentRequirement]);
|
||||
|
||||
const childRequirement = {
|
||||
permissionsHeld: {
|
||||
grainId: bobGrain.id,
|
||||
accountId: carol.id,
|
||||
permissions: [true, false, true],
|
||||
},
|
||||
};
|
||||
|
||||
const child = parent.shareToWebkey({ allAccess: null }, [childRequirement]);
|
||||
|
||||
test.isFalse(child.mayOpenGrain());
|
||||
|
||||
bob.shareToAccount(bobGrain, carol, { roleId: 1 });
|
||||
|
||||
test.isFalse(child.mayOpenGrain());
|
||||
|
||||
bob.shareToAccount(bobGrain, carol, { roleId: 2 });
|
||||
|
||||
test.isTrue(child.mayOpenGrain());
|
||||
});
|
||||
|
||||
Tinytest.add("permissions: membrane requirements loop", function (test) {
|
||||
// Create two tokens with membrane requirements that depend on each other.
|
||||
// A naive permissions computation could get into a loop here.
|
||||
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
const aliceGrain = new Grain(globalDb, alice, commonViewInfo);
|
||||
const bobGrain = new Grain(globalDb, bob, commonViewInfo);
|
||||
|
||||
const requirement1 = {
|
||||
permissionsHeld: {
|
||||
grainId: bobGrain.id,
|
||||
accountId: alice.id,
|
||||
permissions: [],
|
||||
},
|
||||
};
|
||||
|
||||
alice.shareToAccount(aliceGrain, bob, { allAccess: null }, [requirement1]);
|
||||
|
||||
const requirement2 = {
|
||||
permissionsHeld: {
|
||||
grainId: aliceGrain.id,
|
||||
accountId: bob.id,
|
||||
permissions: [],
|
||||
},
|
||||
};
|
||||
|
||||
bob.shareToAccount(bobGrain, alice, { allAccess: null }, [requirement2]);
|
||||
|
||||
test.isTrue(alice.mayOpenGrain(aliceGrain));
|
||||
test.isFalse(bob.mayOpenGrain(aliceGrain));
|
||||
test.isTrue(bob.mayOpenGrain(bobGrain));
|
||||
test.isFalse(alice.mayOpenGrain(bobGrain));
|
||||
|
||||
test.equal(alice.grainPermissions(bobGrain), null);
|
||||
test.equal(bob.grainPermissions(aliceGrain), null);
|
||||
});
|
||||
|
||||
Tinytest.add("permissions: membrane requirements nontrivial normalization", function (test) {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
const carol = new Account(globalDb, false);
|
||||
const aliceGrain = new Grain(globalDb, alice, commonViewInfo);
|
||||
|
||||
const requirement1 = {
|
||||
permissionsHeld: {
|
||||
grainId: aliceGrain.id,
|
||||
accountId: carol.id,
|
||||
permissions: [true, true, true],
|
||||
},
|
||||
};
|
||||
|
||||
const webkey = alice.shareToWebkey(aliceGrain, { roleId: 2 }, [requirement1]);
|
||||
|
||||
test.isFalse(webkey.mayOpenGrain());
|
||||
|
||||
const requirement2 = {
|
||||
permissionsHeld: {
|
||||
grainId: aliceGrain.id,
|
||||
accountId: bob.id,
|
||||
permissions: [true, true, false],
|
||||
},
|
||||
};
|
||||
|
||||
alice.shareToAccount(aliceGrain, carol, { roleId: 1 }, [requirement2]);
|
||||
test.isFalse(webkey.mayOpenGrain());
|
||||
|
||||
const requirement3 = {
|
||||
permissionsHeld: {
|
||||
grainId: aliceGrain.id,
|
||||
accountId: bob.id,
|
||||
permissions: [true, false, true],
|
||||
},
|
||||
};
|
||||
|
||||
alice.shareToAccount(aliceGrain, carol, { roleId: 2 }, [requirement3]);
|
||||
test.isFalse(webkey.mayOpenGrain());
|
||||
|
||||
const requirement4 = {
|
||||
permissionsHeld: {
|
||||
grainId: aliceGrain.id,
|
||||
accountId: bob.id,
|
||||
permissions: [true, true, true],
|
||||
},
|
||||
};
|
||||
|
||||
alice.shareToAccount(aliceGrain, carol, { roleId: 4 }, [requirement4]);
|
||||
test.isFalse(webkey.mayOpenGrain());
|
||||
|
||||
alice.shareToAccount(aliceGrain, bob, { roleId: 1 });
|
||||
test.isFalse(webkey.mayOpenGrain());
|
||||
|
||||
alice.shareToAccount(aliceGrain, bob, { roleId: 4 });
|
||||
test.isFalse(webkey.mayOpenGrain());
|
||||
|
||||
alice.shareToAccount(aliceGrain, bob, { allAccess: null });
|
||||
test.isTrue(webkey.mayOpenGrain());
|
||||
});
|
||||
|
||||
Tinytest.add("permissions: many membrane requirements", function (test) {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
|
||||
const grain = new Grain(globalDb, alice, commonViewInfo);
|
||||
const otherGrains = [];
|
||||
|
||||
const NUM_OTHER_GRAINS = 30;
|
||||
|
||||
for (let idx = 0; idx < NUM_OTHER_GRAINS; ++idx) {
|
||||
const otherGrain = new Grain(globalDb, alice, commonViewInfo);
|
||||
const requirement = {
|
||||
permissionsHeld: {
|
||||
grainId: otherGrain.id,
|
||||
accountId: bob.id,
|
||||
permissions: [],
|
||||
},
|
||||
};
|
||||
|
||||
alice.shareToAccount(grain, bob, { allAccess: null }, [requirement]);
|
||||
otherGrains.push(otherGrain);
|
||||
}
|
||||
|
||||
test.isFalse(bob.mayOpenGrain(grain));
|
||||
|
||||
alice.shareToAccount(otherGrains[0], bob, { allAccess: null });
|
||||
|
||||
test.isTrue(bob.mayOpenGrain(grain));
|
||||
});
|
||||
|
||||
Tinytest.add("permissions: membrane requirements long chain", function (test) {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
|
||||
const grains = [];
|
||||
|
||||
const NUM_GRAINS = 50;
|
||||
|
||||
for (let idx = 0; idx < NUM_GRAINS; ++idx) {
|
||||
grains.push(new Grain(globalDb, alice, commonViewInfo));
|
||||
}
|
||||
|
||||
// Bob's access to grain[i] is dependent on his access to grain[i+1];
|
||||
for (let idx = 0; idx < NUM_GRAINS - 1; ++idx) {
|
||||
const requirement = {
|
||||
permissionsHeld: {
|
||||
grainId: grains[idx + 1].id,
|
||||
accountId: bob.id,
|
||||
permissions: [],
|
||||
},
|
||||
};
|
||||
alice.shareToAccount(grains[idx], bob, { allAccess: null }, [requirement]);
|
||||
}
|
||||
|
||||
test.isFalse(bob.mayOpenGrain(grains[0]));
|
||||
|
||||
alice.shareToAccount(grains[grains.length - 1], bob, { allAccess: null });
|
||||
|
||||
test.isTrue(bob.mayOpenGrain(grains[0]));
|
||||
test.equal(bob.grainPermissions(grains[0]), [true, true, true]);
|
||||
});
|
||||
|
||||
function createViewInfo(numPermissions) {
|
||||
const permissionDefs = [];
|
||||
const roleDefs = [];
|
||||
|
||||
for (let ii = 0; ii < numPermissions; ++ii) {
|
||||
permissionDefs.push({ name: ii.toString() });
|
||||
const roleDefPermissions = [];
|
||||
for (let jj = 0; jj < numPermissions; ++jj) {
|
||||
roleDefPermissions.push(ii !== jj);
|
||||
}
|
||||
|
||||
const roleDef = { permissions: roleDefPermissions };
|
||||
if (ii == 0) {
|
||||
roleDef.default = true;
|
||||
}
|
||||
|
||||
roleDefs.push(roleDef);
|
||||
}
|
||||
|
||||
return { permissions: permissionDefs, roles: roleDefs };
|
||||
}
|
||||
|
||||
Tinytest.add("permissions: membrane requirements many permissions", function (test) {
|
||||
const NUM_PERMISSIONS = 25;
|
||||
const viewInfo = createViewInfo(NUM_PERMISSIONS);
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
|
||||
const grain0 = new Grain(globalDb, alice, viewInfo);
|
||||
const grain1 = new Grain(globalDb, alice, viewInfo);
|
||||
|
||||
const requirementPermissions = [];
|
||||
for (let idx = 0; idx < NUM_PERMISSIONS; ++idx) {
|
||||
requirementPermissions.push(idx % 2 == 0);
|
||||
}
|
||||
|
||||
const requirement = {
|
||||
permissionsHeld: {
|
||||
grainId: grain1.id,
|
||||
accountId: bob.id,
|
||||
permissions: requirementPermissions,
|
||||
},
|
||||
};
|
||||
|
||||
alice.shareToAccount(grain0, bob, { allAccess: null }, [requirement]);
|
||||
test.isFalse(bob.mayOpenGrain(grain0));
|
||||
for (let idx = 0; idx < NUM_PERMISSIONS; ++idx) {
|
||||
alice.shareToAccount(grain1, bob, { roleId: idx });
|
||||
}
|
||||
|
||||
test.isTrue(bob.mayOpenGrain(grain0));
|
||||
});
|
||||
|
||||
Tinytest.add("permissions: blow up disjunctive normal form", function (test) {
|
||||
// In a previous version of our permissions computation, the time this test took to complete
|
||||
// was at least exponential in `NUM_PERMISSIONS`, and effectively took forever if
|
||||
// `NUM_PERMISSIONS` was greater than 10.
|
||||
|
||||
const NUM_PERMISSIONS = 30;
|
||||
|
||||
const viewInfo = createViewInfo(NUM_PERMISSIONS);
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
|
||||
const grain1 = new Grain(globalDb, alice, viewInfo);
|
||||
const grain2 = new Grain(globalDb, alice, commonViewInfo);
|
||||
const allPermissions = new Array(NUM_PERMISSIONS);
|
||||
for (let idx = 0; idx < NUM_PERMISSIONS; ++idx) {
|
||||
allPermissions[idx] = true;
|
||||
}
|
||||
|
||||
const requirement = {
|
||||
permissionsHeld: {
|
||||
grainId: grain1.id,
|
||||
accountId: bob.id,
|
||||
permissions: allPermissions,
|
||||
},
|
||||
};
|
||||
|
||||
alice.shareToAccount(grain2, bob, { allAccess: null }, [requirement]);
|
||||
|
||||
test.isFalse(bob.mayOpenGrain(grain1));
|
||||
test.isFalse(bob.mayOpenGrain(grain2));
|
||||
|
||||
const otherGrains = [];
|
||||
|
||||
const NUM_OTHER_GRAINS = NUM_PERMISSIONS; // Also equals number of roles.
|
||||
|
||||
for (let idx = 0; idx < NUM_OTHER_GRAINS; ++idx) {
|
||||
const otherGrain = new Grain(globalDb, alice, commonViewInfo);
|
||||
const requirement = {
|
||||
permissionsHeld: {
|
||||
grainId: otherGrain.id,
|
||||
accountId: bob.id,
|
||||
permissions: [],
|
||||
},
|
||||
};
|
||||
|
||||
alice.shareToAccount(grain1, bob, { roleId: idx }, [requirement]);
|
||||
otherGrains.push(otherGrain);
|
||||
}
|
||||
|
||||
test.isFalse(bob.mayOpenGrain(grain1));
|
||||
test.isFalse(bob.mayOpenGrain(grain2));
|
||||
|
||||
alice.shareToAccount(otherGrains[0], bob, { allAccess: null });
|
||||
|
||||
test.isTrue(bob.mayOpenGrain(grain1));
|
||||
test.isFalse(bob.mayOpenGrain(grain2));
|
||||
|
||||
alice.shareToAccount(otherGrains[otherGrains.length - 1], bob, { allAccess: null });
|
||||
|
||||
test.isTrue(bob.mayOpenGrain(grain1));
|
||||
test.isTrue(bob.mayOpenGrain(grain2));
|
||||
});
|
||||
|
||||
Tinytest.add("permissions: userIsAdmin requirements", function (test) {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
const carol = new Account(globalDb, false);
|
||||
const aliceGrain = new Grain(globalDb, alice, commonViewInfo);
|
||||
const bobGrain = new Grain(globalDb, bob, commonViewInfo);
|
||||
|
||||
const requirement = { userIsAdmin: alice.id };
|
||||
const webkey = alice.shareToWebkey(aliceGrain, { allAccess: null }, [requirement]);
|
||||
|
||||
test.isFalse(webkey.mayOpenGrain());
|
||||
test.isFalse(!!webkey.grainPermissions());
|
||||
|
||||
Meteor.users.update({ _id: alice.id }, { $set: { isAdmin: true } });
|
||||
|
||||
test.isTrue(webkey.mayOpenGrain());
|
||||
test.equal(webkey.grainPermissions(), [true, true, true]);
|
||||
|
||||
const childWebkey = webkey.shareToWebkey({ allAccess: null }, [{ userIsAdmin: bob.id }]);
|
||||
|
||||
test.isFalse(childWebkey.mayOpenGrain());
|
||||
test.isFalse(!!childWebkey.grainPermissions());
|
||||
|
||||
Meteor.users.update({ _id: bob.id }, { $set: { isAdmin: true } });
|
||||
|
||||
test.isTrue(childWebkey.mayOpenGrain());
|
||||
test.equal(childWebkey.grainPermissions(), [true, true, true]);
|
||||
});
|
||||
|
||||
Tinytest.add("permissions: tokenValid requirements", function (test) {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
const aliceGrain = new Grain(globalDb, alice, commonViewInfo);
|
||||
const bobGrain = new Grain(globalDb, bob, commonViewInfo);
|
||||
|
||||
const tokenId = Crypto.randomBytes(20).toString("base64");
|
||||
const requirement = { tokenValid: tokenId };
|
||||
|
||||
const webkey = alice.shareToWebkey(aliceGrain, { allAccess: null }, [requirement]);
|
||||
|
||||
test.isFalse(webkey.mayOpenGrain());
|
||||
test.isFalse(!!webkey.grainPermissions());
|
||||
|
||||
globalDb.collections.apiTokens.insert({ _id: tokenId });
|
||||
|
||||
test.isTrue(webkey.mayOpenGrain());
|
||||
test.equal(webkey.grainPermissions(), [true, true, true]);
|
||||
|
||||
const childTokenId = Crypto.randomBytes(20).toString("base64");
|
||||
|
||||
globalDb.collections.apiTokens.insert({
|
||||
_id: childTokenId,
|
||||
parentToken: tokenId,
|
||||
requirements: [{
|
||||
permissionsHeld: {
|
||||
accountId: bob.id,
|
||||
grainId: aliceGrain.id,
|
||||
permissions: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const webkey2 = alice.shareToWebkey(aliceGrain, { allAccess: null },
|
||||
[{ tokenValid: childTokenId }]);
|
||||
|
||||
test.isFalse(webkey2.mayOpenGrain());
|
||||
test.isFalse(!!webkey2.grainPermissions());
|
||||
|
||||
alice.shareToAccount(aliceGrain, bob, { allAccess: null });
|
||||
|
||||
test.isTrue(webkey2.mayOpenGrain());
|
||||
test.equal(webkey2.grainPermissions(), [true, true, true]);
|
||||
|
||||
globalDb.collections.apiTokens.remove({ _id: tokenId });
|
||||
|
||||
test.isFalse(webkey2.mayOpenGrain());
|
||||
test.isFalse(!!webkey2.grainPermissions());
|
||||
});
|
||||
|
||||
Tinytest.add("permissions: collections app basic requirements", function (test) {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
const collectionGrain = new Grain(globalDb, alice, commonViewInfo);
|
||||
const otherGrain = new Grain(globalDb, alice, commonViewInfo);
|
||||
|
||||
alice.shareToAccount(collectionGrain, bob, { allAccess: null });
|
||||
|
||||
test.isTrue(bob.mayOpenGrain(collectionGrain));
|
||||
test.isFalse(bob.mayOpenGrain(otherGrain));
|
||||
|
||||
const webkey = alice.shareToWebkey(otherGrain, { allAccess: null },
|
||||
[
|
||||
{
|
||||
permissionsHeld: {
|
||||
permissions: [],
|
||||
accountId: alice.id,
|
||||
grainId: collectionGrain.id,
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
test.isTrue(bob.mayOpenGrain(collectionGrain));
|
||||
test.isFalse(bob.mayOpenGrain(otherGrain));
|
||||
|
||||
webkey.shareToAccount(bob, { allAccess: null },
|
||||
[
|
||||
{
|
||||
permissionsHeld: {
|
||||
permissions: [],
|
||||
accountId: bob.id,
|
||||
grainId: collectionGrain.id,
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
test.isTrue(bob.mayOpenGrain(collectionGrain));
|
||||
test.isTrue(bob.mayOpenGrain(otherGrain));
|
||||
});
|
||||
|
||||
Tinytest.add("permissions: permissionsHeld with tokenId", function (test) {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
const grain = new Grain(globalDb, alice, commonViewInfo);
|
||||
|
||||
const webkey = alice.shareToWebkey(grain, { allAccess: null });
|
||||
|
||||
test.isTrue(webkey.mayOpenGrain(grain));
|
||||
|
||||
alice.shareToAccount(grain, bob, { allAccess: null },
|
||||
[
|
||||
{
|
||||
permissionsHeld: {
|
||||
permissions: [],
|
||||
tokenId: webkey.hashedToken,
|
||||
grainId: grain.id,
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
test.isTrue(bob.mayOpenGrain(grain));
|
||||
|
||||
globalDb.collections.apiTokens.update(webkey.hashedToken, { $set: { revoked: true } });
|
||||
|
||||
test.isFalse(bob.mayOpenGrain(grain));
|
||||
});
|
||||
@@ -22,7 +22,7 @@ import { _ } from "meteor/underscore";
|
||||
|
||||
import { SandstormDb } from "/imports/sandstorm-db/db.js";
|
||||
|
||||
SandstormPermissions = {};
|
||||
export const SandstormPermissions = {};
|
||||
|
||||
class PermissionSet {
|
||||
// A wrapper around an array of booleans representing a set of permissions like "read" or
|
||||
|
||||
@@ -0,0 +1,812 @@
|
||||
// Sandstorm - Personal Cloud Sandbox
|
||||
// Copyright (c) 2015 Sandstorm Development Group, Inc. and contributors
|
||||
// All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/* eslint-env mocha */
|
||||
|
||||
import Crypto from "crypto";
|
||||
import { Meteor } from "meteor/meteor";
|
||||
import { Match, check } from "meteor/check";
|
||||
import { Accounts } from "meteor/accounts-base";
|
||||
import chai from "chai";
|
||||
|
||||
import { SandstormDb } from "/imports/sandstorm-db/db.js";
|
||||
// We import profile.js for the side-effect of defining more methods on SandstormDb.
|
||||
// TODO(cleanup): Avoid adding methods to an object from another module like this; ew.
|
||||
import {} from "/imports/sandstorm-db/profile.js";
|
||||
import { SandstormPermissions } from "/imports/sandstorm-permissions/permissions.js";
|
||||
|
||||
const globalDb = new SandstormDb();
|
||||
// TODO(cleanup): Use a lightweight fake (minimongo-based?) database here and construct a clean
|
||||
// instance at the start of each test case.
|
||||
|
||||
const localizedTextPattern = {
|
||||
defaultText: String,
|
||||
localizations: Match.Optional([{ locale: String, text: String }]),
|
||||
};
|
||||
|
||||
const roleDefPattern = {
|
||||
title: Match.Optional(localizedTextPattern),
|
||||
verbPhrase: Match.Optional(localizedTextPattern),
|
||||
description: Match.Optional(localizedTextPattern),
|
||||
permissions: [Boolean],
|
||||
obsolete: Match.Optional(Boolean),
|
||||
default: Match.Optional(Boolean),
|
||||
};
|
||||
|
||||
const permissionDefPattern = {
|
||||
name: String,
|
||||
title: Match.Optional(localizedTextPattern),
|
||||
description: Match.Optional(localizedTextPattern),
|
||||
obsolete: Match.Optional(Boolean),
|
||||
};
|
||||
|
||||
const viewInfoPattern = {
|
||||
permissions: Match.Optional([permissionDefPattern]),
|
||||
roles: Match.Optional([roleDefPattern]),
|
||||
deniedPermissions: Match.Optional([Boolean]),
|
||||
matchRequests: Match.Optional(Object), // TODO
|
||||
matchOffers: Match.Optional(Object), // TODO
|
||||
};
|
||||
|
||||
class Grain {
|
||||
constructor(db, account, viewInfo, isPublic) {
|
||||
check(db, SandstormDb);
|
||||
check(account, Account);
|
||||
check(viewInfo, viewInfoPattern);
|
||||
this.viewInfo = viewInfo;
|
||||
this.id = Crypto.randomBytes(10).toString("hex");
|
||||
db.collections.grains.insert({
|
||||
_id: this.id,
|
||||
packageId: "mock-package-id",
|
||||
appId: "mock-app-id",
|
||||
appVersion: 0,
|
||||
userId: account.id,
|
||||
identityId: Crypto.randomBytes(10).toString("hex"),
|
||||
title: "mock-grain-title",
|
||||
cachedViewInfo: this.viewInfo,
|
||||
private: !isPublic,
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class Account {
|
||||
constructor(db, isAdmin) {
|
||||
check(db, SandstormDb);
|
||||
check(isAdmin, Boolean);
|
||||
|
||||
const name = Crypto.randomBytes(10).toString("hex");
|
||||
this.db = db;
|
||||
|
||||
this.id = Accounts.insertUserDoc(
|
||||
{ profile: { name: name }, },
|
||||
{ type: "account", loginCredentials: [], nonloginCredentials: [], isAdmin: isAdmin, });
|
||||
}
|
||||
|
||||
mayOpenGrain(grain) {
|
||||
check(grain, Grain);
|
||||
return SandstormPermissions.mayOpenGrain(globalDb, { grain: { _id: grain.id,
|
||||
accountId: this.id, }, });
|
||||
}
|
||||
|
||||
grainPermissions(grain) {
|
||||
check(grain, Grain);
|
||||
return SandstormPermissions.grainPermissions(globalDb,
|
||||
{ grain: { _id: grain.id,
|
||||
accountId: this.id, }, },
|
||||
grain.viewInfo).permissions;
|
||||
}
|
||||
|
||||
_shareTo(grainId, owner, roleAssignment, membraneRequirements) {
|
||||
return createNewTokenHelper(this.db, grainId, { accountId: this.id },
|
||||
owner, roleAssignment, membraneRequirements);
|
||||
}
|
||||
|
||||
shareToAccount(grain, recipient, roleAssignment, membraneRequirements) {
|
||||
check(grain, Grain);
|
||||
check(recipient, Account);
|
||||
return this._shareTo(grain.id, { user: { accountId: recipient.id, title: "share" } },
|
||||
roleAssignment, membraneRequirements);
|
||||
}
|
||||
|
||||
shareToWebkey(grain, roleAssignment, membraneRequirements) {
|
||||
check(grain, Grain);
|
||||
const result = this._shareTo(grain.id, { webkey: { forSharing: true } },
|
||||
roleAssignment, membraneRequirements);
|
||||
return new Webkey(this.db, result.token, result.id, grain);
|
||||
}
|
||||
}
|
||||
|
||||
function createNewTokenHelper(db, grainId, provider, owner, roleAssignment, membraneRequirements) {
|
||||
check(db, SandstormDb);
|
||||
check(grainId, String);
|
||||
check(roleAssignment, db.roleAssignmentPattern);
|
||||
const result = SandstormPermissions.createNewApiToken(db, provider, grainId, "<petname>",
|
||||
roleAssignment, owner);
|
||||
if (membraneRequirements) {
|
||||
membraneRequirements.forEach((requirement) => {
|
||||
db.collections.apiTokens.update(
|
||||
{ _id: result.id },
|
||||
{ $push: { requirements: requirement } });
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
class Webkey {
|
||||
constructor(db, rawToken, hashedToken, grain) {
|
||||
check(db, SandstormDb);
|
||||
check(rawToken, String);
|
||||
check(hashedToken, String);
|
||||
check(grain, Grain);
|
||||
this.db = db;
|
||||
this.rawToken = rawToken;
|
||||
this.hashedToken = hashedToken;
|
||||
this.grain = grain;
|
||||
}
|
||||
|
||||
mayOpenGrain() {
|
||||
return SandstormPermissions.mayOpenGrain(globalDb, { token: { _id: this.hashedToken,
|
||||
grainId: this.grain.id, }, });
|
||||
}
|
||||
|
||||
grainPermissions() {
|
||||
return SandstormPermissions.grainPermissions(globalDb,
|
||||
{ token: { _id: this.hashedToken,
|
||||
grainId: this.grain.id, }, },
|
||||
this.grain.viewInfo).permissions;
|
||||
}
|
||||
|
||||
_shareTo(owner, roleAssignment, membraneRequirements) {
|
||||
return createNewTokenHelper(this.db, this.grain.id, { rawParentToken: this.rawToken }, owner,
|
||||
roleAssignment, membraneRequirements);
|
||||
}
|
||||
|
||||
shareToAccount(recipient, roleAssignment, membraneRequirements) {
|
||||
check(recipient, Account);
|
||||
return this._shareTo({ user: { accountId: recipient.id, title: "share" } },
|
||||
roleAssignment, membraneRequirements);
|
||||
}
|
||||
|
||||
shareToWebkey(roleAssignment, membraneRequirements) {
|
||||
const result = this._shareTo({ webkey: { forSharing: true } },
|
||||
roleAssignment, membraneRequirements);
|
||||
return new Webkey(this.db, result.token, result.id, this.grain);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const commonViewInfo = {
|
||||
permissions: [{ name: "one" }, { name: "two" }, { name: "three" }],
|
||||
roles: [{ permissions: [true, true, true] }, // 0
|
||||
{ permissions: [true, false, false], default: true }, // 1
|
||||
{ permissions: [false, false, true] }, // 2
|
||||
{ permissions: [false, false, false] }, // 3
|
||||
{ permissions: [true, true, false] }, // 4
|
||||
{ permissions: [true, false, true] }, // 5
|
||||
{ permissions: [false, true, true] }, // 6
|
||||
],
|
||||
};
|
||||
|
||||
// TODO(cleanup): The names of the tests below scan a bit poorly,
|
||||
// because mocha expects you to write things like it("Should ...", ...),
|
||||
// (which is why the function is called 'it'), but the names below are
|
||||
// a holdover from when we were using Tinytest. We should reword.
|
||||
describe("permissions", function() {
|
||||
it("legacy public grain", function () {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
const grain = new Grain(globalDb, alice, commonViewInfo, true);
|
||||
|
||||
chai.assert.isOk(alice.mayOpenGrain(grain));
|
||||
chai.assert.isOk(bob.mayOpenGrain(grain));
|
||||
|
||||
// anonymous
|
||||
chai.assert.isOk(
|
||||
SandstormPermissions.mayOpenGrain(globalDb, {
|
||||
grain: {
|
||||
_id: grain.id,
|
||||
accountId: null,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
chai.assert.deepEqual(alice.grainPermissions(grain), [true, true, true]);
|
||||
chai.assert.deepEqual(bob.grainPermissions(grain), [true, false, false]);
|
||||
|
||||
// anonymous
|
||||
chai.assert.deepEqual(
|
||||
SandstormPermissions.grainPermissions(
|
||||
globalDb,
|
||||
{ grain: { _id: grain.id,
|
||||
accountId: null, }, },
|
||||
commonViewInfo
|
||||
).permissions,
|
||||
[true, false, false]);
|
||||
});
|
||||
|
||||
it("only owner may open private non-shared grain", function () {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
const carol = new Account(globalDb, false);
|
||||
const grain = new Grain(globalDb, alice, {});
|
||||
|
||||
chai.assert.isOk(alice.mayOpenGrain(grain));
|
||||
chai.assert.isNotOk(bob.mayOpenGrain(grain));
|
||||
chai.assert.isNotOk(carol.mayOpenGrain(grain));
|
||||
});
|
||||
|
||||
it("owner gets all permissions", function () {
|
||||
const alice = new Account(globalDb, false);
|
||||
const grain = new Grain(globalDb, alice, commonViewInfo);
|
||||
|
||||
chai.assert.deepEqual(alice.grainPermissions(grain), [true, true, true]);
|
||||
});
|
||||
|
||||
it("default role", function () {
|
||||
const alice = new Account(globalDb, false);
|
||||
const grain = new Grain(globalDb, alice, commonViewInfo);
|
||||
|
||||
const webkey = alice.shareToWebkey(grain, { none: null }, []);
|
||||
|
||||
chai.assert.isOk(webkey.mayOpenGrain());
|
||||
chai.assert.deepEqual(webkey.grainPermissions(), [true, false, false]);
|
||||
});
|
||||
|
||||
it("parentToken", function () {
|
||||
const alice = new Account(globalDb, false);
|
||||
const grain = new Grain(globalDb, alice, commonViewInfo);
|
||||
|
||||
const parent = alice.shareToWebkey(grain, { allAccess: null });
|
||||
|
||||
chai.assert.isOk(parent.mayOpenGrain());
|
||||
chai.assert.deepEqual(parent.grainPermissions(), [true, true, true]);
|
||||
|
||||
const child = parent.shareToWebkey({ roleId: 2 });
|
||||
|
||||
chai.assert.isOk(child.mayOpenGrain());
|
||||
chai.assert.deepEqual(child.grainPermissions(), [false, false, true]);
|
||||
|
||||
globalDb.collections.apiTokens.update(parent.hashedToken, { $set: { revoked: true } });
|
||||
|
||||
chai.assert.isNotOk(parent.mayOpenGrain());
|
||||
chai.assert.isNotOk(child.mayOpenGrain());
|
||||
});
|
||||
|
||||
it("merge user permissions", function () {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
const grain = new Grain(globalDb, alice, commonViewInfo);
|
||||
|
||||
const parent1 = alice.shareToWebkey(grain, { allAccess: null });
|
||||
parent1.shareToAccount(bob, { roleId: 1 });
|
||||
const parent2 = alice.shareToWebkey(grain, { allAccess: null });
|
||||
parent2.shareToAccount(bob, { roleId: 2 });
|
||||
|
||||
chai.assert.isOk(bob.mayOpenGrain(grain));
|
||||
chai.assert.deepEqual(bob.grainPermissions(grain), [true, false, true]);
|
||||
});
|
||||
|
||||
it("membrane requirements", function () {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
const carol = new Account(globalDb, false);
|
||||
const aliceGrain = new Grain(globalDb, alice, commonViewInfo);
|
||||
const bobGrain = new Grain(globalDb, bob, commonViewInfo);
|
||||
|
||||
const requirement = {
|
||||
permissionsHeld: {
|
||||
grainId: bobGrain.id,
|
||||
accountId: carol.id,
|
||||
permissions: [true, false, false],
|
||||
},
|
||||
};
|
||||
|
||||
const webkey = alice.shareToWebkey(aliceGrain, { allAccess: null }, [requirement]);
|
||||
|
||||
chai.assert.isNotOk(webkey.mayOpenGrain());
|
||||
|
||||
const result = bob.shareToAccount(bobGrain, carol, { roleId: 1 });
|
||||
|
||||
chai.assert.isOk(carol.mayOpenGrain(bobGrain));
|
||||
chai.assert.isOk(webkey.mayOpenGrain());
|
||||
|
||||
globalDb.collections.apiTokens.update(result.id, { $set: { revoked: true } });
|
||||
|
||||
chai.assert.isNotOk(webkey.mayOpenGrain());
|
||||
|
||||
const requirement1 = {
|
||||
permissionsHeld: {
|
||||
grainId: bobGrain.id,
|
||||
accountId: alice.id,
|
||||
permissions: [true, false, false],
|
||||
},
|
||||
};
|
||||
|
||||
bob.shareToAccount(bobGrain, carol, { roleId: 1 }, [requirement1]);
|
||||
|
||||
chai.assert.isNotOk(webkey.mayOpenGrain());
|
||||
bob.shareToAccount(bobGrain, alice, { roleId: 1 });
|
||||
|
||||
chai.assert.isOk(webkey.mayOpenGrain());
|
||||
});
|
||||
|
||||
it("membrane requirements sequence", function () {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
const carol = new Account(globalDb, false);
|
||||
const aliceGrain = new Grain(globalDb, alice, commonViewInfo);
|
||||
const bobGrain = new Grain(globalDb, bob, commonViewInfo);
|
||||
|
||||
const parentRequirement = {
|
||||
permissionsHeld: {
|
||||
grainId: bobGrain.id,
|
||||
accountId: carol.id,
|
||||
permissions: [true, false, false],
|
||||
},
|
||||
};
|
||||
|
||||
const parent = alice.shareToWebkey(aliceGrain, { allAccess: null }, [parentRequirement]);
|
||||
|
||||
const childRequirement = {
|
||||
permissionsHeld: {
|
||||
grainId: bobGrain.id,
|
||||
accountId: carol.id,
|
||||
permissions: [true, false, true],
|
||||
},
|
||||
};
|
||||
|
||||
const child = parent.shareToWebkey({ allAccess: null }, [childRequirement]);
|
||||
|
||||
chai.assert.isNotOk(child.mayOpenGrain());
|
||||
|
||||
bob.shareToAccount(bobGrain, carol, { roleId: 1 });
|
||||
|
||||
chai.assert.isNotOk(child.mayOpenGrain());
|
||||
|
||||
bob.shareToAccount(bobGrain, carol, { roleId: 2 });
|
||||
|
||||
chai.assert.isOk(child.mayOpenGrain());
|
||||
});
|
||||
|
||||
it("membrane requirements loop", function () {
|
||||
// Create two tokens with membrane requirements that depend on each other.
|
||||
// A naive permissions computation could get into a loop here.
|
||||
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
const aliceGrain = new Grain(globalDb, alice, commonViewInfo);
|
||||
const bobGrain = new Grain(globalDb, bob, commonViewInfo);
|
||||
|
||||
const requirement1 = {
|
||||
permissionsHeld: {
|
||||
grainId: bobGrain.id,
|
||||
accountId: alice.id,
|
||||
permissions: [],
|
||||
},
|
||||
};
|
||||
|
||||
alice.shareToAccount(aliceGrain, bob, { allAccess: null }, [requirement1]);
|
||||
|
||||
const requirement2 = {
|
||||
permissionsHeld: {
|
||||
grainId: aliceGrain.id,
|
||||
accountId: bob.id,
|
||||
permissions: [],
|
||||
},
|
||||
};
|
||||
|
||||
bob.shareToAccount(bobGrain, alice, { allAccess: null }, [requirement2]);
|
||||
|
||||
chai.assert.isOk(alice.mayOpenGrain(aliceGrain));
|
||||
chai.assert.isNotOk(bob.mayOpenGrain(aliceGrain));
|
||||
chai.assert.isOk(bob.mayOpenGrain(bobGrain));
|
||||
chai.assert.isNotOk(alice.mayOpenGrain(bobGrain));
|
||||
|
||||
chai.assert.deepEqual(alice.grainPermissions(bobGrain), null);
|
||||
chai.assert.deepEqual(bob.grainPermissions(aliceGrain), null);
|
||||
});
|
||||
|
||||
it("membrane requirements nontrivial normalization", function () {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
const carol = new Account(globalDb, false);
|
||||
const aliceGrain = new Grain(globalDb, alice, commonViewInfo);
|
||||
|
||||
const requirement1 = {
|
||||
permissionsHeld: {
|
||||
grainId: aliceGrain.id,
|
||||
accountId: carol.id,
|
||||
permissions: [true, true, true],
|
||||
},
|
||||
};
|
||||
|
||||
const webkey = alice.shareToWebkey(aliceGrain, { roleId: 2 }, [requirement1]);
|
||||
|
||||
chai.assert.isNotOk(webkey.mayOpenGrain());
|
||||
|
||||
const requirement2 = {
|
||||
permissionsHeld: {
|
||||
grainId: aliceGrain.id,
|
||||
accountId: bob.id,
|
||||
permissions: [true, true, false],
|
||||
},
|
||||
};
|
||||
|
||||
alice.shareToAccount(aliceGrain, carol, { roleId: 1 }, [requirement2]);
|
||||
chai.assert.isNotOk(webkey.mayOpenGrain());
|
||||
|
||||
const requirement3 = {
|
||||
permissionsHeld: {
|
||||
grainId: aliceGrain.id,
|
||||
accountId: bob.id,
|
||||
permissions: [true, false, true],
|
||||
},
|
||||
};
|
||||
|
||||
alice.shareToAccount(aliceGrain, carol, { roleId: 2 }, [requirement3]);
|
||||
chai.assert.isNotOk(webkey.mayOpenGrain());
|
||||
|
||||
const requirement4 = {
|
||||
permissionsHeld: {
|
||||
grainId: aliceGrain.id,
|
||||
accountId: bob.id,
|
||||
permissions: [true, true, true],
|
||||
},
|
||||
};
|
||||
|
||||
alice.shareToAccount(aliceGrain, carol, { roleId: 4 }, [requirement4]);
|
||||
chai.assert.isNotOk(webkey.mayOpenGrain());
|
||||
|
||||
alice.shareToAccount(aliceGrain, bob, { roleId: 1 });
|
||||
chai.assert.isNotOk(webkey.mayOpenGrain());
|
||||
|
||||
alice.shareToAccount(aliceGrain, bob, { roleId: 4 });
|
||||
chai.assert.isNotOk(webkey.mayOpenGrain());
|
||||
|
||||
alice.shareToAccount(aliceGrain, bob, { allAccess: null });
|
||||
chai.assert.isOk(webkey.mayOpenGrain());
|
||||
});
|
||||
|
||||
it("many membrane requirements", function () {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
|
||||
const grain = new Grain(globalDb, alice, commonViewInfo);
|
||||
const otherGrains = [];
|
||||
|
||||
const NUM_OTHER_GRAINS = 30;
|
||||
|
||||
for (let idx = 0; idx < NUM_OTHER_GRAINS; ++idx) {
|
||||
const otherGrain = new Grain(globalDb, alice, commonViewInfo);
|
||||
const requirement = {
|
||||
permissionsHeld: {
|
||||
grainId: otherGrain.id,
|
||||
accountId: bob.id,
|
||||
permissions: [],
|
||||
},
|
||||
};
|
||||
|
||||
alice.shareToAccount(grain, bob, { allAccess: null }, [requirement]);
|
||||
otherGrains.push(otherGrain);
|
||||
}
|
||||
|
||||
chai.assert.isNotOk(bob.mayOpenGrain(grain));
|
||||
|
||||
alice.shareToAccount(otherGrains[0], bob, { allAccess: null });
|
||||
|
||||
chai.assert.isOk(bob.mayOpenGrain(grain));
|
||||
});
|
||||
|
||||
it("membrane requirements long chain", function () {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
|
||||
const grains = [];
|
||||
|
||||
const NUM_GRAINS = 50;
|
||||
|
||||
for (let idx = 0; idx < NUM_GRAINS; ++idx) {
|
||||
grains.push(new Grain(globalDb, alice, commonViewInfo));
|
||||
}
|
||||
|
||||
// Bob's access to grain[i] is dependent on his access to grain[i+1];
|
||||
for (let idx = 0; idx < NUM_GRAINS - 1; ++idx) {
|
||||
const requirement = {
|
||||
permissionsHeld: {
|
||||
grainId: grains[idx + 1].id,
|
||||
accountId: bob.id,
|
||||
permissions: [],
|
||||
},
|
||||
};
|
||||
alice.shareToAccount(grains[idx], bob, { allAccess: null }, [requirement]);
|
||||
}
|
||||
|
||||
chai.assert.isNotOk(bob.mayOpenGrain(grains[0]));
|
||||
|
||||
alice.shareToAccount(grains[grains.length - 1], bob, { allAccess: null });
|
||||
|
||||
chai.assert.isOk(bob.mayOpenGrain(grains[0]));
|
||||
chai.assert.deepEqual(bob.grainPermissions(grains[0]), [true, true, true]);
|
||||
});
|
||||
|
||||
function createViewInfo(numPermissions) {
|
||||
const permissionDefs = [];
|
||||
const roleDefs = [];
|
||||
|
||||
for (let ii = 0; ii < numPermissions; ++ii) {
|
||||
permissionDefs.push({ name: ii.toString() });
|
||||
const roleDefPermissions = [];
|
||||
for (let jj = 0; jj < numPermissions; ++jj) {
|
||||
roleDefPermissions.push(ii !== jj);
|
||||
}
|
||||
|
||||
const roleDef = { permissions: roleDefPermissions };
|
||||
if (ii == 0) {
|
||||
roleDef.default = true;
|
||||
}
|
||||
|
||||
roleDefs.push(roleDef);
|
||||
}
|
||||
|
||||
return { permissions: permissionDefs, roles: roleDefs };
|
||||
}
|
||||
|
||||
it("membrane requirements many permissions", function () {
|
||||
const NUM_PERMISSIONS = 25;
|
||||
const viewInfo = createViewInfo(NUM_PERMISSIONS);
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
|
||||
const grain0 = new Grain(globalDb, alice, viewInfo);
|
||||
const grain1 = new Grain(globalDb, alice, viewInfo);
|
||||
|
||||
const requirementPermissions = [];
|
||||
for (let idx = 0; idx < NUM_PERMISSIONS; ++idx) {
|
||||
requirementPermissions.push(idx % 2 == 0);
|
||||
}
|
||||
|
||||
const requirement = {
|
||||
permissionsHeld: {
|
||||
grainId: grain1.id,
|
||||
accountId: bob.id,
|
||||
permissions: requirementPermissions,
|
||||
},
|
||||
};
|
||||
|
||||
alice.shareToAccount(grain0, bob, { allAccess: null }, [requirement]);
|
||||
chai.assert.isNotOk(bob.mayOpenGrain(grain0));
|
||||
for (let idx = 0; idx < NUM_PERMISSIONS; ++idx) {
|
||||
alice.shareToAccount(grain1, bob, { roleId: idx });
|
||||
}
|
||||
|
||||
chai.assert.isOk(bob.mayOpenGrain(grain0));
|
||||
});
|
||||
|
||||
it("blow up disjunctive normal form", function () {
|
||||
// In a previous version of our permissions computation, the time this test took to complete
|
||||
// was at least exponential in `NUM_PERMISSIONS`, and effectively took forever if
|
||||
// `NUM_PERMISSIONS` was greater than 10.
|
||||
|
||||
const NUM_PERMISSIONS = 30;
|
||||
|
||||
const viewInfo = createViewInfo(NUM_PERMISSIONS);
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
|
||||
const grain1 = new Grain(globalDb, alice, viewInfo);
|
||||
const grain2 = new Grain(globalDb, alice, commonViewInfo);
|
||||
const allPermissions = new Array(NUM_PERMISSIONS);
|
||||
for (let idx = 0; idx < NUM_PERMISSIONS; ++idx) {
|
||||
allPermissions[idx] = true;
|
||||
}
|
||||
|
||||
const requirement = {
|
||||
permissionsHeld: {
|
||||
grainId: grain1.id,
|
||||
accountId: bob.id,
|
||||
permissions: allPermissions,
|
||||
},
|
||||
};
|
||||
|
||||
alice.shareToAccount(grain2, bob, { allAccess: null }, [requirement]);
|
||||
|
||||
chai.assert.isNotOk(bob.mayOpenGrain(grain1));
|
||||
chai.assert.isNotOk(bob.mayOpenGrain(grain2));
|
||||
|
||||
const otherGrains = [];
|
||||
|
||||
const NUM_OTHER_GRAINS = NUM_PERMISSIONS; // Also equals number of roles.
|
||||
|
||||
for (let idx = 0; idx < NUM_OTHER_GRAINS; ++idx) {
|
||||
const otherGrain = new Grain(globalDb, alice, commonViewInfo);
|
||||
const requirement = {
|
||||
permissionsHeld: {
|
||||
grainId: otherGrain.id,
|
||||
accountId: bob.id,
|
||||
permissions: [],
|
||||
},
|
||||
};
|
||||
|
||||
alice.shareToAccount(grain1, bob, { roleId: idx }, [requirement]);
|
||||
otherGrains.push(otherGrain);
|
||||
}
|
||||
|
||||
chai.assert.isNotOk(bob.mayOpenGrain(grain1));
|
||||
chai.assert.isNotOk(bob.mayOpenGrain(grain2));
|
||||
|
||||
alice.shareToAccount(otherGrains[0], bob, { allAccess: null });
|
||||
|
||||
chai.assert.isOk(bob.mayOpenGrain(grain1));
|
||||
chai.assert.isNotOk(bob.mayOpenGrain(grain2));
|
||||
|
||||
alice.shareToAccount(otherGrains[otherGrains.length - 1], bob, { allAccess: null });
|
||||
|
||||
chai.assert.isOk(bob.mayOpenGrain(grain1));
|
||||
chai.assert.isOk(bob.mayOpenGrain(grain2));
|
||||
});
|
||||
|
||||
it("userIsAdmin requirements", function () {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
const carol = new Account(globalDb, false);
|
||||
const aliceGrain = new Grain(globalDb, alice, commonViewInfo);
|
||||
const bobGrain = new Grain(globalDb, bob, commonViewInfo);
|
||||
|
||||
const requirement = { userIsAdmin: alice.id };
|
||||
const webkey = alice.shareToWebkey(aliceGrain, { allAccess: null }, [requirement]);
|
||||
|
||||
chai.assert.isNotOk(webkey.mayOpenGrain());
|
||||
chai.assert.isNotOk(!!webkey.grainPermissions());
|
||||
|
||||
Meteor.users.update({ _id: alice.id }, { $set: { isAdmin: true } });
|
||||
|
||||
chai.assert.isOk(webkey.mayOpenGrain());
|
||||
chai.assert.deepEqual(webkey.grainPermissions(), [true, true, true]);
|
||||
|
||||
const childWebkey = webkey.shareToWebkey({ allAccess: null }, [{ userIsAdmin: bob.id }]);
|
||||
|
||||
chai.assert.isNotOk(childWebkey.mayOpenGrain());
|
||||
chai.assert.isNotOk(!!childWebkey.grainPermissions());
|
||||
|
||||
Meteor.users.update({ _id: bob.id }, { $set: { isAdmin: true } });
|
||||
|
||||
chai.assert.isOk(childWebkey.mayOpenGrain());
|
||||
chai.assert.deepEqual(childWebkey.grainPermissions(), [true, true, true]);
|
||||
});
|
||||
|
||||
it("tokenValid requirements", function () {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
const aliceGrain = new Grain(globalDb, alice, commonViewInfo);
|
||||
const bobGrain = new Grain(globalDb, bob, commonViewInfo);
|
||||
|
||||
const tokenId = Crypto.randomBytes(20).toString("base64");
|
||||
const requirement = { tokenValid: tokenId };
|
||||
|
||||
const webkey = alice.shareToWebkey(aliceGrain, { allAccess: null }, [requirement]);
|
||||
|
||||
chai.assert.isNotOk(webkey.mayOpenGrain());
|
||||
chai.assert.isNotOk(!!webkey.grainPermissions());
|
||||
|
||||
globalDb.collections.apiTokens.insert({ _id: tokenId });
|
||||
|
||||
chai.assert.isOk(webkey.mayOpenGrain());
|
||||
chai.assert.deepEqual(webkey.grainPermissions(), [true, true, true]);
|
||||
|
||||
const childTokenId = Crypto.randomBytes(20).toString("base64");
|
||||
|
||||
globalDb.collections.apiTokens.insert({
|
||||
_id: childTokenId,
|
||||
parentToken: tokenId,
|
||||
requirements: [{
|
||||
permissionsHeld: {
|
||||
accountId: bob.id,
|
||||
grainId: aliceGrain.id,
|
||||
permissions: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const webkey2 = alice.shareToWebkey(aliceGrain, { allAccess: null },
|
||||
[{ tokenValid: childTokenId }]);
|
||||
|
||||
chai.assert.isNotOk(webkey2.mayOpenGrain());
|
||||
chai.assert.isNotOk(!!webkey2.grainPermissions());
|
||||
|
||||
alice.shareToAccount(aliceGrain, bob, { allAccess: null });
|
||||
|
||||
chai.assert.isOk(webkey2.mayOpenGrain());
|
||||
chai.assert.deepEqual(webkey2.grainPermissions(), [true, true, true]);
|
||||
|
||||
globalDb.collections.apiTokens.remove({ _id: tokenId });
|
||||
|
||||
chai.assert.isNotOk(webkey2.mayOpenGrain());
|
||||
chai.assert.isNotOk(!!webkey2.grainPermissions());
|
||||
});
|
||||
|
||||
it("collections app basic requirements", function () {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
const collectionGrain = new Grain(globalDb, alice, commonViewInfo);
|
||||
const otherGrain = new Grain(globalDb, alice, commonViewInfo);
|
||||
|
||||
alice.shareToAccount(collectionGrain, bob, { allAccess: null });
|
||||
|
||||
chai.assert.isOk(bob.mayOpenGrain(collectionGrain));
|
||||
chai.assert.isNotOk(bob.mayOpenGrain(otherGrain));
|
||||
|
||||
const webkey = alice.shareToWebkey(otherGrain, { allAccess: null },
|
||||
[
|
||||
{
|
||||
permissionsHeld: {
|
||||
permissions: [],
|
||||
accountId: alice.id,
|
||||
grainId: collectionGrain.id,
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
chai.assert.isOk(bob.mayOpenGrain(collectionGrain));
|
||||
chai.assert.isNotOk(bob.mayOpenGrain(otherGrain));
|
||||
|
||||
webkey.shareToAccount(bob, { allAccess: null },
|
||||
[
|
||||
{
|
||||
permissionsHeld: {
|
||||
permissions: [],
|
||||
accountId: bob.id,
|
||||
grainId: collectionGrain.id,
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
chai.assert.isOk(bob.mayOpenGrain(collectionGrain));
|
||||
chai.assert.isOk(bob.mayOpenGrain(otherGrain));
|
||||
});
|
||||
|
||||
it("permissionsHeld with tokenId", function () {
|
||||
const alice = new Account(globalDb, false);
|
||||
const bob = new Account(globalDb, false);
|
||||
const grain = new Grain(globalDb, alice, commonViewInfo);
|
||||
|
||||
const webkey = alice.shareToWebkey(grain, { allAccess: null });
|
||||
|
||||
chai.assert.isOk(webkey.mayOpenGrain(grain));
|
||||
|
||||
alice.shareToAccount(grain, bob, { allAccess: null },
|
||||
[
|
||||
{
|
||||
permissionsHeld: {
|
||||
permissions: [],
|
||||
tokenId: webkey.hashedToken,
|
||||
grainId: grain.id,
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
|
||||
chai.assert.isOk(bob.mayOpenGrain(grain));
|
||||
|
||||
globalDb.collections.apiTokens.update(webkey.hashedToken, { $set: { revoked: true } });
|
||||
|
||||
chai.assert.isNotOk(bob.mayOpenGrain(grain));
|
||||
});
|
||||
});
|
||||
@@ -21,6 +21,7 @@ import { Meteor } from "meteor/meteor";
|
||||
import { check } from "meteor/check";
|
||||
import { _ } from "meteor/underscore";
|
||||
|
||||
import { SandstormPermissions } from "/imports/sandstorm-permissions/permissions.js";
|
||||
import Capnp from "/imports/server/capnp.js";
|
||||
|
||||
import { waitPromise } from '../server/async-helpers.js';
|
||||
|
||||
@@ -19,6 +19,7 @@ import { HTTP } from "meteor/http";
|
||||
|
||||
import { SandstormDb } from "/imports/sandstorm-db/db.js";
|
||||
import { globalDb } from "/imports/db-deprecated.js";
|
||||
import { SandstormPermissions } from "/imports/sandstorm-permissions/permissions.js";
|
||||
import { FrontendRefRegistry } from "/imports/server/frontend-ref.js";
|
||||
import { PersistentImpl } from "/imports/server/persistent.js";
|
||||
import { migrateToLatest } from "/imports/server/migrations.js";
|
||||
|
||||
@@ -26,6 +26,7 @@ import { Random } from "meteor/random";
|
||||
import Capnp from "/imports/server/capnp.js";
|
||||
import { SandstormDb } from "/imports/sandstorm-db/db.js";
|
||||
import { globalDb } from "/imports/db-deprecated.js";
|
||||
import { SandstormPermissions } from "/imports/sandstorm-permissions/permissions.js";
|
||||
|
||||
import { makeHackSessionContext } from "/imports/server/hack-session.js";
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import { send as sendEmail } from "/imports/server/email.js";
|
||||
import { inMeteor, waitPromise } from "/imports/server/async-helpers.js";
|
||||
import { SandstormDb } from "/imports/sandstorm-db/db.js";
|
||||
import { globalDb } from "/imports/db-deprecated.js";
|
||||
import { SandstormPermissions } from "/imports/sandstorm-permissions/permissions.js";
|
||||
|
||||
const ROOT_URL = process.env.ROOT_URL;
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import { _ } from "meteor/underscore";
|
||||
|
||||
import { SandstormDb } from "/imports/sandstorm-db/db.js";
|
||||
import { globalDb } from "/imports/db-deprecated.js";
|
||||
import { SandstormPermissions } from "/imports/sandstorm-permissions/permissions.js";
|
||||
import { PersistentImpl } from "/imports/server/persistent.js";
|
||||
import { StaticAssetImpl, IdenticonStaticAssetImpl } from "/imports/server/static-asset.js";
|
||||
import Capnp from "/imports/server/capnp.js";
|
||||
|
||||
@@ -22,6 +22,7 @@ import { Random } from "meteor/random";
|
||||
import Crypto from "crypto";
|
||||
import { inMeteor } from "/imports/server/async-helpers.js";
|
||||
import Capnp from "/imports/server/capnp.js";
|
||||
import { SandstormPermissions } from "/imports/sandstorm-permissions/permissions.js";
|
||||
|
||||
const privateDb = Symbol("PersistentImpl.db");
|
||||
const privateTemplate = Symbol("PersistentImpl.template");
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
import { Meteor } from "meteor/meteor";
|
||||
import { globalDb } from "/imports/db-deprecated.js";
|
||||
import { SandstormPermissions } from "/imports/sandstorm-permissions/permissions.js";
|
||||
|
||||
const ROOT_URL = process.env.ROOT_URL;
|
||||
|
||||
|
||||
Generated
+53
@@ -287,6 +287,12 @@
|
||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
||||
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
|
||||
},
|
||||
"assertion-error": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
|
||||
"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
|
||||
"dev": true
|
||||
},
|
||||
"astral-regex": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
|
||||
@@ -416,6 +422,20 @@
|
||||
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
|
||||
"dev": true
|
||||
},
|
||||
"chai": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
|
||||
"integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"assertion-error": "^1.1.0",
|
||||
"check-error": "^1.0.2",
|
||||
"deep-eql": "^3.0.1",
|
||||
"get-func-name": "^2.0.0",
|
||||
"pathval": "^1.1.0",
|
||||
"type-detect": "^4.0.5"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
@@ -433,6 +453,12 @@
|
||||
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
|
||||
"dev": true
|
||||
},
|
||||
"check-error": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
|
||||
"integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
|
||||
"dev": true
|
||||
},
|
||||
"cli-cursor": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz",
|
||||
@@ -525,6 +551,15 @@
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"deep-eql": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
|
||||
"integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"type-detect": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"deep-is": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
|
||||
@@ -850,6 +885,12 @@
|
||||
"integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
|
||||
"dev": true
|
||||
},
|
||||
"get-func-name": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
|
||||
"integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
|
||||
"dev": true
|
||||
},
|
||||
"glob": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
|
||||
@@ -1479,6 +1520,12 @@
|
||||
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
|
||||
"dev": true
|
||||
},
|
||||
"pathval": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
|
||||
"integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
|
||||
"dev": true
|
||||
},
|
||||
"precond": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz",
|
||||
@@ -1848,6 +1895,12 @@
|
||||
"prelude-ls": "~1.1.2"
|
||||
}
|
||||
},
|
||||
"type-detect": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
||||
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
|
||||
"dev": true
|
||||
},
|
||||
"type-fest": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.1.0",
|
||||
"chai": "^4.2.0",
|
||||
"eslint": "^6.8.0"
|
||||
}
|
||||
}
|
||||
+6
-12
@@ -1,14 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Runs `meteor test-packages`. The tests run on a fresh isolated Mongo instance.
|
||||
# Runs `meteor test`. The tests run on a fresh isolated Mongo instance.
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
if ! $(which "spacejam" >/dev/null 2>/dev/null) ; then
|
||||
echo "Couldn't find executable 'spacejam' - try installing the package from npm with 'sudo -H npm install -g spacejam'?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
cd "$SCRIPT_DIR"
|
||||
|
||||
@@ -56,12 +51,11 @@ __EOF__
|
||||
# over its own bundled version, and the system gyp doesn't work.
|
||||
export PYTHONPATH=$("$SCRIPT_DIR/../find-meteor-dev-bundle.sh")/lib/node_modules/npm/node_modules/node-gyp/gyp/pylib
|
||||
|
||||
# To test interactively through a browser:
|
||||
#meteor test-packages --settings $SETTINGS ./packages/sandstorm-permissions
|
||||
|
||||
# Terrible hack: Make capnp.node available to tests running under spacejam.
|
||||
# Terrible hack: Make capnp.node available to tests.
|
||||
rm -f /tmp/node_modules
|
||||
ln -s "$(dirname "$PWD")/node_modules" /tmp/node_modules
|
||||
|
||||
# To test on the command line:
|
||||
exec spacejam test-packages --settings $SETTINGS ./packages/sandstorm-permissions
|
||||
meteor test \
|
||||
--once \
|
||||
--driver-package=meteortesting:mocha \
|
||||
--settings "$SETTINGS"
|
||||
Reference in new issue
Block a user