rbac-engine
Version:
Role-based access control engine with policy-based permissions
308 lines • 15.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const evaluator_1 = require("../../policy/evaluator");
const models_1 = require("../../models");
describe('Policy Evaluator', () => {
describe('evaluateCondition', () => {
it('should return true when all conditions match the context', () => {
const condition = { department: 'engineering', country: 'USA' };
const context = { department: 'engineering', country: 'USA', role: 'developer' };
expect((0, evaluator_1.evaluateCondition)(condition, context)).toBe(true);
});
it('should return false when any condition does not match the context', () => {
const condition = { department: 'engineering', country: 'USA' };
const context = { department: 'engineering', country: 'Canada', role: 'developer' };
expect((0, evaluator_1.evaluateCondition)(condition, context)).toBe(false);
});
it('should return false when context is missing required condition keys', () => {
const condition = { department: 'engineering', country: 'USA' };
const context = { department: 'engineering' };
expect((0, evaluator_1.evaluateCondition)(condition, context)).toBe(false);
});
});
describe('matches', () => {
it('should return true for exact string match', () => {
expect((0, evaluator_1.matches)('read', 'read')).toBe(true);
});
it('should return false for non-matching strings', () => {
expect((0, evaluator_1.matches)('read', 'write')).toBe(false);
});
it('should handle wildcard at the beginning', () => {
expect((0, evaluator_1.matches)('*:document', 'read:document')).toBe(true);
expect((0, evaluator_1.matches)('*:document', 'write:document')).toBe(true);
expect((0, evaluator_1.matches)('*:document', 'document')).toBe(false);
});
it('should handle wildcard at the end', () => {
expect((0, evaluator_1.matches)('document:*', 'document:read')).toBe(true);
expect((0, evaluator_1.matches)('document:*', 'document:write')).toBe(true);
expect((0, evaluator_1.matches)('document:*', 'document')).toBe(false);
});
it('should handle wildcard in the middle', () => {
expect((0, evaluator_1.matches)('document:*:sensitive', 'document:read:sensitive')).toBe(true);
expect((0, evaluator_1.matches)('document:*:sensitive', 'document:write:sensitive')).toBe(true);
expect((0, evaluator_1.matches)('document:*:sensitive', 'document:sensitive')).toBe(false);
});
it('should handle multiple wildcards', () => {
expect((0, evaluator_1.matches)('*:document:*', 'read:document:public')).toBe(true);
expect((0, evaluator_1.matches)('*:document:*', 'write:document:secret')).toBe(true);
expect((0, evaluator_1.matches)('*:document:*', 'document')).toBe(false);
});
it('should check against array of patterns', () => {
expect((0, evaluator_1.matches)(['read:*', 'write:document'], 'read:document')).toBe(true);
expect((0, evaluator_1.matches)(['read:*', 'write:document'], 'write:document')).toBe(true);
expect((0, evaluator_1.matches)(['read:*', 'write:document'], 'delete:document')).toBe(false);
});
});
describe('isStatementActive', () => {
const testDate = new Date(Date.UTC(2025, 4, 1, 12, 0, 0));
beforeEach(() => {
jest.useFakeTimers().setSystemTime(testDate);
});
afterEach(() => {
jest.useRealTimers();
});
it('should return true when no date constraints are provided', () => {
const statement = {
Effect: models_1.Effect.Allow,
Action: ['read'],
Resource: ['document']
};
expect((0, evaluator_1.isStatementActive)(statement)).toBe(true);
});
it('should return true when current time is after StartDate', () => {
const statement = {
Effect: models_1.Effect.Allow,
Action: ['read'],
Resource: ['document'],
StartDate: '2025-04-01T00:00:00Z'
};
expect((0, evaluator_1.isStatementActive)(statement)).toBe(true);
});
it('should return false when current time is before StartDate', () => {
const statement = {
Effect: models_1.Effect.Allow,
Action: ['read'],
Resource: ['document'],
StartDate: '2025-06-01T00:00:00Z'
};
expect((0, evaluator_1.isStatementActive)(statement)).toBe(false);
});
it('should return true when current time is before EndDate', () => {
const statement = {
Effect: models_1.Effect.Allow,
Action: ['read'],
Resource: ['document'],
EndDate: '2025-06-01T00:00:00Z'
};
expect((0, evaluator_1.isStatementActive)(statement)).toBe(true);
});
it('should return false when current time is after EndDate', () => {
const statement = {
Effect: models_1.Effect.Allow,
Action: ['read'],
Resource: ['document'],
EndDate: '2025-04-01T00:00:00Z'
};
expect((0, evaluator_1.isStatementActive)(statement)).toBe(false);
});
it('should return true when current time is within date range', () => {
const statement = {
Effect: models_1.Effect.Allow,
Action: ['read'],
Resource: ['document'],
StartDate: '2025-04-01T00:00:00Z',
EndDate: '2025-06-01T00:00:00Z'
};
expect((0, evaluator_1.isStatementActive)(statement)).toBe(true);
});
it('should return false when current time is outside date range (too early)', () => {
const statement = {
Effect: models_1.Effect.Allow,
Action: ['read'],
Resource: ['document'],
StartDate: '2025-06-01T00:00:00Z',
EndDate: '2025-07-01T00:00:00Z'
};
expect((0, evaluator_1.isStatementActive)(statement)).toBe(false);
});
it('should return false when current time is outside date range (too late)', () => {
const statement = {
Effect: models_1.Effect.Allow,
Action: ['read'],
Resource: ['document'],
StartDate: '2025-01-01T00:00:00Z',
EndDate: '2025-04-01T00:00:00Z'
};
expect((0, evaluator_1.isStatementActive)(statement)).toBe(false);
});
it('should return false when date format is invalid', () => {
const statement = {
Effect: models_1.Effect.Allow,
Action: ['read'],
Resource: ['document'],
StartDate: 'invalid-date'
};
expect((0, evaluator_1.isStatementActive)(statement)).toBe(false);
});
});
describe('evaluate', () => {
const createPolicy = (id, statements) => ({
id,
document: {
Version: '2023-10-17',
Statement: statements
}
});
it('should return false when no policies are provided', () => {
expect((0, evaluator_1.evaluate)([], 'read', 'document')).toBe(false);
});
it('should return true when action and resource match an Allow policy', () => {
const policy = createPolicy('p1', [{
Effect: models_1.Effect.Allow,
Action: ['read'],
Resource: ['document']
}]);
expect((0, evaluator_1.evaluate)([policy], 'read', 'document')).toBe(true);
});
it('should return false when action and resource match a Deny policy', () => {
const policy = createPolicy('p1', [{
Effect: models_1.Effect.Deny,
Action: ['read'],
Resource: ['document']
}]);
expect((0, evaluator_1.evaluate)([policy], 'read', 'document')).toBe(false);
});
it('should handle wildcard patterns in actions and resources', () => {
const policy = createPolicy('p1', [{
Effect: models_1.Effect.Allow,
Action: ['read:*'],
Resource: ['document:*']
}]);
expect((0, evaluator_1.evaluate)([policy], 'read:metadata', 'document:report')).toBe(true);
});
it('should enforce deny-override across multiple policies', () => {
const allowPolicy = createPolicy('p1', [{
Effect: models_1.Effect.Allow,
Action: ['read'],
Resource: ['document']
}]);
const denyPolicy = createPolicy('p2', [{
Effect: models_1.Effect.Deny,
Action: ['read'],
Resource: ['document']
}]);
expect((0, evaluator_1.evaluate)([allowPolicy, denyPolicy], 'read', 'document')).toBe(false);
expect((0, evaluator_1.evaluate)([denyPolicy, allowPolicy], 'read', 'document')).toBe(false);
});
it('should evaluate conditions in statements', () => {
const policy = createPolicy('p1', [{
Effect: models_1.Effect.Allow,
Action: ['read'],
Resource: ['document'],
Condition: { department: 'engineering' }
}]);
expect((0, evaluator_1.evaluate)([policy], 'read', 'document', { department: 'engineering' })).toBe(true);
expect((0, evaluator_1.evaluate)([policy], 'read', 'document', { department: 'marketing' })).toBe(false);
});
it('should handle multiple statements in a policy', () => {
const policy = createPolicy('p1', [
{
Effect: models_1.Effect.Allow,
Action: ['read'],
Resource: ['document']
},
{
Effect: models_1.Effect.Deny,
Action: ['read'],
Resource: ['document:secret']
}
]);
expect((0, evaluator_1.evaluate)([policy], 'read', 'document')).toBe(true);
expect((0, evaluator_1.evaluate)([policy], 'read', 'document:secret')).toBe(false);
});
it('should handle multiple policies with different effects', () => {
const policy1 = createPolicy('p1', [{
Effect: models_1.Effect.Allow,
Action: ['read', 'write'],
Resource: ['*']
}]);
const policy2 = createPolicy('p2', [{
Effect: models_1.Effect.Deny,
Action: ['write'],
Resource: ['document:secret']
}]);
expect((0, evaluator_1.evaluate)([policy1, policy2], 'read', 'document')).toBe(true);
expect((0, evaluator_1.evaluate)([policy1, policy2], 'write', 'document')).toBe(true);
expect((0, evaluator_1.evaluate)([policy1, policy2], 'write', 'document:secret')).toBe(false);
});
describe('with date constraints', () => {
const testDate = new Date(Date.UTC(2025, 4, 1, 12, 0, 0));
beforeEach(() => {
jest.useFakeTimers().setSystemTime(testDate);
});
afterEach(() => {
jest.useRealTimers();
});
it('should allow access when policy is within active date range', () => {
const policy = createPolicy('p1', [{
Effect: models_1.Effect.Allow,
Action: ['read'],
Resource: ['document'],
StartDate: '2025-04-01T00:00:00Z',
EndDate: '2025-06-01T00:00:00Z'
}]);
expect((0, evaluator_1.evaluate)([policy], 'read', 'document')).toBe(true);
});
it('should deny access when policy has not yet become active', () => {
const policy = createPolicy('p1', [{
Effect: models_1.Effect.Allow,
Action: ['read'],
Resource: ['document'],
StartDate: '2025-06-01T00:00:00Z',
}]);
expect((0, evaluator_1.evaluate)([policy], 'read', 'document')).toBe(false);
});
it('should deny access when policy has expired', () => {
const policy = createPolicy('p1', [{
Effect: models_1.Effect.Allow,
Action: ['read'],
Resource: ['document'],
EndDate: '2025-04-01T00:00:00Z',
}]);
expect((0, evaluator_1.evaluate)([policy], 'read', 'document')).toBe(false);
});
it('should correctly handle mixed time-constrained and regular policies', () => {
const expiredPolicy = createPolicy('p1', [{
Effect: models_1.Effect.Allow,
Action: ['write'],
Resource: ['document'],
EndDate: '2025-04-01T00:00:00Z',
}]);
const futurePolicy = createPolicy('p2', [{
Effect: models_1.Effect.Allow,
Action: ['delete'],
Resource: ['document'],
StartDate: '2025-06-01T00:00:00Z',
}]);
const activePolicy = createPolicy('p3', [{
Effect: models_1.Effect.Allow,
Action: ['read'],
Resource: ['document'],
StartDate: '2025-04-01T00:00:00Z',
EndDate: '2025-06-01T00:00:00Z',
}]);
const regularPolicy = createPolicy('p4', [{
Effect: models_1.Effect.Allow,
Action: ['list'],
Resource: ['document'],
}]);
const policies = [expiredPolicy, futurePolicy, activePolicy, regularPolicy];
expect((0, evaluator_1.evaluate)(policies, 'read', 'document')).toBe(true);
expect((0, evaluator_1.evaluate)(policies, 'list', 'document')).toBe(true);
expect((0, evaluator_1.evaluate)(policies, 'write', 'document')).toBe(false);
expect((0, evaluator_1.evaluate)(policies, 'delete', 'document')).toBe(false);
});
});
});
});
//# sourceMappingURL=evaluator.spec.js.map