Home Reference Source Test

test/PolicyEngine.spec.js

import chai from 'chai';
import chaiAsPromised from 'chai-as-promised';

chai.config.truncateThreshold = 0;

let expect = chai.expect;
chai.use(chaiAsPromised);

import AdvancedCondition from '../src/policy/conditions/AdvancedCondition';
import AllowOverrides from '../src/policy/combiningAlgorithms/AllowOverrides';
import Condition from '../src/policy/conditions/Condition';
import PEP from '../src/policy/PEP';
import Rule from '../src/policy/Rule';
import RuntimeCoreCtx from '../src/policy/context/RuntimeCoreCtx';
import Policy from '../src/policy/Policy';
import SubscriptionCondition from '../src/policy/conditions/SubscriptionCondition';
import { runtimeFactory } from './resources/runtimeFactory';

/********** CONDITIONS **********/
let simpleCondition = new Condition('source', 'equals', 'user1@domain1');
let andCondition = new AdvancedCondition(['and', new Condition('source', 'equals', 'user1@domain1'), new Condition('domain', 'equals', 'domain1')]);
let orCondition = new AdvancedCondition(['or', new Condition('source', 'equals', 'user1@domain1'), new Condition('domain', 'equals', 'domain1')]);
let notCondition = new AdvancedCondition(['not', new Condition('source', 'equals', 'user2@domain2')]);
let andNotCondition = new AdvancedCondition(['and', new Condition('source', 'equals', 'user1@domain1'), ['not', new Condition('domain', 'equals', 'domain1')]]);
let orNotCondition = new AdvancedCondition(['or', ['not', new Condition('domain', 'equals', 'domain1')], new Condition('source', 'equals', 'user1@domain1')]);

/********** RULES **********/
let simpleRule = new Rule(false, simpleCondition, 'global', 'global', 0);
let acceptAnySubscriptionRule = new Rule(true, new SubscriptionCondition('subscription', 'equals', '*'), 'global', 'global', 0);
let acceptPreAuthSubscriptionRule = new Rule(true, new SubscriptionCondition('subscription', 'in', 'preauthorised'), 'global', 'global', 0);
let blockAnySubscriptionRule = new Rule(false, new SubscriptionCondition('subscription', 'equals', '*'), 'global', 'global', 0);
let blockPreAuthSubscriptionRule = new Rule(false, new AdvancedCondition(['not', new SubscriptionCondition('subscription', 'in', 'preauthorised')]), 'global', 'global', 0);
let schemeRule = new Rule(false, new Condition('scheme', 'equals', 'comm'), 'global', 'global', 0);
let dateRule = new Rule(false, new Condition('date', 'equals', '01/01/2016'), 'global', 'global', 0);
let domainRule = new Rule(false, new Condition('domain', 'equals', 'blockedDomain'), 'global', 'global', 0);
let sourceRule = new Rule(false, new Condition('source', 'equals', 'user@blockedDomain'), 'global', 'global', 0);
let sourceRuleForConn = new Rule(false, new Condition('source', 'equals', 'user@blockedDomain'), 'hyperty', 'Connector', 0);
let sourceRuleForUser1 = new Rule(false, new Condition('source', 'equals', 'user@blockedDomain'), 'identity', 'user1@work,', 0);

/********** POLICIES **********/
let sourcePolicy = new Policy('HypertyChat', [simpleRule], [], 'allowOverrides');

/********** MESSAGES **********/
let messageFromChat = { body: { identity: { userProfile: { username: 'user@blockedDomain' } } }, id: 1, type: 'subscribe', from: 'hyperty://domain/hyperty-123', to: 'hyperty://domain/hyperty-456' };

let messageFromConn = { body: { identity: { userProfile: { username: 'user@blockedDomain' } } }, id: 1, type: 'subscribe', from: 'hyperty://domain/hyperty-012', to: 'hyperty://domain/hyperty-789' };

let messageFromUser1 = { body: { identity: { userProfile: { username: 'user1@domain1' } } }, id: 1, type: 'subscribe', from: 'scheme://domain/data-object-instance', to: 'comm://domain/data-object-instance' };

let messageFromUser2 = { body: { identity: { userProfile: { username: 'user2@domain2' } } }, id: 1, type: 'subscribe', from: 'scheme://domain/data-object-instance', to: 'comm://domain/data-object-instance' };

let noIdMessage = { from: 'hyperty://domain/hyperty-url', id: 1, to: 'comm://domain/hyperty-instance', type: 'subscribe' };

let message = { body: { auth: false, identity: { userProfile: { username: 'user@domain' } } }, from: 'hyperty://domain/hyperty-url', id: 1, to: 'comm://domain/hyperty-instance', type: 'subscribe' };

let messageFromBlocked = { body: { identity: { userProfile: { username: 'user@blockedDomain' } } }, id: 1, type: 'subscribe', from: 'comm://domain/data-object-instance', to: 'comm://domain/hyperty-instance' };

let subscribeMessage = { body: { identity: { userProfile: { username: 'user@domain' } }, subscriber: 'hyperty://domain/hyperty-instance' }, id: 1, type: 'subscribe', from: 'runtime://localhost/7600/sm', to: 'comm://domain/data-object-url/subscription' };

let allowedSubscribeMessage = { body: { auth: true, identity: { userProfile: { username: 'user@domain' } }, subscriber: 'hyperty://domain/hyperty-instance' }, id: 1, type: 'subscribe', from: 'runtime://localhost/7600/sm', to: 'comm://domain/data-object-url/subscription' };

let badSubscribeMessage = { body: { identity: { userProfile: { username: 'user@domain' } }, subscriber: 'hyperty://domain/not-preauthorised-hyperty-instance' }, id: 1, type: 'subscribe', from: 'runtime://localhost/7600/sm', to: 'comm://domain/data-object-url/subscription' };

/********** TESTS **********/
let runtimeCapabilities = {
  isAvailable: () => {
    return new Promise((resolve) => {
      resolve(false);
    });
  }
};

let runtimeCtx = new RuntimeCoreCtx();

describe('Policies management', () => {
  describe('conditions management', () => {
    it('creates a simple condition', () => {
      expect(simpleCondition.attribute).to.be.eql('source');
      expect(simpleCondition.operator).to.be.eql('equals');
      expect(simpleCondition.params).to.be.eql('user1@domain1');
    });
    it('returns condition is applicable', () => {
      expect(simpleCondition.isApplicable(runtimeCtx, messageFromUser1)).to.be.eql(true);
    });
    it('returns condition is not applicable', () => {
      expect(simpleCondition.isApplicable(runtimeCtx, messageFromUser2)).to.be.eql(false);
    });

    it('creates an advanced condition - "and"', () => {
      expect(andCondition.condition).to.be.eql(['and', new Condition('source', 'equals', 'user1@domain1'), new Condition('domain', 'equals', 'domain1')]);
      expect(andCondition.condition[0]).to.be.eql('and');
      expect(andCondition.condition[1]).to.be.eql(new Condition('source', 'equals', 'user1@domain1'));
      expect(andCondition.condition[2]).to.be.eql(new Condition('domain', 'equals', 'domain1'));
    });

    it('correctly returns "and" condition is applicable', () => {
      expect(andCondition.isApplicable(runtimeCtx, messageFromUser1)).to.be.eql(true);
    });

    it('correctly returns "and" condition is not applicable', () => {
      expect(andCondition.isApplicable(runtimeCtx, messageFromUser2)).to.be.eql(false);
    });

    it('creates an advanced condition - "or"', () => {
      expect(orCondition.condition).to.be.eql(['or', new Condition('source', 'equals', 'user1@domain1'), new Condition('domain', 'equals', 'domain1')]);
    });

    it('correctly returns "or" condition is applicable', () => {
      expect(orCondition.isApplicable(runtimeCtx, messageFromUser1)).to.be.eql(true);
    });

    it('correctly returns "or" condition is not applicable', () => {
      expect(orCondition.isApplicable(runtimeCtx, messageFromUser2)).to.be.eql(false);
    });

    it('creates an advanced condition - "not"', () => {
      expect(notCondition.condition).to.be.eql(['not', new Condition('source', 'equals', 'user2@domain2')]);
    });

    it('correctly returns "not" condition is applicable', () => {
      expect(notCondition.isApplicable(runtimeCtx, messageFromUser1)).to.be.eql(true);
    });

    it('correctly returns "not" condition is not applicable', () => {
      expect(notCondition.isApplicable(runtimeCtx, messageFromUser2)).to.be.eql(false);
    });

    it('correctly returns "and not" condition is not applicable', () => {
      expect(andNotCondition.isApplicable(runtimeCtx, messageFromUser1)).to.be.eql(false);
    });

    it('correctly returns "or not" condition is applicable', () => {
      expect(orNotCondition.isApplicable(runtimeCtx, messageFromUser1)).to.be.eql(true);
    });
  });

  describe('rules management', () => {
    it('creates a simple rule', () => {
      expect(simpleRule.decision).to.be.eql(false);
      expect(simpleRule.condition).to.be.eql(simpleCondition);
      expect(simpleRule.scope).to.be.eql('global');
      expect(simpleRule.target).to.be.eql('global');
    });
    it('evaluates a simple rule to false', () => {
      expect(simpleRule.evaluate(runtimeCtx, messageFromUser1)).to.be.eql(false);
    });
    it('evaluates a simple rule to "Not Applicable"', () => {
      expect(simpleRule.evaluate(runtimeCtx, messageFromUser2)).to.be.eql('Not Applicable');
    });
  });

  describe('policies management', () => {
    it('creates a service provider policy', () => {
      expect(sourcePolicy.key).to.be.eql('HypertyChat');
      expect(sourcePolicy.combiningAlgorithm).to.be.eql(new AllowOverrides());
      expect(sourcePolicy.rules).to.be.eql([simpleRule]);
    });
    it('evaluates a service provider policy to false', () => {
      expect(sourcePolicy.evaluateRules(runtimeCtx, messageFromUser1)).to.be.eql(false);
    });
    it('evaluates a service provider policy to "Not Applicable"', () => {
      expect(sourcePolicy.evaluateRules(runtimeCtx, messageFromUser2)).to.be.eql('Not Applicable');
    });
  });
});

describe('Policy Engine with Runtime Core context', () => {
  let runtimeRegistry = {
    isInterworkingProtoStub: () => {
      return false;
    },
    isLocal: () => {
      return true;
    },
    getPreAuthSubscribers: () => {
      return ['hyperty://domain/hyperty-instance'];
    },
    getHypertyName: (hypertyURL) => {
      if (hypertyURL === 'hyperty://domain/hyperty-123') {
        return 'HypertyChat';
      }
      if (hypertyURL === 'hyperty://domain/hyperty-789') {
        return 'Connector';
      }
    },
    getHypertyOwner: () => {
      return 'user://work/user2';
    },
    getReporterURLSynchonous: () => {
      return 'hyperty://domain/hyperty-url';
    },
    getUserEmailFromURL: () => {
      return 'user2@work';
    },
    isDataObjectURL: (dataObjectURL) => {
      let splitURL = dataObjectURL.split('://');
      return splitURL[0] === 'comm';
    },
    registerSubscribedDataObject: () => {},
    registerSubscriber: () => {},
    runtimeURL: 'runtime://localhost/7601'
  };

  let identityModule = {
    decryptMessage: (message) => {
      return new Promise((resolve) => {
        resolve(message);
      });
    },
    doMutualAuthentication: (message) => {
      return new Promise((resolve) => {
        resolve(message);
      });
    },
    encryptMessage: (message) => {
      return new Promise((resolve) => {
        resolve(message);
      });
    },
    getIdentity: () => {
      return new Promise((resolve) => {
        resolve({ userProfile: {username: 'user@domain' } });
      });
    },
    getToken: () => {
      return new Promise((resolve) => {
        resolve({ userProfile: {username: 'user@domain' } });
      });
    },
    _getHypertyFromDataObject: (dataObjectURL) => {
      return new Promise((resolve) => {
        if (dataObjectURL === 'comm://domain/data-object-url') {
          resolve('hyperty://domain/hyperty-url');
        }
      });
    }
  };
  let storageManager = runtimeFactory.storageManager();
  let policyEngine = new PEP(new RuntimeCoreCtx(identityModule, runtimeRegistry, storageManager, runtimeCapabilities));

  describe('initial filtering', () => {
    it('message that loads an hyperty should not be validated by policies', () => {
      let loadMessage = { id: 2, from: 'hyperty-runtime://sandbox/external', to: 'hyperty-runtime://sandbox/internal', type: 'create' };
      expect(policyEngine._isToVerify(loadMessage)).to.be.eql(false);
    });

    /*it('message of the "update" type must have the data object\'s reporter as source', (done) => {
      let updateMessage = { body: { source: 'hyperty://domain/hyperty-url', identity: 'identityToken' }, from: 'comm://domain/data-object-url', id: 3, to: 'comm://domain/data-object-url/changes', type: 'update' };
      expect(policyEngine.authorise(updateMessage).then((message) => {
        return message;
      }), (error) => {
        return error;
      }).to.be.fulfilled.and.eventually.eql(updateMessage).and.notify(done);

      let badUpdateMessage = { body: { source: 'hyperty://domain/hyperty-url2', identity: 'identityToken' }, from: 'comm://domain/data-object-url', id: 3, to: 'comm://domain/data-object-url/changes', type: 'update' };
      expect(policyEngine.authorise(badUpdateMessage).then((message) => {
        return message;
      }), (error) => {
        return error;
      }).to.be.rejected.and.notify(done);
    });*/
  });

  describe('identity obtention', () => {
    it('should add an identity in the message body', (done) => {
      expect(policyEngine.authorise(noIdMessage).then((message) => {
        return message;
      }), (error) => {
        return error;
      }).to.be.fulfilled.and.eventually.eql(message).and.notify(done);
    });

    it('should maintain the identity in the message body', (done) => {
      expect(policyEngine.authorise(message).then((message) => {
        return message;
      }), (error) => {
        return error;
      }).to.be.fulfilled.and.eventually.eql(message).and.notify(done);
    });
  });

  describe('policies management', () => {
    policyEngine.removePolicy('*');

    it('adds a service provider policy to the engine', () => {
      policyEngine.context.activeUserPolicy = undefined;
      policyEngine.addPolicy('SERVICE_PROVIDER', 'HypertyChat', sourcePolicy);
      console.log('policyEngine.context.serviceProviderPolicy');
      console.log(policyEngine.context.serviceProviderPolicy);
      expect(policyEngine.context.serviceProviderPolicy).to.be.eql({HypertyChat: new Policy('HypertyChat', [simpleRule], [])});
      expect(policyEngine.context.serviceProviderPolicy.HypertyChat).to.be.eql(new Policy('HypertyChat', [simpleRule], []));
      expect(policyEngine.context.serviceProviderPolicy.HypertyChat.actions).to.be.eql([]);
      expect(policyEngine.context.serviceProviderPolicy.HypertyChat.rules).to.be.eql([simpleRule]);
    });

    it('adds a user policy to the engine', () => {
      policyEngine.addPolicy('USER', 'My policy', new Policy('My policy', [dateRule], []));
      expect(policyEngine.context.userPolicies).to.be.eql({'My policy': new Policy('My policy', [dateRule], [])});
      expect(policyEngine.context.userPolicies['My policy']).to.be.eql(new Policy('My policy', [dateRule], []));
      expect(policyEngine.context.userPolicies['My policy'].actions).to.be.eql([]);
      expect(policyEngine.context.userPolicies['My policy'].rules).to.be.eql([dateRule]);
    });

    it('adds a second user policy to the engine', () => {
      policyEngine.addPolicy('USER', 'My second policy', new Policy('My second policy', [simpleRule], []));
      expect(policyEngine.context.userPolicies).to.be.eql({'My policy': new Policy('My policy', [dateRule], []), 'My second policy': new Policy('My second policy', [simpleRule], [])});
      expect(policyEngine.context.serviceProviderPolicy).to.be.eql({HypertyChat: new Policy('HypertyChat', [simpleRule], [])});
      expect(policyEngine.context.activeUserPolicy).to.be.eql(undefined);
    });

    it('removes an existing user policy', () => {
      policyEngine.removePolicy('USER', 'My policy');
      expect(policyEngine.context.serviceProviderPolicy).to.be.eql({HypertyChat: new Policy('HypertyChat', [simpleRule], [])});
      expect(policyEngine.context.userPolicies).to.be.eql({'My second policy': new Policy('My second policy', [simpleRule], [])});
      expect(policyEngine.context.activeUserPolicy).to.be.eql(undefined);
    });

    it('tries to remove a policy that does not exist', () => {
      policyEngine.removePolicy('USER', 'Vacations');
      expect(policyEngine.context.serviceProviderPolicy).to.be.eql({HypertyChat: new Policy('HypertyChat', [simpleRule], [])});
      expect(policyEngine.context.userPolicies).to.be.eql({'My second policy': new Policy('My second policy', [simpleRule], [])});
      expect(policyEngine.context.activeUserPolicy).to.be.eql(undefined);
    });

    it('removes all policies', () => {
      policyEngine.removePolicy('*');
      expect(policyEngine.context.serviceProviderPolicy).to.be.eql({});
      expect(policyEngine.context.userPolicies).to.be.eql({});
      expect(policyEngine.context.activeUserPolicy).to.be.eql(undefined);
    });

    it('does not apply rules as it is not its target', (done) => {
      policyEngine.addPolicy('USER', 'My policy', new Policy('My policy', [sourceRuleForConn], []));
      policyEngine.context.activeUserPolicy = 'My policy';
      expect(policyEngine.authorise(messageFromChat).then((message) => {
        return message;
      }), (error) => {
        return error;
      }).to.be.fulfilled.and.eventually.eql(messageFromChat).and.notify(done);

      policyEngine.removePolicy('*');
      policyEngine.addPolicy('USER', 'My policy', new Policy('My policy', [sourceRuleForUser1], []));
      policyEngine.context.activeUserPolicy = 'My policy';
      expect(policyEngine.authorise(messageFromUser2).then((message) => {
        return message;
      }), (error) => {
        return error;
      }).to.be.fulfilled.and.eventually.eql(messageFromChat).and.notify(done);
    });

    it('applies a rule as it is its target', (done) => {
      policyEngine.addPolicy('USER', 'My policy', new Policy('My policy', [sourceRuleForConn], []));
      policyEngine.context.activeUserPolicy = 'My policy';
      expect(policyEngine.authorise(messageFromConn).then((message) => {
        return message;
      }), (error) => {
        return error;
      }).to.be.rejected.and.notify(done);
    });
  });

  describe('functionality: scheme', () => {
    it('rejects the message as it is from a blocked scheme', (done) => {
      policyEngine.removePolicy('*');
      policyEngine.addPolicy('USER', 'My policy', new Policy('My policy', [schemeRule], []));
      policyEngine.context.activeUserPolicy = 'My policy';
      expect(policyEngine.authorise(messageFromBlocked).then((message) => {
        return message;
      }), (error) => {
        return error;
      }).to.be.rejected.and.notify(done);
    });

    it('allows the message as it comes from a scheme that is not blocked', (done) => {
      expect(policyEngine.authorise(message).then((message) => {
        return message;
      }), (error) => {
        return error;
      }).to.be.fulfilled.and.eventually.eql(message).and.notify(done);
    });
  });

  describe('functionality: source', () => {
    let sourceRule2 = new Rule(false, new Condition('source', 'in', ['user@blockedDomain']));
    it('rejects the message as it comes from a blocked source', (done) => {
      policyEngine.removePolicy('*');
      policyEngine.addPolicy('USER', 'My policy', new Policy('My policy', [sourceRule2], []));
      policyEngine.context.activeUserPolicy = 'My policy';
      expect(policyEngine.authorise(messageFromBlocked).then((message) => {
        return message;
      }), (error) => {
        return error;
      }).to.be.rejected.and.notify(done);
    });

    it('rejects the message as it comes from a blocked source', (done) => {
      policyEngine.removePolicy('*');
      policyEngine.addPolicy('USER', 'My policy', new Policy('My policy', [sourceRule], []));
      policyEngine.context.activeUserPolicy = 'My policy';
      expect(policyEngine.authorise(messageFromBlocked).then((message) => {
        return message;
      }), (error) => {
        return error;
      }).to.be.rejected.and.notify(done);
    });

    it('allows the message as it comes from a source that is not blocked', (done) => {
      expect(policyEngine.authorise(message).then((message) => {
        return message;
      }), (error) => {
        return error;
      }).to.be.fulfilled.and.eventually.eql(message).and.notify(done);
    });
  });

  describe('functionality: domain', () => {
    it('rejects the message as it comes from a blocked domain', (done) => {
      policyEngine.removePolicy('*');
      policyEngine.addPolicy('USER', 'My policy', new Policy('My policy', [domainRule], []));
      policyEngine.context.activeUserPolicy = 'My policy';
      expect(policyEngine.authorise(messageFromBlocked).then((message) => {
        return message;
      }), (error) => {
        return error;
      }).to.be.rejected.and.notify(done);
    });

    it('allows the message as it comes from a domain that is not blocked', (done) => {
      expect(policyEngine.authorise(message).then((message) => {
        return message;
      }), (error) => {
        return error;
      }).to.be.fulfilled.and.eventually.eql(message).and.notify(done);
    });
  });

  describe('data objects management', () => {
    it('rejects a subscription attempt, as the policy rejects all', (done) => {
      policyEngine.removePolicy('*');
      policyEngine.addPolicy('USER', 'My policy', new Policy('My policy', [blockAnySubscriptionRule], []));
      policyEngine.context.activeUserPolicy = 'My policy';
      expect(policyEngine.authorise(subscribeMessage).then((message) => {
        return message;
      }), (error) => {
        return error;
      }).to.be.rejected.and.notify(done);
    });

    it('accepts a subscription attempt, as the policy accepts all', (done) => {
      policyEngine.removePolicy('*');
      policyEngine.addPolicy('USER', 'My policy', new Policy('My policy', [acceptAnySubscriptionRule], []));
      policyEngine.context.activeUserPolicy = 'My policy';
      expect(policyEngine.authorise(subscribeMessage).then((message) => {
        return message;
      }), (error) => {
        return error;
      }).to.be.fulfilled.and.eventually.eql(allowedSubscribeMessage).and.notify(done);
    });

    it('accepts a subscription attempt, as the policy accepts preauthorised subscribers and is preauthorised', (done) => {
      policyEngine.removePolicy('*');
      policyEngine.addPolicy('USER', 'My policy', new Policy('My policy', [acceptPreAuthSubscriptionRule], []));
      policyEngine.context.activeUserPolicy = 'My policy';
      expect(policyEngine.authorise(subscribeMessage).then((message) => {
        return message;
      }), (error) => {
        return error;
      }).to.be.fulfilled.and.eventually.eql(allowedSubscribeMessage).and.notify(done);
    });

    it('rejects a subscription attempt, as the policy rejects non-preauthorised subscriber and is not preauthorised', (done) => {
      policyEngine.removePolicy('*');
      policyEngine.addPolicy('USER', 'My policy', new Policy('My policy', [blockPreAuthSubscriptionRule], []));
      policyEngine.context.activeUserPolicy = 'My policy';
      expect(policyEngine.authorise(badSubscribeMessage).then((message) => {
        return message;
      }), (error) => {
        return error;
      }).to.be.rejected.and.notify(done);
    });
  });

  describe('groups management', () => {
    let groups = policyEngine.context.groups;
    for (let i in groups) {
      policyEngine.context.deleteGroup(i);
    }

    it('creates a group', () => {
      policyEngine.context.createGroup('groupA');
      expect(policyEngine.context.getGroupsNames()).to.be.eql(['groupA']);
    });

    it('creates a second group', () => {
      policyEngine.context.createGroup('groupB');
      expect(policyEngine.context.getGroupsNames()).to.be.eql(['groupA', 'groupB']);
    });

    it('adds an email to a group', () => {
      policyEngine.context.addToGroup('groupA', 'user1@domain');
      expect(policyEngine.context.getGroup('groupA')).to.be.eql(['user1@domain']);
    });

    it('adds a second email to a group', () => {
      policyEngine.context.addToGroup('groupA', 'user2@domain');
      expect(policyEngine.context.getGroup('groupA')).to.be.eql(['user1@domain', 'user2@domain']);
    });

    it('removes a user from a group', () => {
      policyEngine.context.removeFromGroup('groupA', 'user1@domain');
      expect(policyEngine.context.getGroup('groupA')).to.be.eql(['user2@domain']);
    });

    it('deletes a group', () => {
      policyEngine.context.deleteGroup('groupA');
      expect(policyEngine.context.getGroup('groupA')).to.be.eql([]);
      expect(policyEngine.context.getGroupsNames()).to.be.eql(['groupB']);
    });
  });
  policyEngine.removePolicy('*');

});