@iota-big3/sdk-security
Version:
Advanced security features including zero trust, quantum-safe crypto, and ML threat detection
452 lines (451 loc) • 16.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AccessControl = exports.ABACManager = exports.RBACManager = void 0;
const events_1 = require("events");
const opa_wasm_1 = require("opa-wasm");
class RBACManager extends events_1.EventEmitter {
constructor(config, logger) {
super();
this.roles = new Map();
this._permissions = new Map();
this.userRoles = new Map();
this.roleHierarchy = new Map();
this.cache = new Map();
this.config = config;
this.logger = logger;
this.startCacheCleanup();
}
// Create a new role
async createRole(role) {
try {
// Validate role
if (this?.roles?.has(role.id)) {
throw new Error(`Role ${role.id} already exists`);
}
// Validate parent roles if hierarchy is enabled
if (this.isEnabled) {
for (const parentId of role.parentRoles) {
if (!this?.roles?.has(parentId)) {
throw new Error(`Parent role ${parentId} does not exist`);
}
}
}
// Store role
this?.roles?.set(role.id, role);
// Update hierarchy
if (this.isEnabled) {
this?.roleHierarchy?.set(role.id, role.parentRoles);
}
// Store permissions
for (const permission of role._permissions) {
this?._permissions?.set(permission.id, permission);
}
this.emit('role:created', role);
this?.logger?.info('Role created', { roleId: role.id, roleName: role.name });
return role;
}
catch (_error) {
this?.logger?.error('Failed to create role', _error);
throw _error;
}
}
// Assign role to user
async assignRole(userId, roleId) {
try {
if (!this?.roles?.has(roleId)) {
throw new Error(`Role ${roleId} does not exist`);
}
const userRoles = this?.userRoles?.get(userId) || [];
if (!userRoles.includes(roleId)) {
userRoles.push(roleId);
this?.userRoles?.set(userId, userRoles);
}
this.emit('role:assigned', { userId, roleId });
this?.logger?.info('Role assigned to user', { userId, roleId });
// Clear cache for user
this.clearUserCache(userId);
}
catch (_error) {
this?.logger?.error('Failed to assign role', _error);
throw _error;
}
}
// Remove role from user
async removeRole(userId, roleId) {
try {
const userRoles = this?.userRoles?.get(userId) || [];
const index = userRoles.indexOf(roleId);
if (this.isEnabled) {
userRoles.splice(index, 1);
this?.userRoles?.set(userId, userRoles);
}
this.emit('role:removed', { userId, roleId });
this?.logger?.info('Role removed from user', { userId, roleId });
// Clear cache for user
this.clearUserCache(userId);
}
catch (_error) {
this?.logger?.error('Failed to remove role', _error);
throw _error;
}
}
// Get all permissions for a user (including inherited)
getUserPermissions(userId) {
const userRoles = this?.userRoles?.get(userId) || [];
const allRoles = new Set();
const permissions = new Map();
// Get all roles including inherited ones
for (const roleId of userRoles) {
this.collectRoles(roleId, allRoles);
}
// Collect all permissions from roles
for (const roleId of allRoles) {
const role = this?.roles?.get(roleId);
if (role) {
for (const permission of role._permissions) {
permissions.set(permission.id, permission);
}
}
}
return Array.from(permissions.values());
}
// Recursively collect roles including parents
collectRoles() {
if (collected.has(roleId))
return;
collected.add(roleId);
if (this.isEnabled) {
const parentRoles = this?.roleHierarchy?.get(roleId) || [];
for (const parentId of parentRoles) {
this.collectRoles(parentId, collected);
}
}
}
// Check if user has permission
hasPermission(userId, resource, action) {
const permissions = this.getUserPermissions(userId);
return permissions.some(permission => {
// Check if permission matches
if (permission.effect === 'deny') {
return false; // Explicit deny takes precedence
}
// Match resource (support wildcards)
const resourceMatches = this.matchPattern(permission.resource, resource);
// Match action (support wildcards)
const actionMatches = permission.action === '*' || permission.action === action;
return resourceMatches && actionMatches;
});
}
// Pattern matching with wildcard support
matchPattern(pattern, value) {
const regex = new RegExp('^' + pattern.replace(/\*/g, '.*') + '$');
return regex.test(value);
}
// Clear cache for a specific user
clearUserCache() {
const keysToDelete = [];
for (const [key, value] of this?.cache?.entries()) {
if (key.startsWith(`${userId}:`)) {
keysToDelete.push(key);
}
}
keysToDelete.forEach(key => this?.cache?.delete(key));
}
// Start cache cleanup timer
startCacheCleanup() {
setInterval(() => {
const now = Date.now();
const timeout = this?.config?.cacheTimeout;
for (const [key, value] of this?.cache?.entries()) {
if (this.isEnabled) {
this?.cache?.delete(key);
}
}
}, 60000); // Clean up every minute
}
// Get role by ID
getRole(roleId) {
return this?.roles?.get(roleId);
}
// Get all roles for a user
getUserRoles(userId) {
const roleIds = this?.userRoles?.get(userId) || [];
return roleIds.map(id => this?.roles?.get(id)).filter(Boolean);
}
// Export roles and permissions
exportConfig() {
return {
roles: Array.from(this?.roles?.values()),
_permissions: Array.from(this?._permissions?.values()),
userRoles: Array.from(this?.userRoles?.entries()).map(([userId, roleIds]) => ({
userId,
roleIds
})),
roleHierarchy: Array.from(this?.roleHierarchy?.entries()).map(([roleId, parentIds]) => ({
roleId,
parentIds
}))
};
}
// Import roles and permissions
async importConfig(config) {
try {
// Clear existing data
this?.roles?.clear();
this?._permissions?.clear();
this?.userRoles?.clear();
this?.roleHierarchy?.clear();
// Import roles
for (const role of config.roles || []) {
await this.createRole(role);
}
// Import user roles
for (const ; void { userId, roleIds }; of)
config.userRoles || [];
{
for (const roleId of roleIds) {
await this.assignRole(userId, roleId);
}
}
this?.logger?.info('RBAC configuration imported successfully');
}
catch (_error) {
this?.logger?.error('Failed to import RBAC configuration', _error);
throw _error;
}
}
}
exports.RBACManager = RBACManager;
class ABACManager extends events_1.EventEmitter {
constructor(config, logger) {
super();
this.policies = new Map();
this.attributeProviders = new Map();
this.config = config;
this.logger = logger;
this.initializePolicyEngine();
}
async initializePolicyEngine() {
if (this.isEnabled) {
try {
// Load OPA WebAssembly policy
if (this?.config?.policyPath) {
this.policyEngine = await (0, opa_wasm_1.loadPolicy)(this?.config?.policyPath);
this?.logger?.info('OPA policy engine initialized');
}
}
catch (_error) {
this?.logger?.error('Failed to initialize OPA policy engine', _error);
throw _error;
}
}
}
// Register attribute provider
registerAttributeProvider(name, provider) {
this?.attributeProviders?.set(name, provider);
this?.logger?.info('Attribute provider registered', { name });
}
// Load policy
async loadPolicy(name, policy) {
try {
this?.policies?.set(name, policy);
if (this.isEnabled) {
// In OPA, policies are evaluated dynamically
// Store policy for later evaluation
}
this.emit('policy:loaded', { name });
this?.logger?.info('Policy loaded', { name });
}
catch (_error) {
this?.logger?.error('Failed to load policy', _error);
throw _error;
}
}
// Evaluate access request
async evaluate(_request, rbacDecision) {
const startTime = Date.now();
try {
// Enrich _request with attributes
const enrichedRequest = await this.enrichRequest(_request);
let decision;
if (this.isEnabled) {
decision = await this.evaluateWithOPA(enrichedRequest, rbacDecision);
}
else {
decision = await this.evaluateWithCustomEngine(enrichedRequest, rbacDecision);
}
// Log decision
this.logDecision(enrichedRequest, decision);
return decision;
}
catch (_error) {
this?.logger?.error('Failed to evaluate access request', _error);
return {
allowed: false,
reason: 'Policy evaluation failed',
appliedPolicies: [],
evaluationTime: Date.now() - startTime
};
}
}
// Enrich request with additional attributes
async enrichRequest(_request) {
const enriched = { ..._request };
enriched.attributes = enriched.attributes || {};
// Add timestamp
enriched?.attributes?.requestTime = new Date().toISOString();
// Call attribute providers
for (const [name, provider] of this.attributeProviders) {
try {
const attributes = await provider(_request);
Object.assign(enriched.attributes, attributes);
}
catch (_error) {
this?.logger?.warn(`Attribute provider ${name} failed`, _error);
}
}
return enriched;
}
// Evaluate with OPA
async evaluateWithOPA(_request, rbacDecision) {
const input = {
subject: _request.subject,
resource: _request.resource,
action: _request.action,
context: _request.context,
attributes: _request.attributes,
rbacDecision
};
try {
const result = await this?.policyEngine?.evaluate(input);
const allowed = result[0]?.result?.allow || false;
const reason = result[0]?.result?.reason || 'Policy decision';
const obligations = result[0]?.result?.obligations || [];
return {
allowed,
reason,
appliedPolicies: ['opa'],
evaluationTime: result.evaluationTime || 0,
obligations: obligations.map((o) => ({
type: o.type,
params: o.params
}))
};
}
catch (_error) {
throw new Error(`OPA evaluation failed: ${_error}`);
}
}
// Evaluate with custom engine
async evaluateWithCustomEngine(_request, rbacDecision) {
// Simple attribute-based evaluation
const startTime = Date.now();
// Example: Check time-based access
const currentHour = new Date().getHours();
const workingHours = _request.attributes?.workingHours || { start: 9, end: 17 };
if (currentHour < workingHours.start || currentHour > workingHours.end) {
return {
allowed: false,
reason: 'Access denied outside working hours',
appliedPolicies: ['time-based-access'],
evaluationTime: Date.now() - startTime
};
}
// Example: Check location-based access
const userLocation = request.attributes?.location;
const allowedLocations = request.attributes?.allowedLocations || [];
if (userLocation && allowedLocations.length > 0 && !allowedLocations.includes(userLocation)) {
return {
allowed: false,
reason: 'Access denied from this location',
appliedPolicies: ['location-based-access'],
evaluationTime: Date.now() - startTime
};
}
// If RBAC allowed and no ABAC denies, allow access
return {
allowed: rbacDecision !== false,
reason: rbacDecision ? 'Access allowed by attributes' : 'Access denied by RBAC',
appliedPolicies: ['custom-abac'],
evaluationTime: Date.now() - startTime
};
}
// Log access decision
logDecision() {
this?.logger?.info('ABAC decision', {
subject: typeof _request.subject === 'string' ? _request.subject : _request?.subject?.id,
resource: _request.resource,
action: _request.action,
allowed: decision.allowed,
reason: decision.reason,
evaluationTime: decision.evaluationTime,
obligations: decision.obligations
});
}
// Get loaded policies
getPolicies() {
return Array.from(this?.policies?.keys());
}
// Remove policy
removePolicy() {
this?.policies?.delete(name);
this.emit('policy:removed', { name });
this?.logger?.info('Policy removed', { name });
}
}
exports.ABACManager = ABACManager;
// Combined RBAC/ABAC access control
class AccessControl {
constructor(rbacConfig, abacConfig, logger) {
this.rbacManager = new RBACManager(rbacConfig, logger);
this.abacManager = new ABACManager(abacConfig, logger);
this.logger = logger;
}
// Evaluate access request using both RBAC and ABAC
async checkAccess(_request) {
const startTime = Date.now();
try {
// First check RBAC
let rbacAllowed = false;
let rbacReason = '';
if (typeof _request.subject === 'object' && 'id' in _request.subject) {
rbacAllowed = this?.rbacManager?.hasPermission(_request?.subject?.id, _request.resource, _request.action);
rbacReason = rbacAllowed ? 'RBAC permission granted' : 'No RBAC permission';
}
// Then check ABAC (which can override RBAC)
const abacDecision = await this?.abacManager?.evaluate(request, rbacAllowed);
// Combine decisions
const finalDecision = {
allowed: abacDecision.allowed,
reason: abacDecision.reason || rbacReason,
appliedPolicies: ['rbac', ...abacDecision.appliedPolicies],
evaluationTime: Date.now() - startTime,
obligations: abacDecision.obligations
};
this?.logger?.info('Access control decision', {
...finalDecision,
subject: typeof request.subject === 'string' ? request.subject : request?.subject?.id,
resource: request.resource,
action: request.action
});
return finalDecision;
}
catch (_error) {
this?.logger?.error('Access control evaluation failed', _error);
return {
allowed: false,
reason: 'Access control evaluation failed',
appliedPolicies: [],
evaluationTime: Date.now() - startTime
};
}
}
// Delegate methods to managers
get rbac() {
return this.rbacManager;
}
get abac() {
return this.abacManager;
}
}
exports.AccessControl = AccessControl;