mirror of
https://github.com/status-im/realm-js.git
synced 2025-01-09 22:05:57 +00:00
Permissions api (#1244)
* Add basic permissions skeleton * ... * Update permissions api * Wait for server to process management realm, add offer api * Fix test of apply and get permissions, add offer test * Accept permission offer * Accept permission offer test * Invalidate permission offer * Add basic docs (still need some links etc) * Refactor tests * Only run permission tests where sync is enabled * Use legal syntax for user name generation * Add changelog entry * Streamline permission tests * Fix casing for access level names etc. * Add basic definitions to index.d.ts * Use settimeout for resolving promise from listener * Complete typescript defs * Improve docs * Allow 'any' as default recipient in getGrantedPermissions * Fix getSpecialPurposeRealm on iOS * Response to PR comments * Respond to PR comments * Fix offer description * Skip permission tests in chrome debugger :-/
This commit is contained in:
parent
f7bf9df6e5
commit
402bf48f88
@ -8,6 +8,7 @@ X.Y.Z Release notes
|
|||||||
* Added property `Realm.isInTransaction` which indicates if write transaction is in progress.
|
* Added property `Realm.isInTransaction` which indicates if write transaction is in progress.
|
||||||
* Added `shouldCompactOnLaunch` to configuration (#507).
|
* Added `shouldCompactOnLaunch` to configuration (#507).
|
||||||
* Added `Realm.compact()` for manually compacting Realm files.
|
* Added `Realm.compact()` for manually compacting Realm files.
|
||||||
|
* Added various methods for permission management (#1204).
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
* None
|
* None
|
||||||
|
54
docs/sync.js
54
docs/sync.js
@ -242,6 +242,60 @@ class User {
|
|||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
retrieveAccount(provider, username) {}
|
retrieveAccount(provider, username) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asynchronously retrieves all permissions associated with the user calling this method.
|
||||||
|
* @param {string} recipient the optional recipient of the permission. Can be either
|
||||||
|
* 'any' which is the default, or 'currentUser' or 'otherUser' if you want only permissions
|
||||||
|
* belonging to the user or *not* belonging to the user.
|
||||||
|
* @returns {Results} a queryable collection of permission objects that provides detailed
|
||||||
|
* information regarding the granted access.
|
||||||
|
* The collection is a live query similar to what you would get by callig Realm.objects,
|
||||||
|
* so the same features apply - you can listen for notifications or filter it.
|
||||||
|
*/
|
||||||
|
getGrantedPermissions(recipient) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes the permissions of a Realm.
|
||||||
|
* @param {object} condition - A condition that will be used to match existing users against.
|
||||||
|
* This should be an object, containing either the key 'userId', or 'metadataKey' and 'metadataValue'.
|
||||||
|
* @param {string} realmUrl - The path to the Realm that you want to apply permissions to.
|
||||||
|
* @param {string} accessLevel - The access level you want to set: 'none', 'read', 'write' or 'admin'.
|
||||||
|
* @returns {Promise} a Promise that, upon completion, indicates that the permissions have been
|
||||||
|
* successfully applied by the server. It will be resolved with the
|
||||||
|
* {@link PermissionChange PermissionChange} object that refers to the applied permission.
|
||||||
|
*/
|
||||||
|
applyPermissions(condition, realmUrl, accessLevel) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates a token that can be used for sharing a Realm.
|
||||||
|
* @param {string} realmUrl - The Realm URL whose permissions settings should be changed. Use * to change
|
||||||
|
* the permissions of all Realms managed by this user.
|
||||||
|
* @param {string} accessLevel - The access level to grant matching users. Note that the access level
|
||||||
|
* setting is additive, i.e. you cannot revoke permissions for users who previously had a higher access level.
|
||||||
|
* Can be 'read', 'write' or 'admin'.
|
||||||
|
* @param {Date} [expiresAt] - Optional expiration date of the offer. If set to null, the offer doesn't expire.
|
||||||
|
* @returns {string} - A token that can be shared with another user, e.g. via email or message and then consumed by
|
||||||
|
* User.acceptPermissionOffer to obtain permissions to a Realm.
|
||||||
|
*/
|
||||||
|
offerPermissions(realmUrl, accessLevel, expiresAt) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consumes a token generated by {@link Realm#Sync#User#offerPermissions offerPermissions} to obtain permissions to a shared Realm.
|
||||||
|
* @param {string} token - The token, generated by User.offerPermissions
|
||||||
|
* @returns {string} The url of the Realm that the token has granted permissions to.
|
||||||
|
*/
|
||||||
|
acceptPermissionOffer(token) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates a permission offer.
|
||||||
|
* Invalidating an offer prevents new users from consuming its token. It doesn't revoke any permissions that have
|
||||||
|
* already been granted.
|
||||||
|
* @param {string|PermissionOffer} permissionOfferOrToken - Either the token or the entire
|
||||||
|
* {@link PermissionOffer PermissionOffer} object that was generated with
|
||||||
|
* {@link Realm#Sync#User#offerPermissions offerPermissions}.
|
||||||
|
*/
|
||||||
|
invalidatePermissionOffer(permissionOfferOrToken) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
41
lib/index.d.ts
vendored
41
lib/index.d.ts
vendored
@ -271,6 +271,47 @@ declare namespace Realm.Sync {
|
|||||||
logout(): void;
|
logout(): void;
|
||||||
openManagementRealm(): Realm;
|
openManagementRealm(): Realm;
|
||||||
retrieveAccount(provider: string, username: string): Promise<Account>;
|
retrieveAccount(provider: string, username: string): Promise<Account>;
|
||||||
|
|
||||||
|
getGrantedPermissions(recipient: 'any' | 'currentUser' | 'otherUser'): Results<Permission>;
|
||||||
|
applyPermissions(condition: PermissionCondition, realmUrl: string, accessLevel: AccessLevel): Promise<PermissionChange>;
|
||||||
|
offerPermissions(realmUrl: string, accessLevel: AccessLevel, expiresAt?: Date): Promise<string>;
|
||||||
|
acceptPermissionOffer(token: string): Promise<string>
|
||||||
|
invalidatePermissionOffer(permissionOfferOrToken: PermissionOffer | string): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
type PermissionCondition =
|
||||||
|
{ [object_type: string]: userId } |
|
||||||
|
{ [object_type: string]: metadataKey, [object_type: string]: metadataValue };
|
||||||
|
|
||||||
|
type AccessLevel = 'none' | 'read' | 'write' | 'admin';
|
||||||
|
|
||||||
|
class PermissionChange {
|
||||||
|
id: string;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
statusCode?: number;
|
||||||
|
statusMessage?: string;
|
||||||
|
userId: string;
|
||||||
|
metadataKey?: string;
|
||||||
|
metadataValue?: string;
|
||||||
|
realmUrl: string;
|
||||||
|
mayRead?: boolean;
|
||||||
|
mayWrite?: boolean;
|
||||||
|
mayManage?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PermissionOffer {
|
||||||
|
id: string;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
statusCode?: number;
|
||||||
|
statusMessage?: string;
|
||||||
|
token?: string;
|
||||||
|
realmUrl: string;
|
||||||
|
mayRead?: boolean;
|
||||||
|
mayWrite?: boolean;
|
||||||
|
mayManage?: boolean;
|
||||||
|
expiresAt?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SyncConfiguration {
|
interface SyncConfiguration {
|
||||||
|
@ -28,6 +28,8 @@ module.exports = [
|
|||||||
statusCode: { type: 'int', optional: true },
|
statusCode: { type: 'int', optional: true },
|
||||||
statusMessage: { type: 'string', optional: true },
|
statusMessage: { type: 'string', optional: true },
|
||||||
userId: { type: 'string' },
|
userId: { type: 'string' },
|
||||||
|
metadataKey: { type: 'string', optional: true },
|
||||||
|
metadataValue: { type: 'string', optional: true },
|
||||||
realmUrl: { type: 'string' },
|
realmUrl: { type: 'string' },
|
||||||
mayRead: { type: 'bool', optional: true },
|
mayRead: { type: 'bool', optional: true },
|
||||||
mayWrite: { type: 'bool', optional: true },
|
mayWrite: { type: 'bool', optional: true },
|
||||||
|
274
lib/permission-api.js
Normal file
274
lib/permission-api.js
Normal file
@ -0,0 +1,274 @@
|
|||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Copyright 2017 Realm Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const url_parse = require('url-parse');
|
||||||
|
const managementSchema = require('./management-schema');
|
||||||
|
|
||||||
|
function generateUniqueId() {
|
||||||
|
const uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||||
|
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissionSchema = [
|
||||||
|
{
|
||||||
|
name: 'Permission',
|
||||||
|
properties: {
|
||||||
|
id: { type: 'string' },
|
||||||
|
updatedAt: { type: 'date' },
|
||||||
|
userId: { type: 'string' },
|
||||||
|
path: { type: 'string' },
|
||||||
|
mayRead: { type: 'bool' },
|
||||||
|
mayWrite: { type: 'bool' },
|
||||||
|
mayManage: { type: 'bool' },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Symbols are not supported on RN yet, so we use this for now:
|
||||||
|
const specialPurposeRealmsKey = '_specialPurposeRealms';
|
||||||
|
|
||||||
|
function getSpecialPurposeRealm(user, realmName, schema) {
|
||||||
|
if (!user.hasOwnProperty(specialPurposeRealmsKey)) {
|
||||||
|
user[specialPurposeRealmsKey] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user[specialPurposeRealmsKey].hasOwnProperty(realmName)) {
|
||||||
|
return Promise.resolve(user[specialPurposeRealmsKey][realmName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = url_parse(user.server);
|
||||||
|
if (url.protocol === 'http:') {
|
||||||
|
url.set('protocol', 'realm:');
|
||||||
|
} else if (url.protocol === 'https:') {
|
||||||
|
url.set('protocol', 'realms:');
|
||||||
|
} else {
|
||||||
|
throw new Error(`Unexpected user auth url: ${user.server}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
url.set('pathname', `/~/${realmName}`);
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
schema: schema,
|
||||||
|
sync: {
|
||||||
|
user,
|
||||||
|
url: url.href
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const _Realm = user.constructor._realmConstructor;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
_Realm._waitForDownload(config, (error) => {
|
||||||
|
|
||||||
|
// FIXME: I don't understand why, but removing the following setTimeout causes the subsequent
|
||||||
|
// setTimeout call (when resolving the promise) to hang on RN iOS.
|
||||||
|
// This might be related to our general makeCallback issue: #1255.
|
||||||
|
setTimeout(() => {}, 1);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
try {
|
||||||
|
let syncedRealm = new _Realm(config);
|
||||||
|
user[specialPurposeRealmsKey][realmName] = syncedRealm;
|
||||||
|
//FIXME: RN hangs here. Remove when node's makeCallback alternative is implemented (#1255)
|
||||||
|
setTimeout(() => resolve(syncedRealm), 1);
|
||||||
|
} catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createInManagementRealm(user, modelName, modelInitializer) {
|
||||||
|
return getSpecialPurposeRealm(user, '__management', managementSchema)
|
||||||
|
.then(managementRealm => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
let o;
|
||||||
|
|
||||||
|
const listener = () => {
|
||||||
|
if (!o) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusCode = o.statusCode;
|
||||||
|
if (typeof statusCode === 'number') {
|
||||||
|
managementRealm.removeListener('change', listener);
|
||||||
|
|
||||||
|
if (statusCode === 0) {
|
||||||
|
setTimeout(() => resolve(o), 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const e = new Error(o.statusMessage);
|
||||||
|
e.statusCode = statusCode;
|
||||||
|
e.managementObject = o;
|
||||||
|
setTimeout(() => reject(e), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
managementRealm.addListener('change', listener);
|
||||||
|
|
||||||
|
managementRealm.write(() => {
|
||||||
|
o = managementRealm.create(modelName, modelInitializer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
reject(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const accessLevels = ['none', 'read', 'write', 'admin'];
|
||||||
|
const offerAccessLevels = ['read', 'write', 'admin'];
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getGrantedPermissions(recipient) {
|
||||||
|
if (recipient && ['currentUser', 'otherUser', 'any'].indexOf(recipient) === -1) {
|
||||||
|
return Promise.reject(new Error(`'${recipient}' is not a valid recipient type. Must be 'any', 'currentUser' or 'otherUser'.`));
|
||||||
|
}
|
||||||
|
|
||||||
|
return getSpecialPurposeRealm(this, '__permission', permissionSchema)
|
||||||
|
.then(permissionRealm => {
|
||||||
|
let permissions = permissionRealm.objects('Permission')
|
||||||
|
.filtered('NOT path ENDSWITH "__permission" AND NOT path ENDSWITH "__management"');
|
||||||
|
|
||||||
|
if (recipient === 'currentUser') {
|
||||||
|
permissions = permissions.filtered('userId = $0', this.identity);
|
||||||
|
}
|
||||||
|
else if (recipient === 'otherUser') {
|
||||||
|
permissions = permissions.filtered('userId != $0', this.identity);
|
||||||
|
}
|
||||||
|
return permissions;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
applyPermissions(condition, realmUrl, accessLevel) {
|
||||||
|
if (!realmUrl) {
|
||||||
|
return Promise.reject(new Error('realmUrl must be specified'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accessLevels.indexOf(accessLevel) === -1) {
|
||||||
|
return Promise.reject(new Error(`'${accessLevel}' is not a valid access level. Must be ${accessLevels.join(', ')}.`));
|
||||||
|
}
|
||||||
|
|
||||||
|
const mayRead = accessLevel === 'read' || accessLevel === 'write' || accessLevel === 'admin';
|
||||||
|
const mayWrite = accessLevel === 'write' || accessLevel === 'admin';
|
||||||
|
const mayManage = accessLevel === 'admin';
|
||||||
|
|
||||||
|
const permissionChange = {
|
||||||
|
id: generateUniqueId(),
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
realmUrl,
|
||||||
|
mayRead,
|
||||||
|
mayWrite,
|
||||||
|
mayManage
|
||||||
|
};
|
||||||
|
|
||||||
|
if (condition.hasOwnProperty('userId')) {
|
||||||
|
permissionChange.userId = condition.userId;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
permissionChange.userId = '';
|
||||||
|
permissionChange.metadataKey = condition.metadataKey;
|
||||||
|
permissionChange.metadataValue = condition.metadataValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return createInManagementRealm(this, 'PermissionChange', permissionChange);
|
||||||
|
},
|
||||||
|
|
||||||
|
offerPermissions(realmUrl, accessLevel, expiresAt) {
|
||||||
|
if (!realmUrl) {
|
||||||
|
return Promise.reject(new Error('realmUrl must be specified'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offerAccessLevels.indexOf(accessLevel) === -1) {
|
||||||
|
return Promise.reject(new Error(`'${accessLevel}' is not a valid access level. Must be ${offerAccessLevels.join(', ')}.`));
|
||||||
|
}
|
||||||
|
|
||||||
|
const mayRead = true;
|
||||||
|
const mayWrite = accessLevel === 'write' || accessLevel === 'admin';
|
||||||
|
const mayManage = accessLevel === 'admin';
|
||||||
|
|
||||||
|
const permissionOffer = {
|
||||||
|
id: generateUniqueId(),
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
expiresAt,
|
||||||
|
realmUrl,
|
||||||
|
mayRead,
|
||||||
|
mayWrite,
|
||||||
|
mayManage
|
||||||
|
};
|
||||||
|
|
||||||
|
return createInManagementRealm(this, 'PermissionOffer', permissionOffer)
|
||||||
|
.then(appliedOffer => appliedOffer.token);
|
||||||
|
},
|
||||||
|
|
||||||
|
acceptPermissionOffer(token) {
|
||||||
|
if (!token) {
|
||||||
|
return Promise.reject(new Error('Offer token must be specified'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissionOfferResponse = {
|
||||||
|
id: generateUniqueId(),
|
||||||
|
createdAt: new Date(),
|
||||||
|
updatedAt: new Date(),
|
||||||
|
token
|
||||||
|
};
|
||||||
|
|
||||||
|
return createInManagementRealm(this, 'PermissionOfferResponse', permissionOfferResponse)
|
||||||
|
.then(appliedReponse => appliedReponse.realmUrl);
|
||||||
|
},
|
||||||
|
|
||||||
|
invalidatePermissionOffer(permissionOfferOrToken) {
|
||||||
|
return getSpecialPurposeRealm(this, '__management', managementSchema)
|
||||||
|
.then(managementRealm => {
|
||||||
|
let permissionOffer;
|
||||||
|
|
||||||
|
if (typeof permissionOfferOrToken === 'string') {
|
||||||
|
// We were given a token, not an object. Find the matching object.
|
||||||
|
const q = managementRealm.objects('PermissionOffer')
|
||||||
|
.filtered('token = $0', permissionOfferOrToken);
|
||||||
|
|
||||||
|
if (q.length === 0) {
|
||||||
|
throw new Error("No permission offers with the given token were found");
|
||||||
|
}
|
||||||
|
|
||||||
|
permissionOffer = q[0];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
permissionOffer = permissionOfferOrToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
managementRealm.write(() => {
|
||||||
|
permissionOffer.expiresAt = new Date();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const AuthError = require('./errors').AuthError;
|
const AuthError = require('./errors').AuthError;
|
||||||
|
const permissionApis = require('./permission-api');
|
||||||
|
|
||||||
function node_require(module) {
|
function node_require(module) {
|
||||||
return require(module);
|
return require(module);
|
||||||
@ -139,8 +140,7 @@ function _authenticate(userConstructor, server, json, callback) {
|
|||||||
.catch(callback);
|
.catch(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
const staticMethods = {
|
||||||
static: {
|
|
||||||
get current() {
|
get current() {
|
||||||
const allUsers = this.all;
|
const allUsers = this.all;
|
||||||
const keys = Object.keys(allUsers);
|
const keys = Object.keys(allUsers);
|
||||||
@ -208,8 +208,9 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
_refreshAccessToken: refreshAccessToken
|
_refreshAccessToken: refreshAccessToken
|
||||||
},
|
};
|
||||||
instance: {
|
|
||||||
|
const instanceMethods = {
|
||||||
openManagementRealm() {
|
openManagementRealm() {
|
||||||
let url = url_parse(this.server);
|
let url = url_parse(this.server);
|
||||||
if (url.protocol === 'http:') {
|
if (url.protocol === 'http:') {
|
||||||
@ -257,5 +258,12 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
};
|
||||||
|
|
||||||
|
// Append the permission apis
|
||||||
|
Object.assign(instanceMethods, permissionApis);
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
static: staticMethods,
|
||||||
|
instance: instanceMethods
|
||||||
};
|
};
|
@ -40,6 +40,12 @@ if (!(typeof process === 'object' && process.platform === 'win32')) {
|
|||||||
if (Realm.Sync) {
|
if (Realm.Sync) {
|
||||||
TESTS.UserTests = require('./user-tests');
|
TESTS.UserTests = require('./user-tests');
|
||||||
TESTS.SessionTests = require('./session-tests');
|
TESTS.SessionTests = require('./session-tests');
|
||||||
|
|
||||||
|
// FIXME: Permission tests currently fail in chrome debugging mode.
|
||||||
|
if (typeof navigator === 'undefined' ||
|
||||||
|
!/Chrome/.test(navigator.userAgent)) { // eslint-disable-line no-undef
|
||||||
|
TESTS.PermissionTests = require('./permission-tests');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function node_require(module) { return require(module); }
|
function node_require(module) { return require(module); }
|
||||||
|
115
tests/js/permission-tests.js
Normal file
115
tests/js/permission-tests.js
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Copyright 2017 Realm Inc.
|
||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
var Realm = require('realm');
|
||||||
|
var TestCase = require('./asserts');
|
||||||
|
|
||||||
|
function uuid() {
|
||||||
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
|
||||||
|
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createUsersWithTestRealms(count) {
|
||||||
|
const createUserWithTestRealm = username => new Promise((resolve, reject) => {
|
||||||
|
Realm.Sync.User.register('http://localhost:9080', username, 'password', (error, user) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
new Realm({ sync: { user, url: 'realm://localhost:9080/~/test'}}).close();
|
||||||
|
resolve(user);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate some usernames
|
||||||
|
const usernames = new Array(count).fill(undefined).map(uuid);
|
||||||
|
|
||||||
|
// And turn them into users and realms
|
||||||
|
const userPromises = usernames.map(createUserWithTestRealm);
|
||||||
|
return Promise.all(userPromises);
|
||||||
|
}
|
||||||
|
|
||||||
|
function wait(t) {
|
||||||
|
return new Promise(resolve => setTimeout(resolve, t));
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
testApplyAndGetGrantedPermissions() {
|
||||||
|
return createUsersWithTestRealms(1)
|
||||||
|
.then(([user]) => {
|
||||||
|
return user.applyPermissions({ userId: '*' }, `/${user.identity}/test`, 'read')
|
||||||
|
.then(() => user.getGrantedPermissions('any'))
|
||||||
|
.then(permissions => {
|
||||||
|
TestCase.assertEqual(permissions[1].path, `/${user.identity}/test`);
|
||||||
|
TestCase.assertEqual(permissions[1].mayRead, true);
|
||||||
|
TestCase.assertEqual(permissions[1].mayWrite, false);
|
||||||
|
TestCase.assertEqual(permissions[1].mayManage, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
testOfferPermissions() {
|
||||||
|
return createUsersWithTestRealms(2)
|
||||||
|
.then(([user1, user2]) => {
|
||||||
|
return user1.offerPermissions(`/${user1.identity}/test`, 'read')
|
||||||
|
.then(token => user2.acceptPermissionOffer(token))
|
||||||
|
.then(realmUrl => {
|
||||||
|
TestCase.assertEqual(realmUrl, `/${user1.identity}/test`);
|
||||||
|
return user2.getGrantedPermissions('any')
|
||||||
|
.then(permissions => {
|
||||||
|
TestCase.assertEqual(permissions[1].path, `/${user1.identity}/test`);
|
||||||
|
TestCase.assertEqual(permissions[1].mayRead, true);
|
||||||
|
TestCase.assertEqual(permissions[1].mayWrite, false);
|
||||||
|
TestCase.assertEqual(permissions[1].mayManage, false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
testInvalidatePermissionOffer() {
|
||||||
|
return createUsersWithTestRealms(2)
|
||||||
|
.then(([user1, user2]) => {
|
||||||
|
user1.offerPermissions(`/${user1.identity}/test`, 'read')
|
||||||
|
.then((token) => {
|
||||||
|
return user1.invalidatePermissionOffer(token)
|
||||||
|
// Since we don't yet support notification when the invalidation has gone through,
|
||||||
|
// wait for a bit and hope the server is done processing.
|
||||||
|
.then(wait(100))
|
||||||
|
.then(user2.acceptPermissionOffer(token))
|
||||||
|
// We want the call to fail, i.e. the catch() below should be called.
|
||||||
|
.then(() => { throw new Error("User was able to accept an invalid permission offer token"); })
|
||||||
|
.catch(error => {
|
||||||
|
try {
|
||||||
|
TestCase.assertEqual(error.message, 'The permission offer is expired.');
|
||||||
|
TestCase.assertEqual(error.statusCode, 701);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
throw new Error(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -51,7 +51,7 @@ async function runTests() {
|
|||||||
await runTest(suiteName, testName);
|
await runTest(suiteName, testName);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
itemTest.ele('error', {'message': ''}, e.message);
|
itemTest.ele('error', {'message': e.message, 'stacktrace': e.stack}, e.toString());
|
||||||
nbrFailures++;
|
nbrFailures++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user