Files
ipam/db.py
T
2025-11-06 13:26:33 +00:00

360 lines
14 KiB
Python

import os
import hashlib
import base64
import secrets
import mysql.connector
from flask import current_app
def hash_password(password, salt=None):
if salt is None:
salt = base64.b64encode(os.urandom(16)).decode('utf-8')
salted = (salt + password).encode('utf-8')
hashed = hashlib.sha256(salted).hexdigest()
return f'{salt}${hashed}'
def verify_password(password, hashed):
try:
salt, hash_val = hashed.split('$', 1)
except ValueError:
return False
return hash_password(password, salt) == hashed
def generate_api_key():
"""Generate a secure API key"""
return secrets.token_urlsafe(32)
def get_db_connection(app=None):
if app is None:
app = current_app
conn = mysql.connector.connect(
host=app.config['MYSQL_HOST'],
user=app.config['MYSQL_USER'],
password=app.config['MYSQL_PASSWORD'],
database=app.config['MYSQL_DATABASE'],
autocommit=True
)
return conn
def init_db(app=None):
if app is None:
app = current_app
conn = get_db_connection(app)
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS User (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS Subnet (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
cidr VARCHAR(255) NOT NULL,
site VARCHAR(255)
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS AuditLog (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
user_id INTEGER,
action VARCHAR(255) NOT NULL,
details TEXT,
subnet_id INTEGER,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES User(id),
FOREIGN KEY (subnet_id) REFERENCES Subnet(id)
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS IPAddress (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
ip VARCHAR(255) NOT NULL,
hostname VARCHAR(255),
subnet_id INTEGER NOT NULL,
FOREIGN KEY (subnet_id) REFERENCES Subnet (id)
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS DeviceType (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL UNIQUE,
icon_class VARCHAR(255) NOT NULL
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS Device (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
description TEXT,
device_type_id INTEGER DEFAULT 1,
FOREIGN KEY (device_type_id) REFERENCES DeviceType(id)
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS DeviceIPAddress (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
device_id INTEGER NOT NULL,
ip_id INTEGER NOT NULL,
FOREIGN KEY (device_id) REFERENCES Device (id),
FOREIGN KEY (ip_id) REFERENCES IPAddress (id)
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS DHCPPool (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
subnet_id INTEGER NOT NULL,
start_ip VARCHAR(255) NOT NULL,
end_ip VARCHAR(255) NOT NULL,
excluded_ips TEXT,
FOREIGN KEY (subnet_id) REFERENCES Subnet(id) ON DELETE CASCADE
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS Rack (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL,
site VARCHAR(255) NOT NULL,
height_u INTEGER NOT NULL
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS RackDevice (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
rack_id INTEGER NOT NULL,
device_id INTEGER,
position_u INTEGER NOT NULL,
side ENUM('front', 'back') NOT NULL,
nonnet_device_name VARCHAR(255),
FOREIGN KEY (rack_id) REFERENCES Rack(id) ON DELETE CASCADE,
FOREIGN KEY (device_id) REFERENCES Device(id) ON DELETE CASCADE
)
''')
cursor.execute('SELECT COUNT(*) FROM DeviceType')
if cursor.fetchone()[0] == 0:
cursor.executemany('INSERT INTO DeviceType (name, icon_class) VALUES (%s, %s)', [
('Server', 'fa-server'),
('Virtual Machine', 'fa-boxes-stacked'),
('Switch', 'fa-network-wired'),
('Firewall', 'fa-shield-halved'),
('WiFi AP', 'fa-wifi'),
('Printer', 'fa-print'),
('Other', 'fa-question')
])
cursor.execute("SHOW COLUMNS FROM Device LIKE 'device_type_id'")
if not cursor.fetchone():
cursor.execute('ALTER TABLE Device ADD COLUMN device_type_id INTEGER DEFAULT NULL')
cursor.execute("SELECT id FROM DeviceType WHERE name='Other'")
other_id = cursor.fetchone()[0]
cursor.execute('UPDATE Device SET device_type_id = %s WHERE device_type_id IS NULL', (other_id,))
try:
cursor.execute('ALTER TABLE Device ADD CONSTRAINT fk_device_type FOREIGN KEY (device_type_id) REFERENCES DeviceType(id)')
except mysql.connector.Error as e:
if e.errno != 1061 and e.errno != 1826 and 'Duplicate' not in str(e):
raise
# Create Role table
cursor.execute('''
CREATE TABLE IF NOT EXISTS Role (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL UNIQUE,
description TEXT
)
''')
# Create Permission table
cursor.execute('''
CREATE TABLE IF NOT EXISTS Permission (
id INTEGER PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255) NOT NULL UNIQUE,
description TEXT,
category VARCHAR(255)
)
''')
# Create RolePermission junction table
cursor.execute('''
CREATE TABLE IF NOT EXISTS RolePermission (
role_id INTEGER NOT NULL,
permission_id INTEGER NOT NULL,
PRIMARY KEY (role_id, permission_id),
FOREIGN KEY (role_id) REFERENCES Role(id) ON DELETE CASCADE,
FOREIGN KEY (permission_id) REFERENCES Permission(id) ON DELETE CASCADE
)
''')
# Add role_id column to User table if it doesn't exist
cursor.execute("SHOW COLUMNS FROM User LIKE 'role_id'")
if not cursor.fetchone():
cursor.execute('ALTER TABLE User ADD COLUMN role_id INTEGER DEFAULT NULL')
try:
cursor.execute('ALTER TABLE User ADD CONSTRAINT fk_user_role FOREIGN KEY (role_id) REFERENCES Role(id)')
except mysql.connector.Error as e:
if e.errno != 1061 and e.errno != 1826 and 'Duplicate' not in str(e):
raise
# Add api_key column to User table if it doesn't exist
cursor.execute("SHOW COLUMNS FROM User LIKE 'api_key'")
if not cursor.fetchone():
cursor.execute('ALTER TABLE User ADD COLUMN api_key VARCHAR(255) DEFAULT NULL UNIQUE')
# Define all permissions with categories
permissions = [
# View permissions
('view_index', 'View Home/Index page', 'View'),
('view_devices', 'View Devices page', 'View'),
('view_device', 'View Device details', 'View'),
('view_subnet', 'View Subnet details', 'View'),
('view_racks', 'View Racks page', 'View'),
('view_rack', 'View Rack details', 'View'),
('view_audit', 'View Audit Log', 'View'),
('view_admin', 'View Admin panel', 'View'),
('view_users', 'View Users page', 'View'),
('view_device_types', 'View Device Types page', 'View'),
('view_device_type_stats', 'View Device Type Statistics', 'View'),
('view_devices_by_type', 'View Devices by Type', 'View'),
('view_dhcp', 'View DHCP configuration', 'View'),
('view_help', 'View Help page', 'View'),
# Device permissions
('add_device', 'Add new device', 'Device'),
('edit_device', 'Edit device (rename, description, type)', 'Device'),
('delete_device', 'Delete device', 'Device'),
('add_device_ip', 'Add IP address to device', 'Device'),
('remove_device_ip', 'Remove IP address from device', 'Device'),
# Subnet permissions
('add_subnet', 'Add new subnet', 'Subnet'),
('edit_subnet', 'Edit subnet (name, CIDR, site)', 'Subnet'),
('delete_subnet', 'Delete subnet', 'Subnet'),
('export_subnet_csv', 'Export subnet as CSV', 'Subnet'),
# Rack permissions
('add_rack', 'Add new rack', 'Rack'),
('delete_rack', 'Delete rack', 'Rack'),
('add_device_to_rack', 'Add device to rack', 'Rack'),
('remove_device_from_rack', 'Remove device from rack', 'Rack'),
('add_nonnet_device_to_rack', 'Add non-networked device to rack', 'Rack'),
('export_rack_csv', 'Export rack as CSV', 'Rack'),
# DHCP permissions
('configure_dhcp', 'Configure DHCP pools', 'DHCP'),
# Device Type permissions
('add_device_type', 'Add device type', 'Device Type'),
('edit_device_type', 'Edit device type', 'Device Type'),
('delete_device_type', 'Delete device type', 'Device Type'),
# Admin permissions
('manage_users', 'Manage users (add, edit, delete)', 'Admin'),
('manage_roles', 'Manage roles and permissions', 'Admin'),
]
# Insert permissions
for perm_name, perm_desc, perm_category in permissions:
cursor.execute('SELECT id FROM Permission WHERE name = %s', (perm_name,))
if not cursor.fetchone():
cursor.execute('INSERT INTO Permission (name, description, category) VALUES (%s, %s, %s)',
(perm_name, perm_desc, perm_category))
# Create default roles if they don't exist
cursor.execute('SELECT id FROM Role WHERE name = %s', ('admin',))
admin_role = cursor.fetchone()
if not admin_role:
cursor.execute('INSERT INTO Role (name, description) VALUES (%s, %s)',
('admin', 'Administrator with full access to all features'))
admin_role_id = cursor.lastrowid
else:
admin_role_id = admin_role[0]
cursor.execute('SELECT id FROM Role WHERE name = %s', ('user',))
user_role = cursor.fetchone()
if not user_role:
cursor.execute('INSERT INTO Role (name, description) VALUES (%s, %s)',
('user', 'Standard user with access to most features except admin functions'))
user_role_id = cursor.lastrowid
else:
user_role_id = user_role[0]
cursor.execute('SELECT id FROM Role WHERE name = %s', ('view_only',))
view_only_role = cursor.fetchone()
if not view_only_role:
cursor.execute('INSERT INTO Role (name, description) VALUES (%s, %s)',
('view_only', 'View-only user with read-only access to all pages'))
view_only_role_id = cursor.lastrowid
else:
view_only_role_id = view_only_role[0]
# Assign all permissions to admin role
cursor.execute('SELECT id FROM Permission')
all_permission_ids = [row[0] for row in cursor.fetchall()]
for perm_id in all_permission_ids:
cursor.execute('SELECT role_id FROM RolePermission WHERE role_id = %s AND permission_id = %s',
(admin_role_id, perm_id))
if not cursor.fetchone():
cursor.execute('INSERT INTO RolePermission (role_id, permission_id) VALUES (%s, %s)',
(admin_role_id, perm_id))
# Assign non-admin permissions to user role
non_admin_permissions = [
'view_index', 'view_devices', 'view_device', 'view_subnet', 'view_racks', 'view_rack',
'view_audit', 'view_device_types', 'view_device_type_stats', 'view_devices_by_type',
'view_dhcp', 'view_help',
'add_device', 'edit_device', 'delete_device', 'add_device_ip', 'remove_device_ip',
'add_subnet', 'edit_subnet', 'delete_subnet', 'export_subnet_csv',
'add_rack', 'delete_rack', 'add_device_to_rack', 'remove_device_from_rack',
'add_nonnet_device_to_rack', 'export_rack_csv',
'configure_dhcp',
'add_device_type', 'edit_device_type', 'delete_device_type'
]
for perm_name in non_admin_permissions:
cursor.execute('SELECT id FROM Permission WHERE name = %s', (perm_name,))
perm_result = cursor.fetchone()
if perm_result:
perm_id = perm_result[0]
cursor.execute('SELECT role_id FROM RolePermission WHERE role_id = %s AND permission_id = %s',
(user_role_id, perm_id))
if not cursor.fetchone():
cursor.execute('INSERT INTO RolePermission (role_id, permission_id) VALUES (%s, %s)',
(user_role_id, perm_id))
# Assign view-only permissions to view_only role
# Same view permissions as user role, but excluding admin views (view_admin, view_users)
view_only_permissions = [
'view_index', 'view_devices', 'view_device', 'view_subnet', 'view_racks', 'view_rack',
'view_audit', 'view_device_types', 'view_device_type_stats', 'view_devices_by_type',
'view_dhcp', 'view_help'
]
for perm_name in view_only_permissions:
cursor.execute('SELECT id FROM Permission WHERE name = %s', (perm_name,))
perm_result = cursor.fetchone()
if perm_result:
perm_id = perm_result[0]
cursor.execute('SELECT role_id FROM RolePermission WHERE role_id = %s AND permission_id = %s',
(view_only_role_id, perm_id))
if not cursor.fetchone():
cursor.execute('INSERT INTO RolePermission (role_id, permission_id) VALUES (%s, %s)',
(view_only_role_id, perm_id))
# Assign existing users to 'admin' role if they don't have a role
# This ensures existing users maintain admin access
cursor.execute('UPDATE User SET role_id = %s WHERE role_id IS NULL', (admin_role_id,))
# Generate API keys for users that don't have one
cursor.execute('SELECT id FROM User WHERE api_key IS NULL')
users_without_api_key = cursor.fetchall()
for (user_id,) in users_without_api_key:
api_key = generate_api_key()
cursor.execute('UPDATE User SET api_key = %s WHERE id = %s', (api_key, user_id))
cursor.execute('SELECT COUNT(*) FROM User')
if cursor.fetchone()[0] == 0:
api_key = generate_api_key()
cursor.execute('''INSERT INTO User (name, email, password, role_id, api_key) VALUES (%s, %s, %s, %s, %s)''',
('admin', 'admin@example.com', hash_password('password'), admin_role_id, api_key))
conn.commit()
conn.close()