This commit is contained in:
Iliyan Angelov
2025-09-19 11:58:53 +03:00
parent 306b20e24a
commit 6b247e5b9f
11423 changed files with 1500615 additions and 778 deletions

View File

@@ -0,0 +1,468 @@
# Enterprise Incident Management API - Security Module
## Overview
This API provides advanced security features for enterprise incident management, including:
- **Single Sign-On (SSO)** with SAML, LDAP, OAuth2, and OIDC support
- **Multi-Factor Authentication (MFA)** with TOTP support
- **Role-Based Access Control (RBAC)** and Attribute-Based Access Control (ABAC)
- **Immutable Audit Trails** with integrity verification
- **Data Classification** system with 5 security levels
## Base URL
```
http://localhost:8000/security/
```
## Authentication
The API supports multiple authentication methods:
1. **Token Authentication** (Primary)
2. **Session Authentication** (Web interface)
3. **SSO Authentication** (SAML, OAuth2, LDAP)
### Getting an Authentication Token
```bash
POST /security/api/auth/login/
Content-Type: application/json
{
"username": "admin",
"password": "admin123",
"mfa_token": "123456" # Optional, required if MFA is enabled
}
```
**Response:**
```json
{
"token": "your-auth-token-here",
"user": {
"id": "uuid",
"username": "admin",
"email": "admin@example.com",
"mfa_enabled": false,
"clearance_level": {
"name": "TOP_SECRET",
"level": 5
}
},
"message": "Login successful"
}
```
## API Endpoints
### Authentication Endpoints
#### Login
```http
POST /security/api/auth/login/
```
#### Logout
```http
POST /security/api/auth/logout/
Authorization: Token your-token-here
```
#### User Profile
```http
GET /security/api/auth/profile/
Authorization: Token your-token-here
```
#### Change Password
```http
POST /security/api/auth/change-password/
Authorization: Token your-token-here
Content-Type: application/json
{
"current_password": "old-password",
"new_password": "new-password",
"confirm_password": "new-password"
}
```
#### MFA Status
```http
GET /security/api/auth/mfa-status/
Authorization: Token your-token-here
```
### Data Classification Management
#### List Classifications
```http
GET /security/api/classifications/
Authorization: Token your-token-here
```
#### Create Classification
```http
POST /security/api/classifications/
Authorization: Token your-token-here
Content-Type: application/json
{
"name": "CUSTOM",
"level": 6,
"description": "Custom classification level",
"color_code": "#ff0000",
"requires_clearance": true
}
```
#### Update Classification
```http
PUT /security/api/classifications/{id}/
Authorization: Token your-token-here
Content-Type: application/json
{
"name": "CUSTOM",
"level": 6,
"description": "Updated description",
"color_code": "#ff0000",
"requires_clearance": true
}
```
#### Delete Classification
```http
DELETE /security/api/classifications/{id}/
Authorization: Token your-token-here
```
### Role Management
#### List Roles
```http
GET /security/api/roles/
Authorization: Token your-token-here
```
#### Create Role
```http
POST /security/api/roles/
Authorization: Token your-token-here
Content-Type: application/json
{
"name": "Incident Responder",
"description": "Can respond to and manage incidents",
"permission_ids": [1, 2, 3],
"classification_ids": [1, 2, 3],
"is_active": true
}
```
#### Update Role
```http
PUT /security/api/roles/{id}/
Authorization: Token your-token-here
Content-Type: application/json
{
"name": "Senior Incident Responder",
"description": "Can respond to and manage high-priority incidents",
"permission_ids": [1, 2, 3, 4],
"classification_ids": [1, 2, 3, 4],
"is_active": true
}
```
### User Management
#### List Users
```http
GET /security/api/users/
Authorization: Token your-token-here
```
#### Create User
```http
POST /security/api/users/
Authorization: Token your-token-here
Content-Type: application/json
{
"username": "newuser",
"email": "user@example.com",
"first_name": "John",
"last_name": "Doe",
"password": "secure-password",
"employee_id": "EMP001",
"department": "IT Security",
"clearance_level_id": 3,
"role_ids": [1, 2],
"is_active": true
}
```
#### Lock User Account
```http
POST /security/api/users/{id}/lock_account/
Authorization: Token your-token-here
Content-Type: application/json
{
"duration_minutes": 30
}
```
#### Unlock User Account
```http
POST /security/api/users/{id}/unlock_account/
Authorization: Token your-token-here
```
### Multi-Factor Authentication (MFA)
#### List MFA Devices
```http
GET /security/api/mfa-devices/
Authorization: Token your-token-here
```
#### Setup TOTP Device
```http
POST /security/api/mfa-devices/setup_totp/
Authorization: Token your-token-here
Content-Type: application/json
{
"device_name": "My Phone",
"device_type": "TOTP"
}
```
**Response:**
```json
{
"device_id": "uuid",
"qr_code_data": "otpauth://totp/...",
"qr_code_image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...",
"message": "TOTP device created. Scan QR code with authenticator app."
}
```
#### Verify TOTP Token
```http
POST /security/api/mfa-devices/{id}/verify_totp/
Authorization: Token your-token-here
Content-Type: application/json
{
"token": "123456"
}
```
#### Enable MFA
```http
POST /security/api/mfa-devices/enable_mfa/
Authorization: Token your-token-here
```
#### Disable MFA
```http
POST /security/api/mfa-devices/disable_mfa/
Authorization: Token your-token-here
```
### Audit Logs
#### List Audit Logs
```http
GET /security/api/audit-logs/
Authorization: Token your-token-here
```
**Query Parameters:**
- `start_date`: Filter logs from this date (YYYY-MM-DD)
- `end_date`: Filter logs until this date (YYYY-MM-DD)
- `action_type`: Filter by action type (LOGIN, LOGOUT, etc.)
- `severity`: Filter by severity (LOW, MEDIUM, HIGH, CRITICAL)
- `user_id`: Filter by user ID
**Example:**
```http
GET /security/api/audit-logs/?start_date=2024-01-01&severity=HIGH&action_type=LOGIN_FAILED
Authorization: Token your-token-here
```
### SSO Provider Management
#### List SSO Providers
```http
GET /security/api/sso-providers/
Authorization: Token your-token-here
```
#### Create SSO Provider
```http
POST /security/api/sso-providers/
Authorization: Token your-token-here
Content-Type: application/json
{
"name": "Company SAML",
"provider_type": "SAML",
"is_active": true,
"configuration": {
"entity_id": "https://company.com/saml",
"sso_url": "https://company.com/saml/sso",
"x509_cert": "-----BEGIN CERTIFICATE-----..."
},
"attribute_mapping": {
"username": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name",
"email": "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
}
}
```
### Access Policy Management
#### List Access Policies
```http
GET /security/api/access-policies/
Authorization: Token your-token-here
```
#### Create Access Policy
```http
POST /security/api/access-policies/
Authorization: Token your-token-here
Content-Type: application/json
{
"name": "High Security Access",
"description": "Allow access to high-security incidents only for senior staff",
"policy_type": "ALLOW",
"conditions": {
"user_clearance_level": {"gte": 4},
"user_roles": {"contains": "Security Admin"},
"time_of_day": {"between": [9, 17]}
},
"resource_type": "incident",
"actions": ["view", "edit"],
"priority": 10,
"is_active": true
}
```
## Data Classification Levels
The system includes 5 predefined classification levels:
1. **PUBLIC** (Level 1) - Public information
2. **INTERNAL** (Level 2) - Internal company information
3. **CONFIDENTIAL** (Level 3) - Confidential information
4. **RESTRICTED** (Level 4) - Restricted information
5. **TOP_SECRET** (Level 5) - Top secret information
## Security Features
### Immutable Audit Trails
All security-relevant actions are logged with:
- SHA-256 hash for integrity verification
- Timestamp and user information
- IP address and user agent
- Action details and severity level
- Immutable records (cannot be modified or deleted)
### Role-Based Access Control (RBAC)
- Users can have multiple roles
- Roles contain permissions and data classification access
- Permissions are inherited from roles
- Data access is controlled by clearance levels
### Multi-Factor Authentication (MFA)
- TOTP (Time-based One-Time Password) support
- QR code generation for easy setup
- Multiple devices per user
- Primary device designation
### Single Sign-On (SSO)
- SAML 2.0 support
- OAuth2/OIDC support (Google, Microsoft)
- LDAP authentication
- Configurable attribute mapping
## Error Handling
The API returns standard HTTP status codes:
- `200 OK` - Success
- `201 Created` - Resource created
- `400 Bad Request` - Invalid request data
- `401 Unauthorized` - Authentication required
- `403 Forbidden` - Insufficient permissions
- `404 Not Found` - Resource not found
- `423 Locked` - Account locked
- `500 Internal Server Error` - Server error
Error responses include detailed error messages:
```json
{
"error": "Invalid credentials",
"details": "Username or password is incorrect"
}
```
## Rate Limiting
The API implements rate limiting for security endpoints:
- Login attempts: 5 per minute per IP
- MFA verification: 10 per minute per user
- Password changes: 3 per hour per user
## Getting Started
1. **Start the development server:**
```bash
python manage.py runserver
```
2. **Access the admin interface:**
```
http://localhost:8000/admin/
Username: admin
Password: admin123
```
3. **Test the API:**
```bash
# Login
curl -X POST http://localhost:8000/security/api/auth/login/ \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "admin123"}'
# Get user profile
curl -X GET http://localhost:8000/security/api/auth/profile/ \
-H "Authorization: Token your-token-here"
```
## Security Best Practices
1. **Change default passwords** immediately
2. **Enable MFA** for all administrative accounts
3. **Configure SSO** for production environments
4. **Regular audit log review** for security events
5. **Implement proper data classification** for all resources
6. **Use HTTPS** in production
7. **Regular security updates** and patches
## Support
For technical support or security concerns, contact the security team at security@etb-incident-management.com.

View File

@@ -0,0 +1,372 @@
# Zero Trust Architecture Implementation
## Overview
The ETB-API security module now implements a comprehensive Zero Trust Architecture that goes beyond traditional perimeter-based security. This implementation provides context-aware, risk-based access control that continuously verifies and validates every access request.
## Core Zero Trust Principles
### 1. **Never Trust, Always Verify**
- Every access request is evaluated regardless of source
- Continuous verification of user identity and device trust
- No implicit trust based on network location
### 2. **Least Privilege Access**
- Users receive minimum necessary access based on context
- Dynamic permission adjustment based on risk assessment
- Time-limited access with automatic expiration
### 3. **Assume Breach**
- Continuous monitoring and threat detection
- Behavioral anomaly detection
- Rapid response to security incidents
## Zero Trust Components
### 1. Device Posture Assessment
**Purpose**: Evaluate the security posture of devices attempting to access the system.
**Features**:
- Device identification and fingerprinting
- Security configuration assessment (antivirus, firewall, encryption)
- Network type detection (corporate, home, public)
- VPN connection status
- Compliance verification
- Risk scoring (0-100)
**API Endpoints**:
```http
GET /security/api/device-postures/ # List user's devices
POST /security/api/device-postures/ # Register new device
POST /security/api/device-postures/{id}/update_posture/ # Update device info
POST /security/api/device-postures/register_device/ # Register device
```
**Example Device Registration**:
```json
{
"device_id": "unique-device-identifier",
"device_name": "John's Laptop",
"device_type": "LAPTOP",
"os_type": "WINDOWS",
"os_version": "Windows 11",
"is_managed": true,
"has_antivirus": true,
"firewall_enabled": true,
"encryption_enabled": true,
"screen_lock_enabled": true,
"biometric_auth": true,
"network_type": "Corporate",
"vpn_connected": true
}
```
### 2. Geolocation-Based Access Control
**Purpose**: Control access based on geographic location and network context.
**Features**:
- Country, region, and city-based restrictions
- IP range allow/block lists
- Distance-based access control from office locations
- Time zone and working hours restrictions
- Risk-based location scoring
**API Endpoints**:
```http
GET /security/api/geolocation-rules/ # List geolocation rules
POST /security/api/geolocation-rules/ # Create new rule
POST /security/api/geolocation-rules/{id}/test_rule/ # Test rule
```
**Example Geolocation Rule**:
```json
{
"name": "Bulgaria Office Access",
"rule_type": "ALLOW",
"allowed_countries": ["BG"],
"allowed_cities": ["Sofia", "Plovdiv", "Varna"],
"max_distance_from_office": 50.0,
"office_latitude": 42.6977,
"office_longitude": 23.3219,
"working_hours_only": true,
"working_hours_start": "08:00",
"working_hours_end": "18:00",
"working_days": [0, 1, 2, 3, 4]
}
```
### 3. Risk Assessment Engine
**Purpose**: Continuously assess risk for every access request using multiple factors.
**Risk Factors**:
- **Device Risk** (25%): Device security posture and compliance
- **Location Risk** (20%): Geographic and network location
- **Behavior Risk** (20%): User behavior patterns and anomalies
- **Network Risk** (15%): Network security and VPN status
- **Time Risk** (10%): Access time and working hours
- **User Risk** (10%): User account status and history
**Risk Levels**:
- **LOW** (0-25): Normal access granted
- **MEDIUM** (26-50): Step-up authentication required
- **HIGH** (51-75): Manual review required
- **CRITICAL** (76-100): Access denied
**API Endpoints**:
```http
GET /security/api/risk-assessments/ # List user's assessments
POST /security/api/risk-assessments/assess_access/ # Perform assessment
GET /security/api/risk-assessments/my_risk_profile/ # Get risk profile
```
### 4. Adaptive Authentication
**Purpose**: Dynamically adjust authentication requirements based on risk level.
**Features**:
- Risk-based authentication method selection
- Context-aware risk adjustment
- Behavioral analysis integration
- Machine learning support (optional)
- Fallback authentication methods
**Authentication Methods**:
- Password
- MFA TOTP
- MFA SMS/Email
- Biometric
- Hardware Token
- SSO
- Certificate
**API Endpoints**:
```http
GET /security/api/adaptive-auth/ # List adaptive auth configs
POST /security/api/adaptive-auth/{id}/test_auth_requirements/ # Test requirements
```
### 5. Behavioral Analysis
**Purpose**: Learn and detect anomalous user behavior patterns.
**Features**:
- Login time and location patterns
- Device usage patterns
- Access frequency analysis
- Session duration tracking
- Anomaly scoring (0-1)
**API Endpoints**:
```http
GET /security/api/behavior-profiles/ # List behavior profiles
POST /security/api/behavior-profiles/{id}/calculate_anomaly/ # Calculate anomaly
```
## Zero Trust Middleware
The system includes three middleware components that automatically apply Zero Trust principles:
### 1. ZeroTrustMiddleware
- Intercepts all requests
- Performs risk assessment
- Applies access policies
- Updates behavior profiles
### 2. DeviceRegistrationMiddleware
- Handles device registration requests
- Validates device information
- Creates device posture records
### 3. RiskBasedRateLimitMiddleware
- Applies rate limiting based on risk level
- Higher risk = stricter limits
- Prevents abuse and brute force attacks
## Configuration
### Settings Configuration
```python
# Zero Trust Architecture Settings
ZERO_TRUST_ENABLED = True
ZERO_TRUST_STRICT_MODE = False # Set to True for maximum security
# Geolocation API Settings
GEO_API_KEY = "your-api-key" # Set your geolocation API key
GEO_API_PROVIDER = 'ipapi' # Options: 'ipapi', 'ipinfo', 'maxmind'
# Device Posture Assessment
DEVICE_POSTURE_ENABLED = True
DEVICE_POSTURE_STRICT_MODE = False
DEVICE_POSTURE_UPDATE_INTERVAL = 3600 # Update every hour
# Risk Assessment Settings
RISK_ASSESSMENT_ENABLED = True
RISK_ASSESSMENT_CACHE_TTL = 300 # Cache for 5 minutes
RISK_ASSESSMENT_ML_ENABLED = False # Enable ML-based assessment
# Behavioral Analysis Settings
BEHAVIORAL_ANALYSIS_ENABLED = True
BEHAVIORAL_LEARNING_PERIOD = 30 # Days to learn behavior
BEHAVIORAL_ANOMALY_THRESHOLD = 0.7 # Anomaly threshold
# Adaptive Authentication Settings
ADAPTIVE_AUTH_ENABLED = True
ADAPTIVE_AUTH_FALLBACK_METHODS = ['PASSWORD', 'MFA_TOTP']
ADAPTIVE_AUTH_MAX_ATTEMPTS = 3
ADAPTIVE_AUTH_LOCKOUT_DURATION = 15 # minutes
```
## API Usage Examples
### 1. Check Zero Trust Status
```bash
curl -X GET "http://localhost:8000/security/api/zero-trust/status/" \
-H "Authorization: Token your-token"
```
**Response**:
```json
{
"zero_trust_enabled": true,
"user_status": {
"registered_devices": 2,
"trusted_devices": 1,
"latest_risk_level": "MEDIUM",
"latest_risk_score": 35
},
"system_configuration": {
"adaptive_auth_enabled": true,
"geolocation_rules_count": 3,
"behavioral_analysis_enabled": true,
"device_posture_enabled": true
},
"recommendations": [
{
"type": "device",
"priority": "medium",
"message": "1 untrusted devices detected",
"action": "improve_device_security"
}
]
}
```
### 2. Perform Risk Assessment
```bash
curl -X POST "http://localhost:8000/security/api/zero-trust/assess/" \
-H "Authorization: Token your-token" \
-H "Content-Type: application/json" \
-d '{
"assessment_type": "ACCESS",
"resource_type": "incident",
"device_id": "device-123",
"location_data": {
"latitude": 42.6977,
"longitude": 23.3219,
"country_code": "BG",
"city": "Sofia"
}
}'
```
**Response**:
```json
{
"access_granted": true,
"reason": "Access granted - low risk",
"required_actions": [],
"risk_level": "LOW",
"risk_score": 25,
"auth_requirements": ["PASSWORD"],
"assessment_id": "uuid-here"
}
```
### 3. Register Device
```bash
curl -X POST "http://localhost:8000/security/api/device-postures/register_device/" \
-H "Authorization: Token your-token" \
-H "Content-Type: application/json" \
-d '{
"device_id": "unique-device-id",
"device_name": "My Laptop",
"device_type": "LAPTOP",
"os_type": "WINDOWS",
"is_managed": true,
"has_antivirus": true,
"firewall_enabled": true,
"encryption_enabled": true
}'
```
## Security Benefits
### 1. **Enhanced Security Posture**
- Continuous verification of all access requests
- Context-aware access decisions
- Reduced attack surface through least privilege
### 2. **Improved Compliance**
- Comprehensive audit trails
- Risk-based access controls
- Regulatory compliance support (GDPR, ISO 27001, SOC 2)
### 3. **Better User Experience**
- Adaptive authentication reduces friction for low-risk access
- Transparent security controls
- Self-service device registration
### 4. **Operational Efficiency**
- Automated risk assessment
- Reduced manual security reviews
- Proactive threat detection
## Implementation Considerations
### 1. **Performance**
- Risk assessments are cached for 5 minutes
- Geolocation lookups are optimized
- Database queries are indexed for performance
### 2. **Scalability**
- Middleware can be disabled for high-traffic scenarios
- Risk assessment can be moved to background tasks
- Caching strategies for large-scale deployments
### 3. **Privacy**
- User behavior data is anonymized
- Geolocation data is not stored permanently
- Compliance with data protection regulations
### 4. **Monitoring**
- Comprehensive audit logging
- Risk assessment metrics
- Security event correlation
## Future Enhancements
### 1. **Machine Learning Integration**
- ML-based risk scoring
- Behavioral pattern recognition
- Predictive threat detection
### 2. **Advanced Analytics**
- Risk trend analysis
- Security posture dashboards
- Compliance reporting
### 3. **Integration Capabilities**
- SIEM integration
- Threat intelligence feeds
- External security tools
## Conclusion
The Zero Trust Architecture implementation provides a robust, scalable, and comprehensive security framework that continuously adapts to changing threat landscapes while maintaining usability and compliance requirements. This implementation positions the ETB-API as a leader in enterprise security architecture.

View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

326
ETB-API/security/admin.py Normal file
View File

@@ -0,0 +1,326 @@
"""
Admin configuration for security models
"""
from django.contrib import admin
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
from django.utils.html import format_html
from django.utils import timezone
from .models import (
DataClassification, Role, User, MFADevice,
AuditLog, SSOProvider, AccessPolicy, DevicePosture,
GeolocationRule, RiskAssessment, AdaptiveAuthentication,
UserBehaviorProfile
)
@admin.register(DataClassification)
class DataClassificationAdmin(admin.ModelAdmin):
list_display = ['name', 'level', 'color_code', 'requires_clearance', 'created_at']
list_filter = ['requires_clearance', 'created_at']
search_fields = ['name', 'description']
ordering = ['level']
@admin.register(Role)
class RoleAdmin(admin.ModelAdmin):
list_display = ['name', 'is_active', 'permission_count', 'classification_count', 'created_at']
list_filter = ['is_active', 'created_at']
search_fields = ['name', 'description']
filter_horizontal = ['permissions', 'data_classification_access']
def permission_count(self, obj):
return obj.permissions.count()
permission_count.short_description = 'Permissions'
def classification_count(self, obj):
return obj.data_classification_access.count()
classification_count.short_description = 'Classifications'
class MFADeviceInline(admin.TabularInline):
model = MFADevice
extra = 0
readonly_fields = ['secret_key', 'last_used', 'created_at']
@admin.register(User)
class UserAdmin(BaseUserAdmin):
list_display = [
'username', 'email', 'first_name', 'last_name',
'clearance_level', 'mfa_enabled', 'is_active', 'is_staff'
]
list_filter = [
'is_active', 'is_staff', 'is_superuser', 'mfa_enabled',
'clearance_level', 'sso_provider', 'created_at'
]
search_fields = ['username', 'email', 'first_name', 'last_name', 'employee_id']
ordering = ['username']
fieldsets = BaseUserAdmin.fieldsets + (
('Security Information', {
'fields': (
'employee_id', 'department', 'clearance_level',
'mfa_enabled', 'mfa_secret', 'last_login_ip',
'failed_login_attempts', 'account_locked_until'
)
}),
('SSO Information', {
'fields': ('sso_provider', 'sso_identifier')
}),
('Additional Information', {
'fields': ('attributes', 'created_at', 'updated_at')
}),
)
readonly_fields = ['mfa_secret', 'last_login_ip', 'failed_login_attempts',
'account_locked_until', 'created_at', 'updated_at']
filter_horizontal = ['roles']
inlines = [MFADeviceInline]
def get_queryset(self, request):
return super().get_queryset(request).select_related('clearance_level')
@admin.register(MFADevice)
class MFADeviceAdmin(admin.ModelAdmin):
list_display = ['user', 'name', 'device_type', 'is_active', 'is_primary', 'last_used']
list_filter = ['device_type', 'is_active', 'is_primary', 'created_at']
search_fields = ['user__username', 'name']
readonly_fields = ['secret_key', 'created_at', 'updated_at']
def get_queryset(self, request):
return super().get_queryset(request).select_related('user')
@admin.register(AuditLog)
class AuditLogAdmin(admin.ModelAdmin):
list_display = [
'timestamp', 'user', 'action_type', 'resource_type',
'severity', 'ip_address'
]
list_filter = [
'action_type', 'severity', 'timestamp', 'user'
]
search_fields = [
'user__username', 'action_type', 'resource_type',
'resource_id', 'ip_address'
]
readonly_fields = [
'id', 'timestamp', 'hash_value'
]
date_hierarchy = 'timestamp'
def get_queryset(self, request):
return super().get_queryset(request).select_related('user')
def has_add_permission(self, request):
return False # Audit logs should not be manually created
def has_change_permission(self, request, obj=None):
return False # Audit logs should be immutable
def has_delete_permission(self, request, obj=None):
return False # Audit logs should not be deleted
@admin.register(SSOProvider)
class SSOProviderAdmin(admin.ModelAdmin):
list_display = ['name', 'provider_type', 'is_active', 'created_at']
list_filter = ['provider_type', 'is_active', 'created_at']
search_fields = ['name']
readonly_fields = ['created_at', 'updated_at']
@admin.register(AccessPolicy)
class AccessPolicyAdmin(admin.ModelAdmin):
list_display = [
'name', 'policy_type', 'priority', 'requires_device_trust',
'requires_geolocation_check', 'is_active', 'created_at'
]
list_filter = [
'policy_type', 'requires_device_trust', 'requires_geolocation_check',
'is_active', 'created_at'
]
search_fields = ['name', 'description']
readonly_fields = ['created_at', 'updated_at']
ordering = ['priority', 'name']
fieldsets = (
('Basic Information', {
'fields': ('name', 'description', 'policy_type', 'priority', 'is_active')
}),
('Zero Trust Settings', {
'fields': ('requires_device_trust', 'min_device_trust_level', 'requires_geolocation_check', 'geolocation_rule')
}),
('Risk & Compliance', {
'fields': ('max_risk_score', 'requires_compliant_device')
}),
('Adaptive Authentication', {
'fields': ('adaptive_auth_enabled', 'auth_factors_required')
}),
('Conditions & Actions', {
'fields': ('conditions', 'resource_type', 'actions', 'time_restrictions')
})
)
@admin.register(DevicePosture)
class DevicePostureAdmin(admin.ModelAdmin):
list_display = ['device_name', 'user', 'device_type', 'os_type', 'trust_level', 'risk_score', 'is_compliant', 'last_seen']
list_filter = ['device_type', 'os_type', 'trust_level', 'is_compliant', 'is_managed', 'is_active', 'last_seen']
search_fields = ['device_name', 'device_id', 'user__username', 'user__email']
readonly_fields = ['device_id', 'risk_score', 'trust_level', 'first_seen', 'last_seen']
ordering = ['-last_seen']
fieldsets = (
('Device Information', {
'fields': ('user', 'device_id', 'device_name', 'device_type', 'os_type', 'os_version', 'browser_info')
}),
('Security Posture', {
'fields': ('is_managed', 'has_antivirus', 'antivirus_status', 'firewall_enabled', 'encryption_enabled', 'screen_lock_enabled', 'biometric_auth')
}),
('Network Information', {
'fields': ('ip_address', 'mac_address', 'network_type', 'vpn_connected')
}),
('Risk Assessment', {
'fields': ('risk_score', 'trust_level', 'is_trusted', 'is_compliant', 'compliance_issues', 'assessment_details')
}),
('Status & Timestamps', {
'fields': ('is_active', 'first_seen', 'last_seen')
})
)
def get_queryset(self, request):
return super().get_queryset(request).select_related('user')
@admin.register(GeolocationRule)
class GeolocationRuleAdmin(admin.ModelAdmin):
list_display = ['name', 'rule_type', 'priority', 'is_active', 'created_at']
list_filter = ['rule_type', 'is_active', 'created_at']
search_fields = ['name', 'description']
ordering = ['priority', 'name']
fieldsets = (
('Basic Information', {
'fields': ('name', 'description', 'rule_type', 'priority', 'is_active')
}),
('Geographic Conditions', {
'fields': ('allowed_countries', 'blocked_countries', 'allowed_regions', 'blocked_regions', 'allowed_cities', 'blocked_cities')
}),
('IP-based Rules', {
'fields': ('allowed_ip_ranges', 'blocked_ip_ranges')
}),
('Time-based Conditions', {
'fields': ('allowed_time_zones', 'working_hours_only', 'working_hours_start', 'working_hours_end', 'working_days')
}),
('Distance Rules', {
'fields': ('max_distance_from_office', 'office_latitude', 'office_longitude')
}),
('Actions & Notifications', {
'fields': ('notification_message', 'log_violation', 'require_manager_approval')
})
)
@admin.register(RiskAssessment)
class RiskAssessmentAdmin(admin.ModelAdmin):
list_display = ['user', 'assessment_type', 'risk_level', 'overall_risk_score', 'access_decision', 'assessed_at']
list_filter = ['assessment_type', 'risk_level', 'access_decision', 'assessed_at']
search_fields = ['user__username', 'user__email', 'resource_type', 'resource_id']
readonly_fields = ['assessed_at', 'overall_risk_score', 'risk_level', 'access_decision']
ordering = ['-assessed_at']
fieldsets = (
('Assessment Context', {
'fields': ('user', 'assessment_type', 'resource_type', 'resource_id')
}),
('Risk Scores', {
'fields': ('device_risk_score', 'location_risk_score', 'behavior_risk_score', 'network_risk_score', 'time_risk_score', 'user_risk_score')
}),
('Overall Assessment', {
'fields': ('overall_risk_score', 'risk_level', 'access_decision', 'decision_reason')
}),
('Context Data', {
'fields': ('ip_address', 'user_agent', 'location_data', 'device_data', 'behavior_data')
}),
('Assessment Details', {
'fields': ('risk_factors', 'mitigation_actions', 'assessment_details')
}),
('Timestamps', {
'fields': ('assessed_at', 'expires_at')
})
)
def get_queryset(self, request):
return super().get_queryset(request).select_related('user')
@admin.register(AdaptiveAuthentication)
class AdaptiveAuthenticationAdmin(admin.ModelAdmin):
list_display = ['name', 'is_active', 'ml_enabled', 'enable_behavioral_analysis', 'created_at']
list_filter = ['is_active', 'ml_enabled', 'enable_behavioral_analysis', 'created_at']
search_fields = ['name', 'description']
ordering = ['name']
fieldsets = (
('Basic Information', {
'fields': ('name', 'description', 'is_active')
}),
('Risk Thresholds', {
'fields': ('low_risk_threshold', 'medium_risk_threshold', 'high_risk_threshold')
}),
('Authentication Methods by Risk Level', {
'fields': ('low_risk_auth_methods', 'medium_risk_auth_methods', 'high_risk_auth_methods', 'critical_risk_auth_methods')
}),
('Context-based Adjustments', {
'fields': ('device_trust_multiplier', 'location_trust_multiplier', 'time_trust_multiplier')
}),
('Behavioral Analysis', {
'fields': ('enable_behavioral_analysis', 'behavior_learning_period', 'anomaly_threshold')
}),
('Machine Learning', {
'fields': ('ml_enabled', 'ml_model_path', 'ml_confidence_threshold')
}),
('Fallback Options', {
'fields': ('fallback_auth_methods', 'max_auth_attempts', 'lockout_duration')
})
)
@admin.register(UserBehaviorProfile)
class UserBehaviorProfileAdmin(admin.ModelAdmin):
list_display = ['user', 'is_learning', 'anomaly_score', 'sample_count', 'last_updated']
list_filter = ['is_learning', 'last_updated']
search_fields = ['user__username', 'user__email']
readonly_fields = ['learning_start_date', 'learning_complete_date', 'sample_count', 'last_updated', 'created_at']
ordering = ['-last_updated']
fieldsets = (
('User Information', {
'fields': ('user',)
}),
('Login Patterns', {
'fields': ('typical_login_times', 'typical_login_locations', 'typical_login_devices')
}),
('Access Patterns', {
'fields': ('typical_access_times', 'typical_access_patterns', 'typical_session_duration')
}),
('Network Patterns', {
'fields': ('typical_ip_ranges', 'typical_user_agents')
}),
('Behavioral Metrics', {
'fields': ('login_frequency', 'access_frequency', 'anomaly_score')
}),
('Learning Status', {
'fields': ('is_learning', 'learning_start_date', 'learning_complete_date', 'sample_count')
}),
('Timestamps', {
'fields': ('created_at', 'last_updated')
})
)
def get_queryset(self, request):
return super().get_queryset(request).select_related('user')

6
ETB-API/security/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class SecurityConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'security'

View File

@@ -0,0 +1,9 @@
# Authentication module for SSO and MFA
from .sso import SSOAuthentication, SAMLAuthentication, OAuth2Authentication, LDAPAuthentication
__all__ = [
'SSOAuthentication',
'SAMLAuthentication',
'OAuth2Authentication',
'LDAPAuthentication'
]

View File

@@ -0,0 +1,324 @@
"""
Single Sign-On (SSO) Authentication Backends
Supports SAML, OAuth2, OIDC, and LDAP
"""
import base64
import json
import logging
from typing import Optional, Dict, Any
from urllib.parse import urlencode, parse_qs
import requests
from django.contrib.auth import get_user_model
from django.contrib.auth.backends import BaseBackend
from django.conf import settings
from django.core.exceptions import ValidationError
from ..models import AuditLog, User
logger = logging.getLogger(__name__)
User = get_user_model()
class SSOAuthentication(BaseBackend):
"""Base SSO authentication backend"""
def authenticate(self, request, **kwargs):
"""Authenticate user via SSO"""
if not request:
return None
# Extract SSO token from request
sso_token = self._extract_sso_token(request)
if not sso_token:
return None
# Validate token and get user info
user_info = self._validate_sso_token(sso_token)
if not user_info:
return None
# Get or create user
user = self._get_or_create_user(user_info)
if user:
self._log_sso_login(user, request)
return user
def _extract_sso_token(self, request) -> Optional[str]:
"""Extract SSO token from request"""
# Check Authorization header
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
if auth_header.startswith('Bearer '):
return auth_header[7:]
# Check query parameter
return request.GET.get('sso_token')
def _validate_sso_token(self, token: str) -> Optional[Dict[str, Any]]:
"""Validate SSO token and return user info"""
raise NotImplementedError("Subclasses must implement _validate_sso_token")
def _get_or_create_user(self, user_info: Dict[str, Any]) -> Optional[User]:
"""Get or create user from SSO user info"""
try:
# Try to find existing user by SSO identifier
user = User.objects.get(
sso_provider=user_info.get('provider'),
sso_identifier=user_info.get('id')
)
# Update user info
self._update_user_from_sso(user, user_info)
return user
except User.DoesNotExist:
# Create new user
return self._create_user_from_sso(user_info)
def _update_user_from_sso(self, user: User, user_info: Dict[str, Any]):
"""Update existing user with SSO info"""
user.email = user_info.get('email', user.email)
user.first_name = user_info.get('first_name', user.first_name)
user.last_name = user_info.get('last_name', user.last_name)
user.last_login_ip = user_info.get('ip_address')
user.save(update_fields=['email', 'first_name', 'last_name', 'last_login_ip'])
def _create_user_from_sso(self, user_info: Dict[str, Any]) -> Optional[User]:
"""Create new user from SSO info"""
try:
username = user_info.get('username') or user_info.get('email')
if not username:
logger.error("No username or email provided in SSO user info")
return None
user = User.objects.create_user(
username=username,
email=user_info.get('email', ''),
first_name=user_info.get('first_name', ''),
last_name=user_info.get('last_name', ''),
sso_provider=user_info.get('provider'),
sso_identifier=user_info.get('id'),
is_active=True
)
logger.info(f"Created new user via SSO: {user.username}")
return user
except Exception as e:
logger.error(f"Failed to create user from SSO: {e}")
return None
def _log_sso_login(self, user: User, request):
"""Log SSO login event"""
AuditLog.objects.create(
user=user,
action_type='SSO_LOGIN',
ip_address=self._get_client_ip(request),
user_agent=request.META.get('HTTP_USER_AGENT', ''),
details={
'provider': user.sso_provider,
'sso_identifier': user.sso_identifier
}
)
def _get_client_ip(self, request) -> str:
"""Get client IP address from request"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip
class SAMLAuthentication(SSOAuthentication):
"""SAML SSO authentication"""
def _validate_sso_token(self, token: str) -> Optional[Dict[str, Any]]:
"""Validate SAML token"""
# This would integrate with a SAML library like python3-saml
# For now, return mock data
try:
# Decode and validate SAML assertion
# In production, use proper SAML validation
decoded_token = base64.b64decode(token).decode('utf-8')
# Mock SAML response parsing
return {
'id': 'saml_user_123',
'username': 'saml.user',
'email': 'saml.user@company.com',
'first_name': 'SAML',
'last_name': 'User',
'provider': 'saml',
'ip_address': '192.168.1.100'
}
except Exception as e:
logger.error(f"SAML token validation failed: {e}")
return None
class OAuth2Authentication(SSOAuthentication):
"""OAuth2 SSO authentication"""
def _validate_sso_token(self, token: str) -> Optional[Dict[str, Any]]:
"""Validate OAuth2 token"""
# Validate token with OAuth2 provider
providers = settings.SSO_PROVIDERS.get('oauth2', {}).get('providers', {})
for provider_name, config in providers.items():
if not config.get('client_id'):
continue
user_info = self._validate_oauth2_token(token, provider_name, config)
if user_info:
user_info['provider'] = f'oauth2_{provider_name}'
return user_info
return None
def _validate_oauth2_token(self, token: str, provider: str, config: Dict) -> Optional[Dict[str, Any]]:
"""Validate OAuth2 token with specific provider"""
try:
if provider == 'google':
return self._validate_google_token(token)
elif provider == 'microsoft':
return self._validate_microsoft_token(token)
else:
logger.warning(f"Unsupported OAuth2 provider: {provider}")
return None
except Exception as e:
logger.error(f"OAuth2 token validation failed for {provider}: {e}")
return None
def _validate_google_token(self, token: str) -> Optional[Dict[str, Any]]:
"""Validate Google OAuth2 token"""
try:
# Verify token with Google
response = requests.get(
'https://www.googleapis.com/oauth2/v2/userinfo',
headers={'Authorization': f'Bearer {token}'}
)
if response.status_code == 200:
data = response.json()
return {
'id': data.get('id'),
'username': data.get('email'),
'email': data.get('email'),
'first_name': data.get('given_name', ''),
'last_name': data.get('family_name', ''),
}
except Exception as e:
logger.error(f"Google token validation failed: {e}")
return None
def _validate_microsoft_token(self, token: str) -> Optional[Dict[str, Any]]:
"""Validate Microsoft OAuth2 token"""
try:
# Verify token with Microsoft Graph
response = requests.get(
'https://graph.microsoft.com/v1.0/me',
headers={'Authorization': f'Bearer {token}'}
)
if response.status_code == 200:
data = response.json()
return {
'id': data.get('id'),
'username': data.get('userPrincipalName'),
'email': data.get('mail') or data.get('userPrincipalName'),
'first_name': data.get('givenName', ''),
'last_name': data.get('surname', ''),
}
except Exception as e:
logger.error(f"Microsoft token validation failed: {e}")
return None
class LDAPAuthentication(BaseBackend):
"""LDAP authentication backend"""
def authenticate(self, request, username=None, password=None, **kwargs):
"""Authenticate user via LDAP"""
if not username or not password:
return None
try:
# This would integrate with python-ldap
# For now, return mock authentication
ldap_config = settings.SSO_PROVIDERS.get('ldap', {})
if not ldap_config.get('enabled'):
return None
# Mock LDAP authentication
if self._authenticate_ldap_user(username, password):
user = self._get_or_create_ldap_user(username)
if user:
self._log_ldap_login(user, request)
return user
except Exception as e:
logger.error(f"LDAP authentication failed: {e}")
return None
def _authenticate_ldap_user(self, username: str, password: str) -> bool:
"""Authenticate user against LDAP server"""
# Mock LDAP authentication
# In production, use python-ldap to connect to LDAP server
return username == 'ldap.user' and password == 'ldap_password'
def _get_or_create_ldap_user(self, username: str) -> Optional[User]:
"""Get or create user from LDAP"""
try:
user = User.objects.get(
sso_provider='ldap',
sso_identifier=username
)
return user
except User.DoesNotExist:
# Create new user from LDAP
try:
user = User.objects.create_user(
username=username,
email=f'{username}@company.com',
sso_provider='ldap',
sso_identifier=username,
is_active=True
)
logger.info(f"Created new user via LDAP: {user.username}")
return user
except Exception as e:
logger.error(f"Failed to create user from LDAP: {e}")
return None
def _log_ldap_login(self, user: User, request):
"""Log LDAP login event"""
AuditLog.objects.create(
user=user,
action_type='SSO_LOGIN',
ip_address=self._get_client_ip(request),
user_agent=request.META.get('HTTP_USER_AGENT', ''),
details={
'provider': 'ldap',
'sso_identifier': user.sso_identifier
}
)
def _get_client_ip(self, request) -> str:
"""Get client IP address from request"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip

View File

@@ -0,0 +1,829 @@
"""
Enterprise Security System for ETB-API
Comprehensive security features including threat detection, audit logging, and compliance
"""
import logging
import hashlib
import hmac
import time
import json
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Any, Union
from django.http import HttpRequest, JsonResponse
from django.conf import settings
from django.utils import timezone
from django.core.cache import cache
from django.contrib.auth.models import User
from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed
from django.dispatch import receiver
from django.db.models import Q
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.throttling import UserRateThrottle
import requests
import ipaddress
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import base64
import os
logger = logging.getLogger(__name__)
class SecurityEvent:
"""Security event tracking"""
def __init__(self, event_type: str, severity: str, description: str,
user: Optional[User] = None, ip_address: str = None,
user_agent: str = None, metadata: Dict[str, Any] = None):
self.event_type = event_type
self.severity = severity
self.description = description
self.user = user
self.ip_address = ip_address
self.user_agent = user_agent
self.metadata = metadata or {}
self.timestamp = timezone.now()
self.id = self._generate_event_id()
def _generate_event_id(self) -> str:
"""Generate unique event ID"""
data = f"{self.event_type}{self.timestamp.isoformat()}{self.ip_address or ''}"
return hashlib.sha256(data.encode()).hexdigest()[:16]
def to_dict(self) -> Dict[str, Any]:
"""Convert to dictionary for storage"""
return {
'id': self.id,
'event_type': self.event_type,
'severity': self.severity,
'description': self.description,
'user_id': self.user.id if self.user else None,
'username': self.user.username if self.user else None,
'ip_address': self.ip_address,
'user_agent': self.user_agent,
'metadata': self.metadata,
'timestamp': self.timestamp.isoformat(),
}
class ThreatDetectionService:
"""Enterprise threat detection and analysis"""
def __init__(self):
self.suspicious_patterns = [
'sql_injection',
'xss_attempt',
'path_traversal',
'command_injection',
'brute_force',
'credential_stuffing',
'anomalous_behavior',
'privilege_escalation',
]
self.risk_factors = {
'high_risk_ips': self._load_high_risk_ips(),
'suspicious_user_agents': self._load_suspicious_user_agents(),
'known_attack_patterns': self._load_attack_patterns(),
}
def _load_high_risk_ips(self) -> List[str]:
"""Load list of high-risk IP addresses"""
# In production, this would load from a threat intelligence feed
return [
'192.168.1.100', # Example suspicious IP
'10.0.0.50', # Example suspicious IP
]
def _load_suspicious_user_agents(self) -> List[str]:
"""Load list of suspicious user agents"""
return [
'sqlmap',
'nikto',
'nmap',
'masscan',
'zap',
'burp',
]
def _load_attack_patterns(self) -> List[str]:
"""Load known attack patterns"""
return [
r'(\b(SELECT|INSERT|UPDATE|DELETE|DROP|CREATE|ALTER|EXEC|UNION)\b)',
r'<script[^>]*>.*?</script>',
r'javascript:',
r'\.\./',
r'\.\.\\',
r'<iframe[^>]*>',
r'<object[^>]*>',
r'<embed[^>]*>',
]
def analyze_request(self, request: HttpRequest) -> Dict[str, Any]:
"""Analyze request for security threats"""
analysis = {
'risk_score': 0,
'threats_detected': [],
'recommendations': [],
'block_request': False,
}
# Check IP address
ip_analysis = self._analyze_ip_address(request)
analysis['risk_score'] += ip_analysis['risk_score']
analysis['threats_detected'].extend(ip_analysis['threats'])
# Check user agent
ua_analysis = self._analyze_user_agent(request)
analysis['risk_score'] += ua_analysis['risk_score']
analysis['threats_detected'].extend(ua_analysis['threats'])
# Check request parameters
param_analysis = self._analyze_parameters(request)
analysis['risk_score'] += param_analysis['risk_score']
analysis['threats_detected'].extend(param_analysis['threats'])
# Check request headers
header_analysis = self._analyze_headers(request)
analysis['risk_score'] += header_analysis['risk_score']
analysis['threats_detected'].extend(header_analysis['threats'])
# Check request body
body_analysis = self._analyze_request_body(request)
analysis['risk_score'] += body_analysis['risk_score']
analysis['threats_detected'].extend(body_analysis['threats'])
# Determine if request should be blocked
if analysis['risk_score'] >= 80:
analysis['block_request'] = True
analysis['recommendations'].append('Block request due to high risk score')
elif analysis['risk_score'] >= 50:
analysis['recommendations'].append('Monitor request closely')
return analysis
def _analyze_ip_address(self, request: HttpRequest) -> Dict[str, Any]:
"""Analyze IP address for threats"""
ip_address = self._get_client_ip(request)
analysis = {
'risk_score': 0,
'threats': [],
}
# Check if IP is in high-risk list
if ip_address in self.risk_factors['high_risk_ips']:
analysis['risk_score'] += 40
analysis['threats'].append('High-risk IP address')
# Check if IP is from suspicious country/region
if self._is_suspicious_geolocation(ip_address):
analysis['risk_score'] += 20
analysis['threats'].append('Suspicious geolocation')
# Check for rapid requests from same IP
if self._is_rapid_requests(ip_address):
analysis['risk_score'] += 30
analysis['threats'].append('Rapid requests detected')
return analysis
def _analyze_user_agent(self, request: HttpRequest) -> Dict[str, Any]:
"""Analyze user agent for threats"""
user_agent = request.META.get('HTTP_USER_AGENT', '')
analysis = {
'risk_score': 0,
'threats': [],
}
# Check for suspicious user agents
for suspicious_ua in self.risk_factors['suspicious_user_agents']:
if suspicious_ua.lower() in user_agent.lower():
analysis['risk_score'] += 50
analysis['threats'].append(f'Suspicious user agent: {suspicious_ua}')
# Check for missing user agent
if not user_agent:
analysis['risk_score'] += 10
analysis['threats'].append('Missing user agent')
return analysis
def _analyze_parameters(self, request: HttpRequest) -> Dict[str, Any]:
"""Analyze request parameters for attack patterns"""
analysis = {
'risk_score': 0,
'threats': [],
}
# Check GET parameters
for key, value in request.GET.items():
param_analysis = self._check_attack_patterns(str(value))
analysis['risk_score'] += param_analysis['risk_score']
analysis['threats'].extend(param_analysis['threats'])
# Check POST parameters
if hasattr(request, 'POST'):
for key, value in request.POST.items():
param_analysis = self._check_attack_patterns(str(value))
analysis['risk_score'] += param_analysis['risk_score']
analysis['threats'].extend(param_analysis['threats'])
return analysis
def _analyze_headers(self, request: HttpRequest) -> Dict[str, Any]:
"""Analyze request headers for threats"""
analysis = {
'risk_score': 0,
'threats': [],
}
# Check for suspicious headers
suspicious_headers = [
'X-Forwarded-For',
'X-Real-IP',
'X-Originating-IP',
'X-Remote-IP',
'X-Remote-Addr',
]
for header in suspicious_headers:
if header in request.META:
value = request.META[header]
if self._is_suspicious_ip(value):
analysis['risk_score'] += 20
analysis['threats'].append(f'Suspicious header: {header}')
return analysis
def _analyze_request_body(self, request: HttpRequest) -> Dict[str, Any]:
"""Analyze request body for threats"""
analysis = {
'risk_score': 0,
'threats': [],
}
# Check request body for attack patterns
if hasattr(request, 'body') and request.body:
body_analysis = self._check_attack_patterns(request.body.decode('utf-8', errors='ignore'))
analysis['risk_score'] += body_analysis['risk_score']
analysis['threats'].extend(body_analysis['threats'])
return analysis
def _check_attack_patterns(self, text: str) -> Dict[str, Any]:
"""Check text for attack patterns"""
analysis = {
'risk_score': 0,
'threats': [],
}
import re
for pattern in self.risk_factors['known_attack_patterns']:
if re.search(pattern, text, re.IGNORECASE):
analysis['risk_score'] += 25
analysis['threats'].append(f'Attack pattern detected: {pattern}')
return analysis
def _get_client_ip(self, request: HttpRequest) -> str:
"""Get client IP address"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip
def _is_suspicious_geolocation(self, ip_address: str) -> bool:
"""Check if IP is from suspicious geolocation"""
# In production, use a geolocation service
# For now, return False
return False
def _is_rapid_requests(self, ip_address: str) -> bool:
"""Check if IP is making rapid requests"""
cache_key = f"rapid_requests_{ip_address}"
request_count = cache.get(cache_key, 0)
if request_count > 100: # More than 100 requests in 1 minute
return True
cache.set(cache_key, request_count + 1, 60) # 1 minute
return False
def _is_suspicious_ip(self, ip_address: str) -> bool:
"""Check if IP address is suspicious"""
try:
ip = ipaddress.ip_address(ip_address)
# Check for private IPs in suspicious headers
if ip.is_private:
return True
except ValueError:
return True
return False
class AuditLogger:
"""Enterprise audit logging system"""
def __init__(self):
self.logger = logging.getLogger('security.audit')
self.encryption_key = self._get_encryption_key()
def _get_encryption_key(self) -> bytes:
"""Get encryption key for sensitive data"""
key = os.getenv('AUDIT_ENCRYPTION_KEY')
if not key:
# Generate a key if not set
key = Fernet.generate_key()
logger.warning("AUDIT_ENCRYPTION_KEY not set, using generated key")
return key
def log_security_event(self, event: SecurityEvent) -> None:
"""Log security event"""
try:
# Encrypt sensitive data
encrypted_data = self._encrypt_sensitive_data(event.to_dict())
# Log to file
self.logger.info(f"Security Event: {json.dumps(encrypted_data)}")
# Store in database (if configured)
self._store_in_database(event)
# Send to SIEM (if configured)
self._send_to_siem(event)
except Exception as e:
logger.error(f"Failed to log security event: {str(e)}")
def _encrypt_sensitive_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
"""Encrypt sensitive data in audit log"""
sensitive_fields = ['ip_address', 'user_agent', 'metadata']
for field in sensitive_fields:
if field in data and data[field]:
try:
fernet = Fernet(self.encryption_key)
encrypted_value = fernet.encrypt(str(data[field]).encode())
data[field] = base64.b64encode(encrypted_value).decode()
except Exception as e:
logger.error(f"Failed to encrypt {field}: {str(e)}")
return data
def _store_in_database(self, event: SecurityEvent) -> None:
"""Store security event in database"""
try:
from security.models import SecurityEvent as SecurityEventModel
SecurityEventModel.objects.create(
event_type=event.event_type,
severity=event.severity,
description=event.description,
user=event.user,
ip_address=event.ip_address,
user_agent=event.user_agent,
metadata=event.metadata,
timestamp=event.timestamp,
)
except Exception as e:
logger.error(f"Failed to store security event in database: {str(e)}")
def _send_to_siem(self, event: SecurityEvent) -> None:
"""Send security event to SIEM system"""
try:
siem_url = os.getenv('SIEM_WEBHOOK_URL')
if not siem_url:
return
payload = {
'event_type': event.event_type,
'severity': event.severity,
'description': event.description,
'timestamp': event.timestamp.isoformat(),
'source': 'etb-api',
}
if event.user:
payload['user_id'] = event.user.id
payload['username'] = event.user.username
if event.ip_address:
payload['ip_address'] = event.ip_address
response = requests.post(siem_url, json=payload, timeout=5)
response.raise_for_status()
except Exception as e:
logger.error(f"Failed to send event to SIEM: {str(e)}")
class ComplianceManager:
"""Enterprise compliance management"""
def __init__(self):
self.compliance_frameworks = {
'SOX': self._get_sox_requirements(),
'HIPAA': self._get_hipaa_requirements(),
'GDPR': self._get_gdpr_requirements(),
'PCI_DSS': self._get_pci_dss_requirements(),
'ISO27001': self._get_iso27001_requirements(),
}
def _get_sox_requirements(self) -> Dict[str, Any]:
"""Get SOX compliance requirements"""
return {
'access_controls': True,
'audit_trails': True,
'data_integrity': True,
'change_management': True,
'segregation_of_duties': True,
}
def _get_hipaa_requirements(self) -> Dict[str, Any]:
"""Get HIPAA compliance requirements"""
return {
'data_encryption': True,
'access_controls': True,
'audit_logs': True,
'data_backup': True,
'incident_response': True,
}
def _get_gdpr_requirements(self) -> Dict[str, Any]:
"""Get GDPR compliance requirements"""
return {
'data_protection': True,
'consent_management': True,
'data_portability': True,
'right_to_erasure': True,
'privacy_by_design': True,
}
def _get_pci_dss_requirements(self) -> Dict[str, Any]:
"""Get PCI DSS compliance requirements"""
return {
'network_security': True,
'data_protection': True,
'access_controls': True,
'monitoring': True,
'incident_response': True,
}
def _get_iso27001_requirements(self) -> Dict[str, Any]:
"""Get ISO 27001 compliance requirements"""
return {
'information_security_policy': True,
'risk_management': True,
'access_controls': True,
'incident_management': True,
'business_continuity': True,
}
def check_compliance(self, framework: str) -> Dict[str, Any]:
"""Check compliance with specific framework"""
if framework not in self.compliance_frameworks:
return {
'error': f'Unknown compliance framework: {framework}',
}
requirements = self.compliance_frameworks[framework]
compliance_status = {}
for requirement, required in requirements.items():
compliance_status[requirement] = self._check_requirement(requirement, required)
# Calculate overall compliance score
total_requirements = len(requirements)
met_requirements = sum(1 for status in compliance_status.values() if status['compliant'])
compliance_score = (met_requirements / total_requirements) * 100
return {
'framework': framework,
'compliance_score': compliance_score,
'requirements': compliance_status,
'overall_status': 'compliant' if compliance_score >= 80 else 'non_compliant',
}
def _check_requirement(self, requirement: str, required: bool) -> Dict[str, Any]:
"""Check specific compliance requirement"""
if not required:
return {
'compliant': True,
'message': 'Requirement not applicable',
}
# Check if requirement is implemented
if requirement == 'access_controls':
return self._check_access_controls()
elif requirement == 'audit_trails':
return self._check_audit_trails()
elif requirement == 'data_encryption':
return self._check_data_encryption()
elif requirement == 'incident_response':
return self._check_incident_response()
else:
return {
'compliant': False,
'message': f'Requirement {requirement} not implemented',
}
def _check_access_controls(self) -> Dict[str, Any]:
"""Check access control implementation"""
try:
from django.contrib.auth.models import User
from django.contrib.auth.models import Permission
# Check if users exist
user_count = User.objects.count()
if user_count == 0:
return {
'compliant': False,
'message': 'No users configured',
}
# Check if permissions are configured
permission_count = Permission.objects.count()
if permission_count == 0:
return {
'compliant': False,
'message': 'No permissions configured',
}
return {
'compliant': True,
'message': 'Access controls properly configured',
}
except Exception as e:
return {
'compliant': False,
'message': f'Error checking access controls: {str(e)}',
}
def _check_audit_trails(self) -> Dict[str, Any]:
"""Check audit trail implementation"""
try:
from security.models import SecurityEvent
# Check if audit logs exist
event_count = SecurityEvent.objects.count()
if event_count == 0:
return {
'compliant': False,
'message': 'No audit logs found',
}
# Check if recent audit logs exist
recent_events = SecurityEvent.objects.filter(
timestamp__gte=timezone.now() - timedelta(days=1)
).count()
if recent_events == 0:
return {
'compliant': False,
'message': 'No recent audit logs found',
}
return {
'compliant': True,
'message': 'Audit trails properly configured',
}
except Exception as e:
return {
'compliant': False,
'message': f'Error checking audit trails: {str(e)}',
}
def _check_data_encryption(self) -> Dict[str, Any]:
"""Check data encryption implementation"""
try:
# Check if encryption is enabled in settings
if not getattr(settings, 'SECURE_SSL_REDIRECT', False):
return {
'compliant': False,
'message': 'SSL/TLS encryption not enabled',
}
# Check if database encryption is configured
db_engine = settings.DATABASES['default']['ENGINE']
if 'postgresql' in db_engine:
# Check if SSL is required
db_options = settings.DATABASES['default'].get('OPTIONS', {})
if not db_options.get('sslmode'):
return {
'compliant': False,
'message': 'Database SSL not configured',
}
return {
'compliant': True,
'message': 'Data encryption properly configured',
}
except Exception as e:
return {
'compliant': False,
'message': f'Error checking data encryption: {str(e)}',
}
def _check_incident_response(self) -> Dict[str, Any]:
"""Check incident response implementation"""
try:
from incident_intelligence.models import Incident
# Check if incident management is configured
incident_count = Incident.objects.count()
if incident_count == 0:
return {
'compliant': False,
'message': 'No incident management configured',
}
return {
'compliant': True,
'message': 'Incident response properly configured',
}
except Exception as e:
return {
'compliant': False,
'message': f'Error checking incident response: {str(e)}',
}
# Global instances
threat_detector = ThreatDetectionService()
audit_logger = AuditLogger()
compliance_manager = ComplianceManager()
# Signal handlers for security events
@receiver(user_logged_in)
def log_user_login(sender, request, user, **kwargs):
"""Log successful user login"""
event = SecurityEvent(
event_type='user_login',
severity='info',
description=f'User {user.username} logged in successfully',
user=user,
ip_address=threat_detector._get_client_ip(request),
user_agent=request.META.get('HTTP_USER_AGENT'),
metadata={'login_method': 'password'}
)
audit_logger.log_security_event(event)
@receiver(user_logged_out)
def log_user_logout(sender, request, user, **kwargs):
"""Log user logout"""
event = SecurityEvent(
event_type='user_logout',
severity='info',
description=f'User {user.username} logged out',
user=user,
ip_address=threat_detector._get_client_ip(request),
user_agent=request.META.get('HTTP_USER_AGENT'),
)
audit_logger.log_security_event(event)
@receiver(user_login_failed)
def log_login_failure(sender, credentials, request, **kwargs):
"""Log failed login attempt"""
event = SecurityEvent(
event_type='login_failure',
severity='warning',
description=f'Failed login attempt for user: {credentials.get("username", "unknown")}',
ip_address=threat_detector._get_client_ip(request),
user_agent=request.META.get('HTTP_USER_AGENT'),
metadata={'username': credentials.get('username', 'unknown')}
)
audit_logger.log_security_event(event)
# API Views for security management
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def security_dashboard(request):
"""Get security dashboard data"""
try:
# Get recent security events
from security.models import SecurityEvent
recent_events = SecurityEvent.objects.filter(
timestamp__gte=timezone.now() - timedelta(days=7)
).order_by('-timestamp')[:10]
# Get threat analysis
threat_analysis = threat_detector.analyze_request(request)
# Get compliance status
compliance_status = {}
for framework in compliance_manager.compliance_frameworks.keys():
compliance_status[framework] = compliance_manager.check_compliance(framework)
return Response({
'recent_events': [
{
'event_type': event.event_type,
'severity': event.severity,
'description': event.description,
'timestamp': event.timestamp.isoformat(),
'user': event.user.username if event.user else None,
}
for event in recent_events
],
'threat_analysis': threat_analysis,
'compliance_status': compliance_status,
})
except Exception as e:
logger.error(f"Security dashboard error: {str(e)}")
return Response(
{'error': 'Failed to load security dashboard'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def compliance_report(request):
"""Get compliance report"""
try:
framework = request.GET.get('framework')
if framework:
report = compliance_manager.check_compliance(framework)
else:
report = {}
for framework in compliance_manager.compliance_frameworks.keys():
report[framework] = compliance_manager.check_compliance(framework)
return Response(report)
except Exception as e:
logger.error(f"Compliance report error: {str(e)}")
return Response(
{'error': 'Failed to generate compliance report'},
status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
class SecurityMiddleware:
"""Security middleware for threat detection and logging"""
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
# Analyze request for threats
threat_analysis = threat_detector.analyze_request(request)
# Log high-risk requests
if threat_analysis['risk_score'] >= 50:
event = SecurityEvent(
event_type='suspicious_request',
severity='warning' if threat_analysis['risk_score'] < 80 else 'critical',
description=f'Suspicious request detected: {threat_analysis["threats_detected"]}',
ip_address=threat_detector._get_client_ip(request),
user_agent=request.META.get('HTTP_USER_AGENT'),
metadata={
'risk_score': threat_analysis['risk_score'],
'threats': threat_analysis['threats_detected'],
'path': request.path,
'method': request.method,
}
)
audit_logger.log_security_event(event)
# Block high-risk requests
if threat_analysis['block_request']:
return JsonResponse(
{
'error': 'Request blocked due to security concerns',
'threats_detected': threat_analysis['threats_detected'],
},
status=status.HTTP_403_FORBIDDEN
)
response = self.get_response(request)
# Add security headers
response['X-Content-Type-Options'] = 'nosniff'
response['X-Frame-Options'] = 'DENY'
response['X-XSS-Protection'] = '1; mode=block'
response['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
response['Content-Security-Policy'] = "default-src 'self'"
return response

View File

@@ -0,0 +1 @@
# Management commands for security app

View File

@@ -0,0 +1 @@
# Management commands

View File

@@ -0,0 +1,201 @@
"""
Management command to set up initial security data
"""
from django.core.management.base import BaseCommand
from django.contrib.auth.models import Permission, ContentType
from django.contrib.contenttypes.models import ContentType
from security.models import DataClassification, Role, User
class Command(BaseCommand):
help = 'Set up initial security data (classifications, roles, permissions)'
def handle(self, *args, **options):
self.stdout.write('Setting up security data...')
# Create data classifications
self.create_data_classifications()
# Create basic roles
self.create_basic_roles()
# Create superuser if none exists
self.create_superuser()
self.stdout.write(
self.style.SUCCESS('Security data setup completed successfully!')
)
def create_data_classifications(self):
"""Create data classification levels"""
classifications = [
{
'name': 'PUBLIC',
'level': 1,
'description': 'Public information that can be freely shared',
'color_code': '#28a745',
'requires_clearance': False
},
{
'name': 'INTERNAL',
'level': 2,
'description': 'Internal company information',
'color_code': '#17a2b8',
'requires_clearance': False
},
{
'name': 'CONFIDENTIAL',
'level': 3,
'description': 'Confidential information requiring protection',
'color_code': '#ffc107',
'requires_clearance': True
},
{
'name': 'RESTRICTED',
'level': 4,
'description': 'Restricted information with limited access',
'color_code': '#fd7e14',
'requires_clearance': True
},
{
'name': 'TOP_SECRET',
'level': 5,
'description': 'Top secret information with highest protection',
'color_code': '#dc3545',
'requires_clearance': True
}
]
for classification_data in classifications:
classification, created = DataClassification.objects.get_or_create(
name=classification_data['name'],
defaults=classification_data
)
if created:
self.stdout.write(f'Created classification: {classification.name}')
else:
self.stdout.write(f'Classification already exists: {classification.name}')
def create_basic_roles(self):
"""Create basic roles with permissions"""
# Get content types for permissions
user_content_type = ContentType.objects.get_for_model(User)
classification_content_type = ContentType.objects.get_for_model(DataClassification)
role_content_type = ContentType.objects.get_for_model(Role)
# Create permissions if they don't exist
permissions_data = [
('view_user', 'Can view user', user_content_type),
('add_user', 'Can add user', user_content_type),
('change_user', 'Can change user', user_content_type),
('delete_user', 'Can delete user', user_content_type),
('view_classification', 'Can view data classification', classification_content_type),
('add_classification', 'Can add data classification', classification_content_type),
('change_classification', 'Can change data classification', classification_content_type),
('delete_classification', 'Can delete data classification', classification_content_type),
('view_role', 'Can view role', role_content_type),
('add_role', 'Can add role', role_content_type),
('change_role', 'Can change role', role_content_type),
('delete_role', 'Can delete role', role_content_type),
]
permissions = {}
for codename, name, content_type in permissions_data:
permission, created = Permission.objects.get_or_create(
codename=codename,
content_type=content_type,
defaults={'name': name}
)
permissions[codename] = permission
if created:
self.stdout.write(f'Created permission: {permission.name}')
# Create roles
roles_data = [
{
'name': 'Incident Manager',
'description': 'Can manage incidents and assign to team members',
'permissions': ['view_user', 'view_classification', 'view_role'],
'classifications': ['PUBLIC', 'INTERNAL', 'CONFIDENTIAL']
},
{
'name': 'Security Admin',
'description': 'Can manage security settings and user access',
'permissions': [
'view_user', 'add_user', 'change_user', 'delete_user',
'view_classification', 'add_classification', 'change_classification', 'delete_classification',
'view_role', 'add_role', 'change_role', 'delete_role'
],
'classifications': ['PUBLIC', 'INTERNAL', 'CONFIDENTIAL', 'RESTRICTED']
},
{
'name': 'Analyst',
'description': 'Can view and analyze incidents',
'permissions': ['view_user', 'view_classification', 'view_role'],
'classifications': ['PUBLIC', 'INTERNAL']
},
{
'name': 'Viewer',
'description': 'Can view public and internal incidents only',
'permissions': ['view_classification'],
'classifications': ['PUBLIC', 'INTERNAL']
}
]
for role_data in roles_data:
role, created = Role.objects.get_or_create(
name=role_data['name'],
defaults={
'description': role_data['description'],
'is_active': True
}
)
if created:
# Add permissions
role_permissions = [permissions[p] for p in role_data['permissions'] if p in permissions]
role.permissions.set(role_permissions)
# Add data classifications
classifications = DataClassification.objects.filter(
name__in=role_data['classifications']
)
role.data_classification_access.set(classifications)
self.stdout.write(f'Created role: {role.name}')
else:
self.stdout.write(f'Role already exists: {role.name}')
def create_superuser(self):
"""Create superuser if none exists"""
if not User.objects.filter(is_superuser=True).exists():
self.stdout.write('Creating superuser...')
# Get the highest clearance level
top_secret = DataClassification.objects.filter(name='TOP_SECRET').first()
superuser = User.objects.create_superuser(
username='admin',
email='admin@etb-incident-management.com',
password='admin123',
first_name='System',
last_name='Administrator',
clearance_level=top_secret,
employee_id='ADMIN001'
)
self.stdout.write(
self.style.WARNING(
f'Created superuser: {superuser.username} (password: admin123)'
)
)
self.stdout.write(
self.style.WARNING(
'Please change the default password immediately!'
)
)
else:
self.stdout.write('Superuser already exists')

View File

@@ -0,0 +1 @@
# Multi-Factor Authentication module

Binary file not shown.

View File

@@ -0,0 +1,235 @@
"""
Time-based One-Time Password (TOTP) implementation for MFA
"""
import base64
import hashlib
import hmac
import time
import qrcode
import io
from typing import Optional, Tuple
from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from ..models import MFADevice, AuditLog
User = get_user_model()
class TOTPGenerator:
"""TOTP generator and validator"""
def __init__(self, secret: str, time_step: int = 30, digits: int = 6):
self.secret = secret
self.time_step = time_step
self.digits = digits
def generate_token(self, timestamp: Optional[int] = None) -> str:
"""Generate TOTP token for given timestamp"""
if timestamp is None:
timestamp = int(time.time())
time_counter = timestamp // self.time_step
# Convert secret to bytes
secret_bytes = base64.b32decode(self.secret.upper() + '=' * (8 - len(self.secret) % 8))
# Generate HMAC-SHA1
time_bytes = time_counter.to_bytes(8, byteorder='big')
hmac_digest = hmac.new(secret_bytes, time_bytes, hashlib.sha1).digest()
# Extract dynamic binary code
offset = hmac_digest[-1] & 0x0f
code = int.from_bytes(hmac_digest[offset:offset+4], byteorder='big') & 0x7fffffff
# Convert to string with leading zeros
return str(code % (10 ** self.digits)).zfill(self.digits)
def verify_token(self, token: str, window: int = 1) -> bool:
"""Verify TOTP token with time window tolerance"""
current_time = int(time.time())
for i in range(-window, window + 1):
expected_token = self.generate_token(current_time + i * self.time_step)
if token == expected_token:
return True
return False
class MFAProvider:
"""MFA provider for managing TOTP devices"""
@staticmethod
def generate_secret() -> str:
"""Generate a new TOTP secret"""
import secrets
return base64.b32encode(secrets.token_bytes(20)).decode('ascii')
@staticmethod
def create_totp_device(user: User, device_name: str) -> Tuple[MFADevice, str]:
"""Create a new TOTP device for user"""
secret = MFAProvider.generate_secret()
device = MFADevice.objects.create(
user=user,
device_type='TOTP',
name=device_name,
secret_key=secret,
is_active=True
)
# Generate QR code data
qr_data = MFAProvider.generate_qr_code_data(user, secret)
return device, qr_data
@staticmethod
def generate_qr_code_data(user: User, secret: str) -> str:
"""Generate QR code data for TOTP setup"""
issuer = getattr(settings, 'MFA_ISSUER_NAME', 'ETB Incident Management')
account_name = f"{user.username}@{issuer}"
# TOTP URI format
uri = f"otpauth://totp/{account_name}?secret={secret}&issuer={issuer}"
return uri
@staticmethod
def generate_qr_code_image(qr_data: str) -> bytes:
"""Generate QR code image as bytes"""
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(qr_data)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
# Convert to bytes
img_buffer = io.BytesIO()
img.save(img_buffer, format='PNG')
img_buffer.seek(0)
return img_buffer.getvalue()
@staticmethod
def verify_totp_token(user: User, token: str, device_id: Optional[str] = None) -> bool:
"""Verify TOTP token for user"""
try:
# Get user's TOTP devices
devices = MFADevice.objects.filter(
user=user,
device_type='TOTP',
is_active=True
)
if device_id:
devices = devices.filter(id=device_id)
for device in devices:
totp = TOTPGenerator(device.secret_key)
if totp.verify_token(token):
# Update last used timestamp
device.last_used = timezone.now()
device.save(update_fields=['last_used'])
# Log successful MFA verification
AuditLog.objects.create(
user=user,
action_type='MFA_VERIFIED',
details={
'device_id': str(device.id),
'device_name': device.name,
'device_type': device.device_type
}
)
return True
# Log failed MFA attempt
AuditLog.objects.create(
user=user,
action_type='MFA_FAILED',
severity='MEDIUM',
details={
'device_id': device_id,
'token_provided': bool(token)
}
)
return False
except Exception as e:
# Log MFA error
AuditLog.objects.create(
user=user,
action_type='MFA_ERROR',
severity='HIGH',
details={'error': str(e)}
)
return False
@staticmethod
def enable_mfa_for_user(user: User) -> bool:
"""Enable MFA for user if they have at least one active device"""
active_devices = MFADevice.objects.filter(
user=user,
is_active=True
).count()
if active_devices > 0:
user.mfa_enabled = True
user.save(update_fields=['mfa_enabled'])
# Log MFA enablement
AuditLog.objects.create(
user=user,
action_type='MFA_ENABLED',
details={'device_count': active_devices}
)
return True
return False
@staticmethod
def disable_mfa_for_user(user: User) -> bool:
"""Disable MFA for user"""
user.mfa_enabled = False
user.save(update_fields=['mfa_enabled'])
# Deactivate all MFA devices
MFADevice.objects.filter(user=user).update(is_active=False)
# Log MFA disablement
AuditLog.objects.create(
user=user,
action_type='MFA_DISABLED',
details={'reason': 'user_requested'}
)
return True
@staticmethod
def get_user_mfa_devices(user: User) -> list:
"""Get all MFA devices for user"""
devices = MFADevice.objects.filter(user=user).order_by('-is_primary', 'name')
return [
{
'id': str(device.id),
'name': device.name,
'device_type': device.device_type,
'is_active': device.is_active,
'is_primary': device.is_primary,
'last_used': device.last_used,
'created_at': device.created_at
}
for device in devices
]

View File

@@ -0,0 +1,289 @@
"""
Zero Trust Middleware
Automatically applies Zero Trust principles to all requests
"""
import logging
from django.http import JsonResponse
from django.utils.deprecation import MiddlewareMixin
from django.contrib.auth import get_user_model
from django.utils import timezone
from ..services.zero_trust import zero_trust_service
logger = logging.getLogger(__name__)
User = get_user_model()
class ZeroTrustMiddleware(MiddlewareMixin):
"""
Middleware that applies Zero Trust principles to all requests
"""
def __init__(self, get_response):
self.get_response = get_response
super().__init__(get_response)
def process_request(self, request):
"""Process incoming request for Zero Trust assessment"""
# Skip Zero Trust for certain paths
skip_paths = [
'/admin/',
'/static/',
'/media/',
'/api/auth/login/',
'/api/auth/logout/',
'/health/',
'/metrics/',
]
if any(request.path.startswith(path) for path in skip_paths):
return None
# Only apply to authenticated users
if not request.user.is_authenticated:
return None
# Skip for API endpoints that don't require Zero Trust
if request.path.startswith('/api/') and self._is_public_endpoint(request.path):
return None
try:
# Collect request context
request_context = self._collect_request_context(request)
# Perform Zero Trust assessment
assessment_result = zero_trust_service.assess_access_request(
request.user,
request_context
)
# Handle assessment result
if not assessment_result.get('access_granted', False):
return self._handle_access_denied(request, assessment_result)
# Store assessment result in request for use in views
request.zero_trust_assessment = assessment_result
# Update behavior profile
zero_trust_service.update_behavior_profile(request.user, request_context)
except Exception as e:
logger.error(f"Zero Trust middleware error: {e}")
# In case of error, allow access but log the issue
request.zero_trust_assessment = {
'access_granted': True,
'reason': 'Assessment error - defaulting to allow',
'risk_level': 'UNKNOWN',
'error': str(e)
}
return None
def _collect_request_context(self, request) -> dict:
"""Collect context data from request"""
# Get client IP
ip_address = self._get_client_ip(request)
# Get device ID from headers or session
device_id = (
request.headers.get('X-Device-ID') or
request.session.get('device_id') or
self._generate_device_fingerprint(request)
)
return {
'ip_address': ip_address,
'user_agent': request.META.get('HTTP_USER_AGENT', ''),
'device_id': device_id,
'timestamp': timezone.now(),
'request_method': request.method,
'request_path': request.path,
'referer': request.META.get('HTTP_REFERER', ''),
'accept_language': request.META.get('HTTP_ACCEPT_LANGUAGE', ''),
'session_id': request.session.session_key,
}
def _get_client_ip(self, request) -> str:
"""Get client IP address from request"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0].strip()
else:
ip = request.META.get('REMOTE_ADDR')
return ip
def _generate_device_fingerprint(self, request) -> str:
"""Generate a device fingerprint for tracking"""
import hashlib
# Create fingerprint from user agent and IP
fingerprint_data = f"{request.META.get('HTTP_USER_AGENT', '')}{self._get_client_ip(request)}"
return hashlib.sha256(fingerprint_data.encode()).hexdigest()[:16]
def _is_public_endpoint(self, path: str) -> bool:
"""Check if endpoint is public and doesn't require Zero Trust"""
public_endpoints = [
'/api/auth/',
'/api/health/',
'/api/status/',
'/api/docs/',
]
return any(path.startswith(endpoint) for endpoint in public_endpoints)
def _handle_access_denied(self, request, assessment_result: dict) -> JsonResponse:
"""Handle access denied based on Zero Trust assessment"""
required_actions = assessment_result.get('required_actions', [])
risk_level = assessment_result.get('risk_level', 'UNKNOWN')
reason = assessment_result.get('reason', 'Access denied')
# Determine response based on required actions
if 'MANUAL_REVIEW' in required_actions:
return JsonResponse({
'error': 'Access requires manual review',
'reason': reason,
'risk_level': risk_level,
'required_actions': required_actions,
'support_contact': 'security@company.com'
}, status=423) # 423 Locked
elif 'STEP_UP_AUTH' in required_actions:
return JsonResponse({
'error': 'Additional authentication required',
'reason': reason,
'risk_level': risk_level,
'required_actions': required_actions,
'auth_url': '/api/auth/step-up/'
}, status=401) # 401 Unauthorized
elif 'DEVICE_REGISTRATION' in required_actions:
return JsonResponse({
'error': 'Device registration required',
'reason': reason,
'risk_level': risk_level,
'required_actions': required_actions,
'registration_url': '/api/security/register-device/'
}, status=403) # 403 Forbidden
elif 'ADDITIONAL_MFA' in required_actions:
return JsonResponse({
'error': 'Additional MFA required',
'reason': reason,
'risk_level': risk_level,
'required_actions': required_actions,
'mfa_url': '/api/auth/mfa/'
}, status=401) # 401 Unauthorized
else:
# Generic access denied
return JsonResponse({
'error': 'Access denied',
'reason': reason,
'risk_level': risk_level,
'required_actions': required_actions
}, status=403) # 403 Forbidden
class DeviceRegistrationMiddleware(MiddlewareMixin):
"""
Middleware to handle device registration for Zero Trust
"""
def process_request(self, request):
"""Process device registration requests"""
if request.path == '/api/security/register-device/' and request.method == 'POST':
return self._handle_device_registration(request)
return None
def _handle_device_registration(self, request):
"""Handle device registration"""
try:
if not request.user.is_authenticated:
return JsonResponse({'error': 'Authentication required'}, status=401)
device_data = request.json if hasattr(request, 'json') else {}
# Register device
device_posture = zero_trust_service.register_device(request.user, device_data)
return JsonResponse({
'success': True,
'device_id': device_posture.device_id,
'trust_level': device_posture.trust_level,
'risk_score': device_posture.risk_score,
'is_compliant': device_posture.is_compliant,
'message': 'Device registered successfully'
})
except Exception as e:
logger.error(f"Device registration failed: {e}")
return JsonResponse({
'error': 'Device registration failed',
'details': str(e)
}, status=400)
class RiskBasedRateLimitMiddleware(MiddlewareMixin):
"""
Middleware for risk-based rate limiting
"""
def __init__(self, get_response):
self.get_response = get_response
super().__init__(get_response)
self.request_counts = {} # In production, use Redis or database
def process_request(self, request):
"""Apply risk-based rate limiting"""
if not request.user.is_authenticated:
return None
# Get user's risk level from Zero Trust assessment
assessment = getattr(request, 'zero_trust_assessment', None)
if not assessment:
return None
risk_level = assessment.get('risk_level', 'LOW')
user_id = str(request.user.id)
current_time = timezone.now()
# Define rate limits based on risk level
rate_limits = {
'LOW': {'requests': 1000, 'window': 3600}, # 1000 requests per hour
'MEDIUM': {'requests': 500, 'window': 3600}, # 500 requests per hour
'HIGH': {'requests': 100, 'window': 3600}, # 100 requests per hour
'CRITICAL': {'requests': 10, 'window': 3600}, # 10 requests per hour
}
limit = rate_limits.get(risk_level, rate_limits['LOW'])
# Check rate limit
if self._is_rate_limited(user_id, limit, current_time):
return JsonResponse({
'error': 'Rate limit exceeded',
'reason': f'Too many requests for risk level {risk_level}',
'retry_after': limit['window']
}, status=429) # 429 Too Many Requests
return None
def _is_rate_limited(self, user_id: str, limit: dict, current_time) -> bool:
"""Check if user has exceeded rate limit"""
# Simplified rate limiting (in production, use proper rate limiting library)
window_start = current_time.timestamp() - limit['window']
# Clean old entries
if user_id in self.request_counts:
self.request_counts[user_id] = [
timestamp for timestamp in self.request_counts[user_id]
if timestamp > window_start
]
else:
self.request_counts[user_id] = []
# Check if limit exceeded
if len(self.request_counts[user_id]) >= limit['requests']:
return True
# Add current request
self.request_counts[user_id].append(current_time.timestamp())
return False

View File

@@ -0,0 +1,168 @@
# Generated by Django 5.2.6 on 2025-09-18 14:49
import django.contrib.auth.models
import django.contrib.auth.validators
import django.db.models.deletion
import django.utils.timezone
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0012_alter_user_first_name_max_length'),
]
operations = [
migrations.CreateModel(
name='AccessPolicy',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, unique=True)),
('description', models.TextField()),
('policy_type', models.CharField(choices=[('ALLOW', 'Allow'), ('DENY', 'Deny')], max_length=10)),
('conditions', models.JSONField(help_text='JSON conditions for policy evaluation')),
('resource_type', models.CharField(blank=True, max_length=100)),
('actions', models.JSONField(default=list, help_text='List of actions this policy applies to')),
('priority', models.IntegerField(default=100, help_text='Lower numbers have higher priority')),
('is_active', models.BooleanField(default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'verbose_name_plural': 'Access Policies',
'ordering': ['priority', 'name'],
},
),
migrations.CreateModel(
name='DataClassification',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(choices=[('PUBLIC', 'Public'), ('INTERNAL', 'Internal'), ('CONFIDENTIAL', 'Confidential'), ('RESTRICTED', 'Restricted'), ('TOP_SECRET', 'Top Secret')], max_length=20, unique=True)),
('level', models.IntegerField(help_text='Numeric level for comparison (higher = more sensitive)', unique=True)),
('description', models.TextField()),
('color_code', models.CharField(default='#000000', help_text='Hex color code for UI', max_length=7)),
('requires_clearance', models.BooleanField(default=False)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'verbose_name': 'Data Classification',
'verbose_name_plural': 'Data Classifications',
'ordering': ['level'],
},
),
migrations.CreateModel(
name='SSOProvider',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, unique=True)),
('provider_type', models.CharField(choices=[('SAML', 'SAML'), ('OAUTH2', 'OAuth 2.0'), ('OIDC', 'OpenID Connect'), ('LDAP', 'LDAP')], max_length=20)),
('is_active', models.BooleanField(default=True)),
('configuration', models.JSONField(default=dict)),
('attribute_mapping', models.JSONField(default=dict, help_text='Mapping of SSO attributes to user fields')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'ordering': ['name'],
},
),
migrations.CreateModel(
name='Role',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=100, unique=True)),
('description', models.TextField()),
('is_active', models.BooleanField(default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('data_classification_access', models.ManyToManyField(blank=True, help_text='Maximum data classification level this role can access', to='security.dataclassification')),
('permissions', models.ManyToManyField(blank=True, to='auth.permission')),
],
options={
'ordering': ['name'],
},
),
migrations.CreateModel(
name='User',
fields=[
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=150, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('employee_id', models.CharField(blank=True, max_length=50, null=True, unique=True)),
('department', models.CharField(blank=True, max_length=100)),
('attributes', models.JSONField(blank=True, default=dict)),
('mfa_enabled', models.BooleanField(default=False)),
('mfa_secret', models.CharField(blank=True, max_length=32)),
('last_login_ip', models.GenericIPAddressField(blank=True, null=True)),
('failed_login_attempts', models.IntegerField(default=0)),
('account_locked_until', models.DateTimeField(blank=True, null=True)),
('sso_provider', models.CharField(blank=True, max_length=50, null=True)),
('sso_identifier', models.CharField(blank=True, max_length=255, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('groups', models.ManyToManyField(blank=True, help_text='The groups this user belongs to. A user will get all permissions granted to each of their groups.', related_name='user_set', related_query_name='user', to='auth.group', verbose_name='groups')),
('user_permissions', models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.permission', verbose_name='user permissions')),
('clearance_level', models.ForeignKey(blank=True, help_text="User's security clearance level", null=True, on_delete=django.db.models.deletion.SET_NULL, to='security.dataclassification')),
('roles', models.ManyToManyField(blank=True, to='security.role')),
],
options={
'ordering': ['username'],
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
migrations.CreateModel(
name='AuditLog',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('timestamp', models.DateTimeField(auto_now_add=True, db_index=True)),
('action_type', models.CharField(choices=[('LOGIN', 'User Login'), ('LOGOUT', 'User Logout'), ('LOGIN_FAILED', 'Failed Login Attempt'), ('PASSWORD_CHANGE', 'Password Changed'), ('MFA_ENABLED', 'MFA Enabled'), ('MFA_DISABLED', 'MFA Disabled'), ('ROLE_ASSIGNED', 'Role Assigned'), ('ROLE_REMOVED', 'Role Removed'), ('DATA_ACCESS', 'Data Accessed'), ('DATA_MODIFIED', 'Data Modified'), ('DATA_DELETED', 'Data Deleted'), ('ACCOUNT_LOCKED', 'Account Locked'), ('ACCOUNT_UNLOCKED', 'Account Unlocked'), ('SSO_LOGIN', 'SSO Login')], max_length=50)),
('resource_type', models.CharField(blank=True, max_length=100)),
('resource_id', models.CharField(blank=True, max_length=255)),
('ip_address', models.GenericIPAddressField(blank=True, null=True)),
('user_agent', models.TextField(blank=True)),
('details', models.JSONField(default=dict)),
('severity', models.CharField(choices=[('LOW', 'Low'), ('MEDIUM', 'Medium'), ('HIGH', 'High'), ('CRITICAL', 'Critical')], default='LOW', max_length=20)),
('hash_value', models.CharField(help_text='SHA-256 hash for integrity verification', max_length=64, unique=True)),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-timestamp'],
'indexes': [models.Index(fields=['timestamp', 'action_type'], name='security_au_timesta_0a6bfb_idx'), models.Index(fields=['user', 'timestamp'], name='security_au_user_id_88bbbb_idx'), models.Index(fields=['severity', 'timestamp'], name='security_au_severit_eb201b_idx')],
},
),
migrations.CreateModel(
name='MFADevice',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('device_type', models.CharField(choices=[('TOTP', 'Time-based One-Time Password'), ('HOTP', 'HMAC-based One-Time Password'), ('SMS', 'SMS'), ('EMAIL', 'Email'), ('HARDWARE', 'Hardware Token')], max_length=20)),
('name', models.CharField(help_text='User-friendly name for the device', max_length=100)),
('secret_key', models.CharField(help_text='Encrypted secret key', max_length=255)),
('is_active', models.BooleanField(default=True)),
('is_primary', models.BooleanField(default=False)),
('last_used', models.DateTimeField(blank=True, null=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='mfa_devices', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-is_primary', 'name'],
'unique_together': {('user', 'name')},
},
),
]

View File

@@ -0,0 +1,28 @@
# Generated by Django 5.2.6 on 2025-09-18 15:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('security', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='user',
name='emergency_contact',
field=models.CharField(blank=True, help_text='Emergency contact information', max_length=100, null=True),
),
migrations.AddField(
model_name='user',
name='oncall_preferences',
field=models.JSONField(blank=True, default=dict, help_text='On-call notification preferences'),
),
migrations.AddField(
model_name='user',
name='phone_number',
field=models.CharField(blank=True, help_text='Phone number for on-call notifications', max_length=20, null=True),
),
]

View File

@@ -0,0 +1,264 @@
# Generated by Django 5.2.6 on 2025-09-18 17:49
import django.core.validators
import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('security', '0002_user_emergency_contact_user_oncall_preferences_and_more'),
]
operations = [
migrations.CreateModel(
name='AdaptiveAuthentication',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('name', models.CharField(max_length=100, unique=True)),
('description', models.TextField()),
('low_risk_threshold', models.IntegerField(default=25, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])),
('medium_risk_threshold', models.IntegerField(default=50, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])),
('high_risk_threshold', models.IntegerField(default=75, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])),
('low_risk_auth_methods', models.JSONField(default=list, help_text='Auth methods for low risk')),
('medium_risk_auth_methods', models.JSONField(default=list, help_text='Auth methods for medium risk')),
('high_risk_auth_methods', models.JSONField(default=list, help_text='Auth methods for high risk')),
('critical_risk_auth_methods', models.JSONField(default=list, help_text='Auth methods for critical risk')),
('device_trust_multiplier', models.FloatField(default=1.0, help_text='Multiplier for device trust')),
('location_trust_multiplier', models.FloatField(default=1.0, help_text='Multiplier for location trust')),
('time_trust_multiplier', models.FloatField(default=1.0, help_text='Multiplier for time trust')),
('enable_behavioral_analysis', models.BooleanField(default=True)),
('behavior_learning_period', models.IntegerField(default=30, help_text='Days to learn user behavior')),
('anomaly_threshold', models.FloatField(default=0.7, help_text='Threshold for behavioral anomalies')),
('ml_enabled', models.BooleanField(default=False)),
('ml_model_path', models.CharField(blank=True, help_text='Path to ML model file', max_length=500)),
('ml_confidence_threshold', models.FloatField(default=0.8, help_text='ML confidence threshold')),
('fallback_auth_methods', models.JSONField(default=list, help_text='Fallback auth methods')),
('max_auth_attempts', models.IntegerField(default=3)),
('lockout_duration', models.IntegerField(default=15, help_text='Lockout duration in minutes')),
('is_active', models.BooleanField(default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
],
options={
'ordering': ['name'],
},
),
migrations.AddField(
model_name='accesspolicy',
name='adaptive_auth_enabled',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='accesspolicy',
name='auth_factors_required',
field=models.JSONField(default=list, help_text='Required authentication factors'),
),
migrations.AddField(
model_name='accesspolicy',
name='max_risk_score',
field=models.IntegerField(default=100, help_text='Maximum allowed risk score', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)]),
),
migrations.AddField(
model_name='accesspolicy',
name='min_device_trust_level',
field=models.CharField(choices=[('HIGH', 'High Trust'), ('MEDIUM', 'Medium Trust'), ('LOW', 'Low Trust')], default='LOW', max_length=20),
),
migrations.AddField(
model_name='accesspolicy',
name='requires_compliant_device',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='accesspolicy',
name='requires_device_trust',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='accesspolicy',
name='requires_geolocation_check',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='accesspolicy',
name='time_restrictions',
field=models.JSONField(default=dict, help_text='Time-based access restrictions'),
),
migrations.AlterField(
model_name='accesspolicy',
name='policy_type',
field=models.CharField(choices=[('ALLOW', 'Allow'), ('DENY', 'Deny'), ('REQUIRE_MFA', 'Require Additional MFA'), ('STEP_UP_AUTH', 'Step-up Authentication'), ('RISK_BASED', 'Risk-based Decision')], max_length=20),
),
migrations.CreateModel(
name='GeolocationRule',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('name', models.CharField(max_length=100, unique=True)),
('description', models.TextField()),
('rule_type', models.CharField(choices=[('ALLOW', 'Allow'), ('DENY', 'Deny'), ('REQUIRE_MFA', 'Require Additional MFA'), ('RESTRICT', 'Restrict Access')], max_length=20)),
('allowed_countries', models.JSONField(default=list, help_text='List of allowed country codes')),
('blocked_countries', models.JSONField(default=list, help_text='List of blocked country codes')),
('allowed_regions', models.JSONField(default=list, help_text='List of allowed regions/states')),
('blocked_regions', models.JSONField(default=list, help_text='List of blocked regions/states')),
('allowed_cities', models.JSONField(default=list, help_text='List of allowed cities')),
('blocked_cities', models.JSONField(default=list, help_text='List of blocked cities')),
('allowed_ip_ranges', models.JSONField(default=list, help_text='List of allowed IP ranges (CIDR)')),
('blocked_ip_ranges', models.JSONField(default=list, help_text='List of blocked IP ranges (CIDR)')),
('allowed_time_zones', models.JSONField(default=list, help_text='List of allowed time zones')),
('working_hours_only', models.BooleanField(default=False)),
('working_hours_start', models.TimeField(blank=True, null=True)),
('working_hours_end', models.TimeField(blank=True, null=True)),
('working_days', models.JSONField(default=list, help_text='List of working days (0-6, Monday=0)')),
('max_distance_from_office', models.FloatField(blank=True, help_text='Max distance from office in km', null=True)),
('office_latitude', models.FloatField(blank=True, null=True)),
('office_longitude', models.FloatField(blank=True, null=True)),
('notification_message', models.TextField(blank=True, help_text='Message to show when rule triggers')),
('log_violation', models.BooleanField(default=True)),
('require_manager_approval', models.BooleanField(default=False)),
('is_active', models.BooleanField(default=True)),
('priority', models.IntegerField(default=100, help_text='Lower numbers have higher priority')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['priority', 'name'],
},
),
migrations.AddField(
model_name='accesspolicy',
name='geolocation_rule',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='security.geolocationrule'),
),
migrations.CreateModel(
name='RiskAssessment',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('assessment_type', models.CharField(default='LOGIN', help_text='Type of assessment (LOGIN, ACCESS, TRANSACTION)', max_length=50)),
('resource_type', models.CharField(blank=True, help_text='Type of resource being accessed', max_length=100)),
('resource_id', models.CharField(blank=True, help_text='ID of resource being accessed', max_length=255)),
('device_risk_score', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])),
('location_risk_score', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])),
('behavior_risk_score', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])),
('network_risk_score', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])),
('time_risk_score', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])),
('user_risk_score', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])),
('overall_risk_score', models.IntegerField(default=0, validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])),
('risk_level', models.CharField(choices=[('LOW', 'Low Risk'), ('MEDIUM', 'Medium Risk'), ('HIGH', 'High Risk'), ('CRITICAL', 'Critical Risk')], default='LOW', max_length=20)),
('ip_address', models.GenericIPAddressField(blank=True, null=True)),
('user_agent', models.TextField(blank=True)),
('location_data', models.JSONField(default=dict, help_text='Geolocation and network data')),
('device_data', models.JSONField(default=dict, help_text='Device information')),
('behavior_data', models.JSONField(default=dict, help_text='User behavior patterns')),
('risk_factors', models.JSONField(default=list, help_text='List of identified risk factors')),
('mitigation_actions', models.JSONField(default=list, help_text='Recommended mitigation actions')),
('assessment_details', models.JSONField(default=dict, help_text='Detailed assessment results')),
('access_decision', models.CharField(choices=[('ALLOW', 'Allow Access'), ('DENY', 'Deny Access'), ('STEP_UP', 'Step-up Authentication'), ('REVIEW', 'Manual Review Required')], default='ALLOW', max_length=20)),
('decision_reason', models.TextField(blank=True)),
('assessed_at', models.DateTimeField(auto_now_add=True)),
('expires_at', models.DateTimeField(blank=True, null=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='risk_assessments', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-assessed_at'],
},
),
migrations.CreateModel(
name='UserBehaviorProfile',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('typical_login_times', models.JSONField(default=list, help_text='Typical login times')),
('typical_login_locations', models.JSONField(default=list, help_text='Typical login locations')),
('typical_login_devices', models.JSONField(default=list, help_text='Typical login devices')),
('typical_access_times', models.JSONField(default=list, help_text='Typical resource access times')),
('typical_access_patterns', models.JSONField(default=list, help_text='Typical access patterns')),
('typical_session_duration', models.FloatField(default=0.0, help_text='Typical session duration in hours')),
('typical_ip_ranges', models.JSONField(default=list, help_text='Typical IP address ranges')),
('typical_user_agents', models.JSONField(default=list, help_text='Typical user agents')),
('login_frequency', models.FloatField(default=0.0, help_text='Average logins per day')),
('access_frequency', models.FloatField(default=0.0, help_text='Average resource accesses per day')),
('anomaly_score', models.FloatField(default=0.0, help_text='Current anomaly score')),
('is_learning', models.BooleanField(default=True)),
('learning_start_date', models.DateTimeField(auto_now_add=True)),
('learning_complete_date', models.DateTimeField(blank=True, null=True)),
('sample_count', models.IntegerField(default=0, help_text='Number of samples used for learning')),
('last_updated', models.DateTimeField(auto_now=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='behavior_profile', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-last_updated'],
},
),
migrations.CreateModel(
name='DevicePosture',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('device_id', models.CharField(help_text='Unique device identifier', max_length=255, unique=True)),
('device_name', models.CharField(blank=True, max_length=200)),
('device_type', models.CharField(choices=[('DESKTOP', 'Desktop Computer'), ('LAPTOP', 'Laptop Computer'), ('MOBILE', 'Mobile Device'), ('TABLET', 'Tablet'), ('SERVER', 'Server'), ('IOT', 'IoT Device'), ('UNKNOWN', 'Unknown Device')], default='UNKNOWN', max_length=20)),
('os_type', models.CharField(choices=[('WINDOWS', 'Windows'), ('MACOS', 'macOS'), ('LINUX', 'Linux'), ('ANDROID', 'Android'), ('IOS', 'iOS'), ('UNKNOWN', 'Unknown OS')], default='UNKNOWN', max_length=20)),
('os_version', models.CharField(blank=True, max_length=100)),
('browser_info', models.CharField(blank=True, max_length=200)),
('is_managed', models.BooleanField(default=False, help_text='Is device managed by organization')),
('has_antivirus', models.BooleanField(default=False)),
('antivirus_status', models.CharField(blank=True, max_length=50)),
('firewall_enabled', models.BooleanField(default=False)),
('encryption_enabled', models.BooleanField(default=False)),
('screen_lock_enabled', models.BooleanField(default=False)),
('biometric_auth', models.BooleanField(default=False)),
('ip_address', models.GenericIPAddressField(blank=True, null=True)),
('mac_address', models.CharField(blank=True, max_length=17)),
('network_type', models.CharField(blank=True, help_text='Corporate, Public, Home, etc.', max_length=50)),
('vpn_connected', models.BooleanField(default=False)),
('risk_score', models.IntegerField(default=0, help_text='Device risk score (0-100, higher = more risky)', validators=[django.core.validators.MinValueValidator(0), django.core.validators.MaxValueValidator(100)])),
('last_assessment', models.DateTimeField(auto_now=True)),
('assessment_details', models.JSONField(default=dict, help_text='Detailed assessment results')),
('is_compliant', models.BooleanField(default=False)),
('compliance_issues', models.JSONField(default=list, help_text='List of compliance issues')),
('is_active', models.BooleanField(default=True)),
('is_trusted', models.BooleanField(default=False)),
('trust_level', models.CharField(choices=[('HIGH', 'High Trust'), ('MEDIUM', 'Medium Trust'), ('LOW', 'Low Trust'), ('UNTRUSTED', 'Untrusted')], default='LOW', max_length=20)),
('first_seen', models.DateTimeField(auto_now_add=True)),
('last_seen', models.DateTimeField(auto_now=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='device_postures', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-last_seen'],
'indexes': [models.Index(fields=['user', 'is_active'], name='security_de_user_id_b40615_idx'), models.Index(fields=['device_id'], name='security_de_device__3e5496_idx'), models.Index(fields=['risk_score', 'trust_level'], name='security_de_risk_sc_248ac7_idx'), models.Index(fields=['is_compliant', 'is_trusted'], name='security_de_is_comp_4de70c_idx')],
},
),
migrations.AddIndex(
model_name='geolocationrule',
index=models.Index(fields=['rule_type', 'is_active'], name='security_ge_rule_ty_2a030f_idx'),
),
migrations.AddIndex(
model_name='geolocationrule',
index=models.Index(fields=['priority'], name='security_ge_priorit_3ffb41_idx'),
),
migrations.AddIndex(
model_name='riskassessment',
index=models.Index(fields=['user', 'assessed_at'], name='security_ri_user_id_d9ab1c_idx'),
),
migrations.AddIndex(
model_name='riskassessment',
index=models.Index(fields=['overall_risk_score', 'risk_level'], name='security_ri_overall_4cd9c9_idx'),
),
migrations.AddIndex(
model_name='riskassessment',
index=models.Index(fields=['access_decision'], name='security_ri_access__e109fb_idx'),
),
migrations.AddIndex(
model_name='userbehaviorprofile',
index=models.Index(fields=['user', 'is_learning'], name='security_us_user_id_9b04d7_idx'),
),
migrations.AddIndex(
model_name='userbehaviorprofile',
index=models.Index(fields=['anomaly_score'], name='security_us_anomaly_2ca992_idx'),
),
]

View File

@@ -0,0 +1,34 @@
# Generated by Django 5.2.6 on 2025-09-18 20:06
import django.db.models.deletion
import uuid
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('security', '0003_adaptiveauthentication_and_more'),
]
operations = [
migrations.CreateModel(
name='SecurityEvent',
fields=[
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('event_type', models.CharField(choices=[('user_login', 'User Login'), ('user_logout', 'User Logout'), ('login_failure', 'Login Failure'), ('suspicious_request', 'Suspicious Request'), ('threat_detected', 'Threat Detected'), ('access_denied', 'Access Denied'), ('privilege_escalation', 'Privilege Escalation'), ('data_breach', 'Data Breach'), ('malware_detected', 'Malware Detected'), ('anomalous_behavior', 'Anomalous Behavior')], max_length=50)),
('severity', models.CharField(choices=[('info', 'Info'), ('warning', 'Warning'), ('error', 'Error'), ('critical', 'Critical')], max_length=20)),
('description', models.TextField()),
('ip_address', models.GenericIPAddressField(blank=True, null=True)),
('user_agent', models.TextField(blank=True)),
('metadata', models.JSONField(blank=True, default=dict)),
('timestamp', models.DateTimeField(auto_now_add=True)),
('user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['-timestamp'],
'indexes': [models.Index(fields=['event_type', 'timestamp'], name='security_se_event_t_1a00f0_idx'), models.Index(fields=['severity', 'timestamp'], name='security_se_severit_5c25b4_idx'), models.Index(fields=['user', 'timestamp'], name='security_se_user_id_6ceb62_idx')],
},
),
]

View File

1077
ETB-API/security/models.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
# Serializers for security API endpoints

View File

@@ -0,0 +1,277 @@
"""
Security-related serializers for API endpoints
"""
from rest_framework import serializers
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Permission
from ..models import (
DataClassification, Role, User, MFADevice,
AuditLog, SSOProvider, AccessPolicy
)
User = get_user_model()
class DataClassificationSerializer(serializers.ModelSerializer):
"""Serializer for data classification levels"""
class Meta:
model = DataClassification
fields = [
'id', 'name', 'level', 'description',
'color_code', 'requires_clearance',
'created_at', 'updated_at'
]
read_only_fields = ['id', 'created_at', 'updated_at']
class PermissionSerializer(serializers.ModelSerializer):
"""Serializer for permissions"""
class Meta:
model = Permission
fields = ['id', 'name', 'codename', 'content_type']
class RoleSerializer(serializers.ModelSerializer):
"""Serializer for roles"""
permissions = PermissionSerializer(many=True, read_only=True)
permission_ids = serializers.ListField(
child=serializers.IntegerField(),
write_only=True,
required=False
)
data_classification_access = DataClassificationSerializer(many=True, read_only=True)
classification_ids = serializers.ListField(
child=serializers.IntegerField(),
write_only=True,
required=False
)
class Meta:
model = Role
fields = [
'id', 'name', 'description', 'permissions', 'permission_ids',
'data_classification_access', 'classification_ids',
'is_active', 'created_at', 'updated_at'
]
read_only_fields = ['id', 'created_at', 'updated_at']
def create(self, validated_data):
permission_ids = validated_data.pop('permission_ids', [])
classification_ids = validated_data.pop('classification_ids', [])
role = Role.objects.create(**validated_data)
if permission_ids:
role.permissions.set(permission_ids)
if classification_ids:
role.data_classification_access.set(classification_ids)
return role
def update(self, instance, validated_data):
permission_ids = validated_data.pop('permission_ids', None)
classification_ids = validated_data.pop('classification_ids', None)
for attr, value in validated_data.items():
setattr(instance, attr, value)
instance.save()
if permission_ids is not None:
instance.permissions.set(permission_ids)
if classification_ids is not None:
instance.data_classification_access.set(classification_ids)
return instance
class UserSerializer(serializers.ModelSerializer):
"""Serializer for users"""
roles = RoleSerializer(many=True, read_only=True)
role_ids = serializers.ListField(
child=serializers.IntegerField(),
write_only=True,
required=False
)
clearance_level = DataClassificationSerializer(read_only=True)
clearance_level_id = serializers.IntegerField(write_only=True, required=False)
mfa_enabled = serializers.BooleanField(read_only=True)
is_account_locked = serializers.BooleanField(read_only=True)
password = serializers.CharField(write_only=True, required=False)
class Meta:
model = User
fields = [
'id', 'username', 'email', 'first_name', 'last_name',
'employee_id', 'department', 'clearance_level', 'clearance_level_id',
'roles', 'role_ids', 'attributes', 'mfa_enabled', 'mfa_secret',
'last_login_ip', 'failed_login_attempts', 'is_account_locked',
'sso_provider', 'sso_identifier', 'is_active', 'is_staff', 'is_superuser',
'date_joined', 'last_login', 'created_at', 'updated_at', 'password'
]
read_only_fields = [
'id', 'mfa_enabled', 'mfa_secret', 'last_login_ip',
'failed_login_attempts', 'is_account_locked', 'sso_provider',
'sso_identifier', 'date_joined', 'last_login', 'created_at', 'updated_at'
]
def create(self, validated_data):
password = validated_data.pop('password', None)
role_ids = validated_data.pop('role_ids', [])
clearance_level_id = validated_data.pop('clearance_level_id', None)
if clearance_level_id:
validated_data['clearance_level_id'] = clearance_level_id
user = User.objects.create_user(**validated_data)
if password:
user.set_password(password)
user.save()
if role_ids:
user.roles.set(role_ids)
return user
def update(self, instance, validated_data):
password = validated_data.pop('password', None)
role_ids = validated_data.pop('role_ids', None)
clearance_level_id = validated_data.pop('clearance_level_id', None)
if clearance_level_id:
validated_data['clearance_level_id'] = clearance_level_id
for attr, value in validated_data.items():
setattr(instance, attr, value)
if password:
instance.set_password(password)
instance.save()
if role_ids is not None:
instance.roles.set(role_ids)
return instance
class MFADeviceSerializer(serializers.ModelSerializer):
"""Serializer for MFA devices"""
user = serializers.StringRelatedField(read_only=True)
secret_key = serializers.CharField(read_only=True) # Never expose secret
class Meta:
model = MFADevice
fields = [
'id', 'user', 'device_type', 'name', 'is_active',
'is_primary', 'last_used', 'created_at', 'updated_at'
]
read_only_fields = ['id', 'user', 'secret_key', 'created_at', 'updated_at']
class MFASetupSerializer(serializers.Serializer):
"""Serializer for MFA setup"""
device_name = serializers.CharField(max_length=100)
device_type = serializers.ChoiceField(choices=MFADevice.DEVICE_TYPES)
def validate_device_name(self, value):
"""Validate device name is unique for user"""
user = self.context['request'].user
if MFADevice.objects.filter(user=user, name=value).exists():
raise serializers.ValidationError("Device name already exists")
return value
class MFAVerificationSerializer(serializers.Serializer):
"""Serializer for MFA verification"""
token = serializers.CharField(max_length=10, min_length=6)
device_id = serializers.UUIDField(required=False)
class AuditLogSerializer(serializers.ModelSerializer):
"""Serializer for audit logs"""
user = serializers.StringRelatedField(read_only=True)
class Meta:
model = AuditLog
fields = [
'id', 'timestamp', 'user', 'action_type', 'resource_type',
'resource_id', 'ip_address', 'user_agent', 'details',
'severity', 'hash_value'
]
read_only_fields = ['id', 'timestamp', 'hash_value']
class SSOProviderSerializer(serializers.ModelSerializer):
"""Serializer for SSO providers"""
configuration = serializers.JSONField()
attribute_mapping = serializers.JSONField()
class Meta:
model = SSOProvider
fields = [
'id', 'name', 'provider_type', 'is_active',
'configuration', 'attribute_mapping',
'created_at', 'updated_at'
]
read_only_fields = ['id', 'created_at', 'updated_at']
class AccessPolicySerializer(serializers.ModelSerializer):
"""Serializer for access policies"""
conditions = serializers.JSONField()
actions = serializers.JSONField()
class Meta:
model = AccessPolicy
fields = [
'id', 'name', 'description', 'policy_type',
'conditions', 'resource_type', 'actions',
'priority', 'is_active', 'created_at', 'updated_at'
]
read_only_fields = ['id', 'created_at', 'updated_at']
class LoginSerializer(serializers.Serializer):
"""Serializer for user login"""
username = serializers.CharField()
password = serializers.CharField()
mfa_token = serializers.CharField(required=False, allow_blank=True)
remember_me = serializers.BooleanField(default=False)
class PasswordChangeSerializer(serializers.Serializer):
"""Serializer for password change"""
current_password = serializers.CharField()
new_password = serializers.CharField(min_length=8)
confirm_password = serializers.CharField()
def validate(self, data):
"""Validate password change"""
if data['new_password'] != data['confirm_password']:
raise serializers.ValidationError("Passwords do not match")
return data
class UserProfileSerializer(serializers.ModelSerializer):
"""Serializer for user profile"""
roles = RoleSerializer(many=True, read_only=True)
clearance_level = DataClassificationSerializer(read_only=True)
mfa_devices = MFADeviceSerializer(many=True, read_only=True)
class Meta:
model = User
fields = [
'id', 'username', 'email', 'first_name', 'last_name',
'employee_id', 'department', 'clearance_level', 'roles',
'mfa_enabled', 'mfa_devices', 'is_superuser', 'is_staff', 'is_active',
'date_joined', 'last_login', 'created_at', 'updated_at'
]
read_only_fields = [
'id', 'username', 'employee_id', 'clearance_level',
'roles', 'mfa_enabled', 'is_superuser', 'is_staff', 'is_active',
'date_joined', 'last_login', 'created_at', 'updated_at'
]

View File

@@ -0,0 +1,203 @@
"""
Zero Trust Serializers
Serializers for device posture, geolocation rules, risk assessment, and adaptive authentication
"""
from rest_framework import serializers
from django.contrib.auth import get_user_model
from ..models import (
DevicePosture, GeolocationRule, RiskAssessment,
AdaptiveAuthentication, UserBehaviorProfile
)
User = get_user_model()
class DevicePostureSerializer(serializers.ModelSerializer):
"""Serializer for device posture"""
class Meta:
model = DevicePosture
fields = [
'id', 'device_id', 'device_name', 'device_type', 'os_type',
'os_version', 'browser_info', 'is_managed', 'has_antivirus',
'antivirus_status', 'firewall_enabled', 'encryption_enabled',
'screen_lock_enabled', 'biometric_auth', 'ip_address',
'mac_address', 'network_type', 'vpn_connected', 'risk_score',
'is_compliant', 'is_trusted', 'trust_level', 'is_active',
'first_seen', 'last_seen', 'created_at', 'updated_at'
]
read_only_fields = [
'id', 'device_id', 'risk_score', 'trust_level', 'is_trusted',
'first_seen', 'last_seen', 'created_at', 'updated_at'
]
class DeviceRegistrationSerializer(serializers.Serializer):
"""Serializer for device registration"""
device_id = serializers.CharField(max_length=255, required=True)
device_name = serializers.CharField(max_length=200, required=False, allow_blank=True)
device_type = serializers.ChoiceField(choices=DevicePosture.DEVICE_TYPES, required=False)
os_type = serializers.ChoiceField(choices=DevicePosture.OS_TYPES, required=False)
os_version = serializers.CharField(max_length=100, required=False, allow_blank=True)
browser_info = serializers.CharField(max_length=200, required=False, allow_blank=True)
is_managed = serializers.BooleanField(default=False)
has_antivirus = serializers.BooleanField(default=False)
antivirus_status = serializers.CharField(max_length=50, required=False, allow_blank=True)
firewall_enabled = serializers.BooleanField(default=False)
encryption_enabled = serializers.BooleanField(default=False)
screen_lock_enabled = serializers.BooleanField(default=False)
biometric_auth = serializers.BooleanField(default=False)
ip_address = serializers.IPAddressField(required=False)
mac_address = serializers.CharField(max_length=17, required=False, allow_blank=True)
network_type = serializers.CharField(max_length=50, required=False, allow_blank=True)
vpn_connected = serializers.BooleanField(default=False)
class GeolocationRuleSerializer(serializers.ModelSerializer):
"""Serializer for geolocation rules"""
class Meta:
model = GeolocationRule
fields = [
'id', 'name', 'description', 'rule_type', 'allowed_countries',
'blocked_countries', 'allowed_regions', 'blocked_regions',
'allowed_cities', 'blocked_cities', 'allowed_ip_ranges',
'blocked_ip_ranges', 'allowed_time_zones', 'working_hours_only',
'working_hours_start', 'working_hours_end', 'working_days',
'max_distance_from_office', 'office_latitude', 'office_longitude',
'notification_message', 'log_violation', 'require_manager_approval',
'is_active', 'priority', 'created_at', 'updated_at'
]
read_only_fields = ['id', 'created_at', 'updated_at']
class RiskAssessmentSerializer(serializers.ModelSerializer):
"""Serializer for risk assessments"""
class Meta:
model = RiskAssessment
fields = [
'id', 'user', 'assessment_type', 'resource_type', 'resource_id',
'device_risk_score', 'location_risk_score', 'behavior_risk_score',
'network_risk_score', 'time_risk_score', 'user_risk_score',
'overall_risk_score', 'risk_level', 'ip_address', 'user_agent',
'location_data', 'device_data', 'behavior_data', 'risk_factors',
'mitigation_actions', 'assessment_details', 'access_decision',
'decision_reason', 'assessed_at', 'expires_at'
]
read_only_fields = [
'id', 'user', 'overall_risk_score', 'risk_level', 'access_decision',
'assessed_at', 'expires_at'
]
class RiskAssessmentRequestSerializer(serializers.Serializer):
"""Serializer for risk assessment requests"""
assessment_type = serializers.ChoiceField(
choices=RiskAssessment.RISK_FACTORS,
default='LOGIN'
)
resource_type = serializers.CharField(max_length=100, required=False, allow_blank=True)
resource_id = serializers.CharField(max_length=255, required=False, allow_blank=True)
device_id = serializers.CharField(max_length=255, required=False, allow_blank=True)
location_data = serializers.JSONField(required=False, default=dict)
additional_context = serializers.JSONField(required=False, default=dict)
class AdaptiveAuthenticationSerializer(serializers.ModelSerializer):
"""Serializer for adaptive authentication"""
class Meta:
model = AdaptiveAuthentication
fields = [
'id', 'name', 'description', 'low_risk_threshold', 'medium_risk_threshold',
'high_risk_threshold', 'low_risk_auth_methods', 'medium_risk_auth_methods',
'high_risk_auth_methods', 'critical_risk_auth_methods',
'device_trust_multiplier', 'location_trust_multiplier', 'time_trust_multiplier',
'enable_behavioral_analysis', 'behavior_learning_period', 'anomaly_threshold',
'ml_enabled', 'ml_model_path', 'ml_confidence_threshold',
'fallback_auth_methods', 'max_auth_attempts', 'lockout_duration',
'is_active', 'created_at', 'updated_at'
]
read_only_fields = ['id', 'created_at', 'updated_at']
class UserBehaviorProfileSerializer(serializers.ModelSerializer):
"""Serializer for user behavior profiles"""
class Meta:
model = UserBehaviorProfile
fields = [
'id', 'user', 'typical_login_times', 'typical_login_locations',
'typical_login_devices', 'typical_access_times', 'typical_access_patterns',
'typical_session_duration', 'typical_ip_ranges', 'typical_user_agents',
'login_frequency', 'access_frequency', 'anomaly_score', 'is_learning',
'learning_start_date', 'learning_complete_date', 'sample_count',
'last_updated', 'created_at'
]
read_only_fields = [
'id', 'user', 'anomaly_score', 'learning_start_date', 'learning_complete_date',
'sample_count', 'last_updated', 'created_at'
]
class ZeroTrustStatusSerializer(serializers.Serializer):
"""Serializer for Zero Trust system status"""
zero_trust_enabled = serializers.BooleanField()
user_status = serializers.DictField()
system_configuration = serializers.DictField()
recommendations = serializers.ListField()
class DeviceSecurityRecommendationSerializer(serializers.Serializer):
"""Serializer for device security recommendations"""
type = serializers.CharField()
priority = serializers.ChoiceField(choices=['low', 'medium', 'high', 'critical'])
message = serializers.CharField()
action = serializers.CharField()
details = serializers.DictField(required=False)
class RiskMitigationActionSerializer(serializers.Serializer):
"""Serializer for risk mitigation actions"""
action_type = serializers.CharField()
description = serializers.CharField()
priority = serializers.ChoiceField(choices=['low', 'medium', 'high', 'critical'])
required_auth_methods = serializers.ListField()
estimated_time = serializers.IntegerField(help_text="Estimated time in minutes")
automated = serializers.BooleanField(default=False)
class GeolocationTestSerializer(serializers.Serializer):
"""Serializer for testing geolocation rules"""
latitude = serializers.FloatField(required=False)
longitude = serializers.FloatField(required=False)
country_code = serializers.CharField(max_length=2, required=False, allow_blank=True)
region = serializers.CharField(max_length=100, required=False, allow_blank=True)
city = serializers.CharField(max_length=100, required=False, allow_blank=True)
ip_address = serializers.IPAddressField(required=False)
class BehavioralAnomalySerializer(serializers.Serializer):
"""Serializer for behavioral anomaly detection"""
login_time = serializers.DateTimeField(required=False)
location = serializers.DictField(required=False)
device_id = serializers.CharField(max_length=255, required=False, allow_blank=True)
ip_address = serializers.IPAddressField(required=False)
user_agent = serializers.CharField(required=False, allow_blank=True)
session_duration = serializers.FloatField(required=False)
access_pattern = serializers.ListField(required=False)
class AccessDecisionSerializer(serializers.Serializer):
"""Serializer for access decisions"""
access_granted = serializers.BooleanField()
reason = serializers.CharField()
required_actions = serializers.ListField()
risk_level = serializers.ChoiceField(choices=['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'])
risk_score = serializers.IntegerField(min_value=0, max_value=100)
auth_requirements = serializers.ListField()
assessment_id = serializers.UUIDField()
expires_at = serializers.DateTimeField(required=False)
mitigation_actions = serializers.ListField(required=False)

View File

@@ -0,0 +1,503 @@
"""
Zero Trust Architecture Service
Integrates device posture, geolocation, risk assessment, and adaptive authentication
"""
import logging
import requests
from typing import Dict, Any, Optional, List, Tuple
from datetime import datetime, timedelta
from django.conf import settings
from django.utils import timezone
from django.contrib.auth import get_user_model
from ..models import (
User, DevicePosture, GeolocationRule, AccessPolicy,
RiskAssessment, AdaptiveAuthentication, UserBehaviorProfile,
AuditLog
)
logger = logging.getLogger(__name__)
User = get_user_model()
class ZeroTrustService:
"""Main Zero Trust service for comprehensive security decisions"""
def __init__(self):
self.geo_api_key = getattr(settings, 'GEO_API_KEY', None)
self.risk_thresholds = {
'low': 25,
'medium': 50,
'high': 75,
'critical': 100
}
def assess_access_request(self, user: User, request_data: Dict[str, Any]) -> Dict[str, Any]:
"""
Comprehensive access assessment using Zero Trust principles
Args:
user: User requesting access
request_data: Request context including IP, user agent, etc.
Returns:
Dict with access decision and required actions
"""
try:
# Collect context data
context = self._collect_context_data(user, request_data)
# Perform risk assessment
risk_assessment = self._perform_risk_assessment(user, context)
# Evaluate access policies
policy_result = self._evaluate_access_policies(user, context)
# Determine adaptive authentication requirements
auth_requirements = self._determine_auth_requirements(risk_assessment, context)
# Make final access decision
decision = self._make_access_decision(
risk_assessment, policy_result, auth_requirements, context
)
# Log the assessment
self._log_access_assessment(user, context, decision)
return decision
except Exception as e:
logger.error(f"Zero Trust assessment failed: {e}")
return {
'access_granted': False,
'reason': 'Assessment failed',
'required_actions': ['MANUAL_REVIEW'],
'risk_level': 'HIGH',
'error': str(e)
}
def _collect_context_data(self, user: User, request_data: Dict[str, Any]) -> Dict[str, Any]:
"""Collect comprehensive context data for assessment"""
context = {
'user': user,
'ip_address': request_data.get('ip_address'),
'user_agent': request_data.get('user_agent'),
'timestamp': timezone.now(),
'request_data': request_data
}
# Get device posture
device_id = request_data.get('device_id')
if device_id:
try:
device_posture = DevicePosture.objects.get(
device_id=device_id,
user=user,
is_active=True
)
context['device_posture'] = device_posture
except DevicePosture.DoesNotExist:
context['device_posture'] = None
# Get geolocation data
if context['ip_address']:
location_data = self._get_geolocation_data(context['ip_address'])
context['location'] = location_data
# Get user behavior profile
try:
behavior_profile = UserBehaviorProfile.objects.get(user=user)
context['behavior_profile'] = behavior_profile
except UserBehaviorProfile.DoesNotExist:
context['behavior_profile'] = None
return context
def _get_geolocation_data(self, ip_address: str) -> Dict[str, Any]:
"""Get geolocation data for IP address"""
try:
# Use external geolocation service (e.g., ipapi.co, ipinfo.io)
if self.geo_api_key:
response = requests.get(
f'https://ipapi.co/{ip_address}/json/',
params={'key': self.geo_api_key},
timeout=5
)
if response.status_code == 200:
data = response.json()
return {
'latitude': data.get('latitude'),
'longitude': data.get('longitude'),
'country_code': data.get('country_code'),
'region': data.get('region'),
'city': data.get('city'),
'timezone': data.get('timezone'),
'isp': data.get('org'),
'ip_address': ip_address
}
except Exception as e:
logger.warning(f"Geolocation lookup failed for {ip_address}: {e}")
return {'ip_address': ip_address}
def _perform_risk_assessment(self, user: User, context: Dict[str, Any]) -> RiskAssessment:
"""Perform comprehensive risk assessment"""
assessment = RiskAssessment(
user=user,
assessment_type='ACCESS',
ip_address=context.get('ip_address'),
user_agent=context.get('user_agent'),
location_data=context.get('location', {}),
device_data=context.get('device_posture', {}),
behavior_data=context.get('behavior_profile', {})
)
# Calculate individual risk scores
assessment.device_risk_score = self._calculate_device_risk(context)
assessment.location_risk_score = self._calculate_location_risk(context)
assessment.behavior_risk_score = self._calculate_behavior_risk(context)
assessment.network_risk_score = self._calculate_network_risk(context)
assessment.time_risk_score = self._calculate_time_risk(context)
assessment.user_risk_score = self._calculate_user_risk(user, context)
# Calculate overall risk
assessment.calculate_overall_risk()
assessment.determine_access_decision()
# Save assessment
assessment.save()
return assessment
def _calculate_device_risk(self, context: Dict[str, Any]) -> int:
"""Calculate device-based risk score"""
device_posture = context.get('device_posture')
if not device_posture:
return 80 # High risk for unknown devices
# Use device posture risk score
return device_posture.risk_score
def _calculate_location_risk(self, context: Dict[str, Any]) -> int:
"""Calculate location-based risk score"""
location = context.get('location', {})
if not location.get('latitude') or not location.get('longitude'):
return 50 # Medium risk for unknown location
# Check against geolocation rules
rules = GeolocationRule.objects.filter(is_active=True).order_by('priority')
for rule in rules:
result = rule.evaluate_location(**location)
if result['matches']:
if rule.rule_type == 'DENY':
return 100 # Critical risk
elif rule.rule_type == 'REQUIRE_MFA':
return 60 # High risk
elif rule.rule_type == 'RESTRICT':
return 40 # Medium risk
# Default location risk based on country/IP
country_code = location.get('country_code')
if country_code in ['CN', 'RU', 'KP', 'IR']: # High-risk countries
return 70
elif country_code in ['US', 'CA', 'GB', 'DE', 'FR', 'BG']: # Trusted countries
return 20
else:
return 40 # Medium risk for other countries
def _calculate_behavior_risk(self, context: Dict[str, Any]) -> int:
"""Calculate behavior-based risk score"""
behavior_profile = context.get('behavior_profile')
if not behavior_profile:
return 30 # Low risk for new users (learning period)
# Calculate anomaly score
current_behavior = {
'login_time': context.get('timestamp'),
'location': context.get('location'),
'device_id': context.get('device_posture', {}).get('device_id'),
'ip_address': context.get('ip_address')
}
anomaly_score = behavior_profile.calculate_anomaly_score(current_behavior)
# Convert anomaly score (0-1) to risk score (0-100)
return int(anomaly_score * 100)
def _calculate_network_risk(self, context: Dict[str, Any]) -> int:
"""Calculate network-based risk score"""
device_posture = context.get('device_posture')
location = context.get('location', {})
risk = 0
# VPN connection
if device_posture and device_posture.vpn_connected:
risk -= 20 # Reduce risk for VPN
# Network type
if device_posture:
if device_posture.network_type == 'Public':
risk += 40
elif device_posture.network_type == 'Home':
risk += 20
elif device_posture.network_type == 'Corporate':
risk -= 10
# ISP reputation (simplified)
isp = location.get('isp', '').lower()
if any(keyword in isp for keyword in ['tor', 'proxy', 'vpn']):
risk += 30
return max(0, min(100, risk))
def _calculate_time_risk(self, context: Dict[str, Any]) -> int:
"""Calculate time-based risk score"""
timestamp = context.get('timestamp', timezone.now())
hour = timestamp.hour
weekday = timestamp.weekday()
# Business hours (9 AM - 5 PM, Monday-Friday)
if 9 <= hour <= 17 and weekday < 5:
return 10 # Low risk during business hours
elif 6 <= hour <= 22 and weekday < 5:
return 30 # Medium risk during extended hours
else:
return 60 # High risk during unusual hours
def _calculate_user_risk(self, user: User, context: Dict[str, Any]) -> int:
"""Calculate user-based risk score"""
risk = 0
# Account age
account_age = (timezone.now() - user.date_joined).days
if account_age < 7:
risk += 30 # New accounts are riskier
elif account_age > 365:
risk -= 10 # Established accounts are less risky
# Failed login attempts
if user.failed_login_attempts > 3:
risk += 40
elif user.failed_login_attempts > 0:
risk += 20
# Account lock status
if user.is_account_locked():
risk += 50
# MFA status
if not user.mfa_enabled:
risk += 20
# Clearance level
if user.clearance_level and user.clearance_level.level >= 4:
risk -= 10 # High clearance users are less risky
return max(0, min(100, risk))
def _evaluate_access_policies(self, user: User, context: Dict[str, Any]) -> Dict[str, Any]:
"""Evaluate all applicable access policies"""
policies = AccessPolicy.objects.filter(is_active=True).order_by('priority')
for policy in policies:
result = policy.evaluate(user, context=context)
if result['required_actions'] or not result['allowed']:
return result
return {'allowed': True, 'required_actions': []}
def _determine_auth_requirements(self, risk_assessment: RiskAssessment, context: Dict[str, Any]) -> List[str]:
"""Determine required authentication methods based on risk"""
try:
adaptive_auth = AdaptiveAuthentication.objects.filter(is_active=True).first()
if not adaptive_auth:
return ['PASSWORD'] # Default to password only
# Get required auth methods based on risk score
required_methods = adaptive_auth.get_required_auth_methods(risk_assessment.overall_risk_score)
# Adjust based on context
adjusted_risk = adaptive_auth.calculate_adjusted_risk(
risk_assessment.overall_risk_score,
context
)
# If adjusted risk is higher, require additional methods
if adjusted_risk > risk_assessment.overall_risk_score:
if 'MFA_TOTP' not in required_methods:
required_methods.append('MFA_TOTP')
return required_methods
except Exception as e:
logger.error(f"Failed to determine auth requirements: {e}")
return ['PASSWORD', 'MFA_TOTP'] # Default to password + MFA
def _make_access_decision(self, risk_assessment: RiskAssessment,
policy_result: Dict[str, Any],
auth_requirements: List[str],
context: Dict[str, Any]) -> Dict[str, Any]:
"""Make final access decision based on all factors"""
decision = {
'access_granted': False,
'reason': '',
'required_actions': [],
'risk_level': risk_assessment.risk_level,
'risk_score': risk_assessment.overall_risk_score,
'auth_requirements': auth_requirements,
'assessment_id': str(risk_assessment.id)
}
# Check policy restrictions first
if not policy_result.get('allowed', True):
decision['reason'] = policy_result.get('reason', 'Policy restriction')
decision['required_actions'] = policy_result.get('required_actions', [])
return decision
# Check risk-based decision
if risk_assessment.access_decision == 'DENY':
decision['reason'] = risk_assessment.decision_reason
decision['required_actions'] = ['MANUAL_REVIEW']
return decision
elif risk_assessment.access_decision == 'REVIEW':
decision['reason'] = risk_assessment.decision_reason
decision['required_actions'] = ['MANUAL_REVIEW']
return decision
elif risk_assessment.access_decision == 'STEP_UP':
decision['access_granted'] = True
decision['reason'] = risk_assessment.decision_reason
decision['required_actions'] = ['STEP_UP_AUTH']
return decision
# Low risk - allow access
decision['access_granted'] = True
decision['reason'] = 'Access granted - low risk'
# Add any required actions from policies
if policy_result.get('required_actions'):
decision['required_actions'].extend(policy_result['required_actions'])
return decision
def _log_access_assessment(self, user: User, context: Dict[str, Any], decision: Dict[str, Any]):
"""Log the access assessment for audit purposes"""
try:
AuditLog.objects.create(
user=user,
action_type='ACCESS_ASSESSMENT',
resource_type='Zero Trust Assessment',
resource_id=decision.get('assessment_id', ''),
ip_address=context.get('ip_address'),
user_agent=context.get('user_agent'),
details={
'risk_level': decision.get('risk_level'),
'risk_score': decision.get('risk_score'),
'access_granted': decision.get('access_granted'),
'required_actions': decision.get('required_actions'),
'auth_requirements': decision.get('auth_requirements'),
'device_id': context.get('device_posture', {}).get('device_id'),
'location': context.get('location', {})
},
severity='HIGH' if decision.get('risk_level') in ['HIGH', 'CRITICAL'] else 'MEDIUM'
)
except Exception as e:
logger.error(f"Failed to log access assessment: {e}")
def register_device(self, user: User, device_data: Dict[str, Any]) -> DevicePosture:
"""Register a new device for Zero Trust assessment"""
device_id = device_data.get('device_id')
if not device_id:
raise ValueError("Device ID is required")
# Create or update device posture
device_posture, created = DevicePosture.objects.get_or_create(
device_id=device_id,
user=user,
defaults={
'device_name': device_data.get('device_name', ''),
'device_type': device_data.get('device_type', 'UNKNOWN'),
'os_type': device_data.get('os_type', 'UNKNOWN'),
'os_version': device_data.get('os_version', ''),
'browser_info': device_data.get('browser_info', ''),
'is_managed': device_data.get('is_managed', False),
'has_antivirus': device_data.get('has_antivirus', False),
'firewall_enabled': device_data.get('firewall_enabled', False),
'encryption_enabled': device_data.get('encryption_enabled', False),
'screen_lock_enabled': device_data.get('screen_lock_enabled', False),
'biometric_auth': device_data.get('biometric_auth', False),
'ip_address': device_data.get('ip_address'),
'network_type': device_data.get('network_type', ''),
'vpn_connected': device_data.get('vpn_connected', False)
}
)
if not created:
# Update existing device
for field, value in device_data.items():
if hasattr(device_posture, field):
setattr(device_posture, field, value)
# Calculate risk score and update trust level
device_posture.risk_score = device_posture.calculate_risk_score()
device_posture.update_trust_level()
device_posture.save()
# Log device registration
AuditLog.objects.create(
user=user,
action_type='DEVICE_REGISTERED',
resource_type='Device',
resource_id=str(device_posture.id),
details={
'device_id': device_id,
'device_type': device_posture.device_type,
'trust_level': device_posture.trust_level,
'risk_score': device_posture.risk_score,
'is_compliant': device_posture.is_compliant
},
severity='MEDIUM'
)
return device_posture
def update_behavior_profile(self, user: User, behavior_data: Dict[str, Any]):
"""Update user behavior profile for anomaly detection"""
try:
profile, created = UserBehaviorProfile.objects.get_or_create(user=user)
# Update behavior patterns (simplified)
current_time = behavior_data.get('timestamp', timezone.now())
current_location = behavior_data.get('location', {})
current_device = behavior_data.get('device_id')
current_ip = behavior_data.get('ip_address')
# Add to typical patterns (in production, this would be more sophisticated)
if current_time not in profile.typical_login_times:
profile.typical_login_times.append(current_time.isoformat())
if current_location and current_location not in profile.typical_login_locations:
profile.typical_login_locations.append(current_location)
if current_device and current_device not in profile.typical_login_devices:
profile.typical_login_devices.append(current_device)
if current_ip and current_ip not in profile.typical_ip_ranges:
profile.typical_ip_ranges.append(current_ip)
# Update sample count
profile.sample_count += 1
# Check if learning period is complete
if profile.is_learning and profile.sample_count >= 30:
profile.is_learning = False
profile.learning_complete_date = timezone.now()
profile.save()
except Exception as e:
logger.error(f"Failed to update behavior profile: {e}")
# Global instance
zero_trust_service = ZeroTrustService()

View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

52
ETB-API/security/urls.py Normal file
View File

@@ -0,0 +1,52 @@
"""
URL configuration for security app
"""
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from .views.security import (
DataClassificationViewSet, RoleViewSet, UserViewSet,
MFADeviceViewSet, AuditLogViewSet, SSOProviderViewSet,
AccessPolicyViewSet, login_view, logout_view, user_profile,
change_password, mfa_status
)
from .views.zero_trust import (
DevicePostureViewSet, GeolocationRuleViewSet, RiskAssessmentViewSet,
AdaptiveAuthenticationViewSet, UserBehaviorProfileViewSet,
zero_trust_status, perform_risk_assessment
)
# Create router for ViewSets
router = DefaultRouter()
router.register(r'classifications', DataClassificationViewSet)
router.register(r'roles', RoleViewSet)
router.register(r'users', UserViewSet)
router.register(r'mfa-devices', MFADeviceViewSet, basename='mfadevice')
router.register(r'audit-logs', AuditLogViewSet)
router.register(r'sso-providers', SSOProviderViewSet)
router.register(r'access-policies', AccessPolicyViewSet)
# Zero Trust ViewSets
router.register(r'device-postures', DevicePostureViewSet, basename='deviceposture')
router.register(r'geolocation-rules', GeolocationRuleViewSet)
router.register(r'risk-assessments', RiskAssessmentViewSet, basename='riskassessment')
router.register(r'adaptive-auth', AdaptiveAuthenticationViewSet)
router.register(r'behavior-profiles', UserBehaviorProfileViewSet, basename='behaviorprofile')
app_name = 'security'
urlpatterns = [
# API router
path('api/', include(router.urls)),
# Authentication endpoints
path('api/auth/login/', login_view, name='login'),
path('api/auth/logout/', logout_view, name='logout'),
path('api/auth/profile/', user_profile, name='user-profile'),
path('api/auth/change-password/', change_password, name='change-password'),
path('api/auth/mfa-status/', mfa_status, name='mfa-status'),
# Zero Trust endpoints
path('api/zero-trust/status/', zero_trust_status, name='zero-trust-status'),
path('api/zero-trust/assess/', perform_risk_assessment, name='risk-assessment'),
]

View File

@@ -0,0 +1,3 @@
from django.shortcuts import render
# Create your views here.

View File

@@ -0,0 +1 @@
# Views for security API endpoints

View File

@@ -0,0 +1,533 @@
"""
Security API views for authentication, authorization, and audit
"""
import base64
from datetime import datetime, timedelta
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.utils import timezone
from django.db.models import Q
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from rest_framework import status, generics, permissions
from rest_framework.decorators import api_view, permission_classes, action
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from rest_framework.authtoken.models import Token
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from ..models import (
DataClassification, Role, User, MFADevice,
AuditLog, SSOProvider, AccessPolicy
)
from ..serializers.security import (
DataClassificationSerializer, RoleSerializer, UserSerializer,
MFADeviceSerializer, MFASetupSerializer, MFAVerificationSerializer,
AuditLogSerializer, SSOProviderSerializer, AccessPolicySerializer,
LoginSerializer, PasswordChangeSerializer, UserProfileSerializer
)
from ..mfa.totp import MFAProvider
from ..authentication.sso import SSOAuthentication
class DataClassificationViewSet(ModelViewSet):
"""ViewSet for data classification management"""
queryset = DataClassification.objects.all()
serializer_class = DataClassificationSerializer
permission_classes = [IsAdminUser]
def get_queryset(self):
"""Filter classifications based on user clearance"""
user = self.request.user
if user.is_superuser:
return DataClassification.objects.all()
# Filter based on user's clearance level
if user.clearance_level:
return DataClassification.objects.filter(
level__lte=user.clearance_level.level
)
return DataClassification.objects.filter(level__lte=1)
class RoleViewSet(ModelViewSet):
"""ViewSet for role management"""
queryset = Role.objects.all()
serializer_class = RoleSerializer
permission_classes = [IsAdminUser]
def get_queryset(self):
"""Filter roles based on user permissions"""
user = self.request.user
if user.is_superuser:
return Role.objects.all()
# Users can only see roles they have permissions for
return Role.objects.filter(
permissions__in=user.get_effective_permissions()
).distinct()
class UserViewSet(ModelViewSet):
"""ViewSet for user management"""
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [IsAdminUser]
def get_queryset(self):
"""Filter users based on data classification access"""
user = self.request.user
if user.is_superuser:
return User.objects.all()
# Filter based on clearance level
if user.clearance_level:
return User.objects.filter(
clearance_level__level__lte=user.clearance_level.level
)
return User.objects.filter(clearance_level__level__lte=1)
@action(detail=True, methods=['post'])
def lock_account(self, request, pk=None):
"""Lock user account"""
user = self.get_object()
duration = request.data.get('duration_minutes', 30)
user.lock_account(duration)
# Log the action
AuditLog.objects.create(
user=request.user,
action_type='ACCOUNT_LOCKED',
resource_type='User',
resource_id=str(user.id),
ip_address=self._get_client_ip(request),
user_agent=request.META.get('HTTP_USER_AGENT', ''),
details={'locked_user': user.username, 'duration_minutes': duration},
severity='HIGH'
)
return Response({'message': f'Account locked for {duration} minutes'})
@action(detail=True, methods=['post'])
def unlock_account(self, request, pk=None):
"""Unlock user account"""
user = self.get_object()
user.unlock_account()
# Log the action
AuditLog.objects.create(
user=request.user,
action_type='ACCOUNT_UNLOCKED',
resource_type='User',
resource_id=str(user.id),
ip_address=self._get_client_ip(request),
user_agent=request.META.get('HTTP_USER_AGENT', ''),
details={'unlocked_user': user.username},
severity='MEDIUM'
)
return Response({'message': 'Account unlocked'})
@action(detail=True, methods=['patch'])
def update_roles(self, request, pk=None):
"""Update user roles"""
user = self.get_object()
role_ids = request.data.get('role_ids', [])
# Validate role IDs
roles = Role.objects.filter(id__in=role_ids)
if len(roles) != len(role_ids):
return Response(
{'error': 'Invalid role IDs provided'},
status=status.HTTP_400_BAD_REQUEST
)
# Update user roles
user.roles.set(roles)
# Log the action
AuditLog.objects.create(
user=request.user,
action_type='ROLES_UPDATED',
resource_type='User',
resource_id=str(user.id),
ip_address=self._get_client_ip(request),
user_agent=request.META.get('HTTP_USER_AGENT', ''),
details={'updated_user': user.username, 'new_roles': [role.name for role in roles]},
severity='MEDIUM'
)
return Response({'message': 'User roles updated successfully'})
@action(detail=True, methods=['patch'])
def update_clearance(self, request, pk=None):
"""Update user clearance level"""
user = self.get_object()
clearance_level_id = request.data.get('clearance_level_id')
if not clearance_level_id:
return Response(
{'error': 'clearance_level_id is required'},
status=status.HTTP_400_BAD_REQUEST
)
try:
clearance_level = DataClassification.objects.get(id=clearance_level_id)
except DataClassification.DoesNotExist:
return Response(
{'error': 'Invalid clearance level ID'},
status=status.HTTP_400_BAD_REQUEST
)
old_clearance = user.clearance_level.name if user.clearance_level else 'None'
user.clearance_level = clearance_level
user.save()
# Log the action
AuditLog.objects.create(
user=request.user,
action_type='CLEARANCE_UPDATED',
resource_type='User',
resource_id=str(user.id),
ip_address=self._get_client_ip(request),
user_agent=request.META.get('HTTP_USER_AGENT', ''),
details={
'updated_user': user.username,
'old_clearance': old_clearance,
'new_clearance': clearance_level.name
},
severity='HIGH'
)
return Response({'message': 'User clearance level updated successfully'})
@action(detail=False, methods=['get'])
def dashboard_permissions(self, request):
"""Get dashboard permission mapping for admin"""
dashboard_components = {
'Dashboard (Overview)': {'permissions': [], 'clearance_level': None},
'Incidents': {'permissions': ['view_incident'], 'clearance_level': None},
'Monitoring': {'permissions': ['view_monitoringdashboard', 'view_alert', 'view_healthcheck'], 'clearance_level': None},
'SLA & On-Call': {'permissions': ['view_sladefinition', 'view_slainstance', 'view_oncallassignment'], 'clearance_level': None},
'Security': {'permissions': ['view_user', 'view_role', 'view_auditlog', 'view_securityevent'], 'clearance_level': 2},
'Automation': {'permissions': ['view_runbook', 'view_autoremediation', 'view_integration'], 'clearance_level': None},
'War Rooms': {'permissions': ['view_warroom', 'view_warroommessage', 'view_conferencebridge'], 'clearance_level': None},
'Analytics': {'permissions': ['view_kpimetric', 'view_anomalydetection', 'view_dashboardconfiguration'], 'clearance_level': None},
'Knowledge': {'permissions': ['view_knowledgebasearticle', 'view_postmortem', 'view_learningpattern'], 'clearance_level': None},
'Compliance': {'permissions': ['view_compliancereport', 'view_regulatoryframework', 'view_legalhold'], 'clearance_level': 3},
}
return Response(dashboard_components)
def _get_client_ip(self, request):
"""Get client IP address"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip
class MFADeviceViewSet(ModelViewSet):
"""ViewSet for MFA device management"""
serializer_class = MFADeviceSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
"""Users can only manage their own MFA devices"""
return MFADevice.objects.filter(user=self.request.user)
def perform_create(self, serializer):
"""Create MFA device for current user"""
serializer.save(user=self.request.user)
@action(detail=False, methods=['post'])
def setup_totp(self, request):
"""Setup TOTP device"""
serializer = MFASetupSerializer(data=request.data, context={'request': request})
if serializer.is_valid():
device_name = serializer.validated_data['device_name']
# Create TOTP device
device, qr_data = MFAProvider.create_totp_device(request.user, device_name)
# Generate QR code image
qr_image = MFAProvider.generate_qr_code_image(qr_data)
qr_base64 = base64.b64encode(qr_image).decode('utf-8')
return Response({
'device_id': str(device.id),
'qr_code_data': qr_data,
'qr_code_image': f"data:image/png;base64,{qr_base64}",
'message': 'TOTP device created. Scan QR code with authenticator app.'
})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@action(detail=True, methods=['post'])
def verify_totp(self, request, pk=None):
"""Verify TOTP token"""
device = self.get_object()
serializer = MFAVerificationSerializer(data=request.data)
if serializer.is_valid():
token = serializer.validated_data['token']
if MFAProvider.verify_totp_token(request.user, token, str(device.id)):
return Response({'message': 'TOTP token verified successfully'})
else:
return Response(
{'error': 'Invalid TOTP token'},
status=status.HTTP_400_BAD_REQUEST
)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@action(detail=False, methods=['post'])
def enable_mfa(self, request):
"""Enable MFA for user"""
if MFAProvider.enable_mfa_for_user(request.user):
return Response({'message': 'MFA enabled successfully'})
else:
return Response(
{'error': 'No active MFA devices found'},
status=status.HTTP_400_BAD_REQUEST
)
@action(detail=False, methods=['post'])
def disable_mfa(self, request):
"""Disable MFA for user"""
MFAProvider.disable_mfa_for_user(request.user)
return Response({'message': 'MFA disabled successfully'})
class AuditLogViewSet(ModelViewSet):
"""ViewSet for audit log viewing"""
queryset = AuditLog.objects.all()
serializer_class = AuditLogSerializer
permission_classes = [IsAdminUser]
http_method_names = ['get', 'head', 'options'] # Read-only
def get_queryset(self):
"""Filter audit logs based on user permissions"""
queryset = AuditLog.objects.all()
# Filter by date range
start_date = self.request.query_params.get('start_date')
end_date = self.request.query_params.get('end_date')
if start_date:
queryset = queryset.filter(timestamp__gte=start_date)
if end_date:
queryset = queryset.filter(timestamp__lte=end_date)
# Filter by action type
action_type = self.request.query_params.get('action_type')
if action_type:
queryset = queryset.filter(action_type=action_type)
# Filter by severity
severity = self.request.query_params.get('severity')
if severity:
queryset = queryset.filter(severity=severity)
# Filter by user
user_id = self.request.query_params.get('user_id')
if user_id:
queryset = queryset.filter(user_id=user_id)
return queryset.order_by('-timestamp')
class SSOProviderViewSet(ModelViewSet):
"""ViewSet for SSO provider management"""
queryset = SSOProvider.objects.all()
serializer_class = SSOProviderSerializer
permission_classes = [IsAdminUser]
class AccessPolicyViewSet(ModelViewSet):
"""ViewSet for access policy management"""
queryset = AccessPolicy.objects.all()
serializer_class = AccessPolicySerializer
permission_classes = [IsAdminUser]
@api_view(['POST'])
@permission_classes([])
def login_view(request):
"""User login with MFA support"""
serializer = LoginSerializer(data=request.data)
if serializer.is_valid():
username = serializer.validated_data['username']
password = serializer.validated_data['password']
mfa_token = serializer.validated_data.get('mfa_token', '')
remember_me = serializer.validated_data.get('remember_me', False)
# Authenticate user
user = authenticate(request, username=username, password=password)
if user is None:
# Log failed login attempt
AuditLog.objects.create(
action_type='LOGIN_FAILED',
ip_address=_get_client_ip(request),
user_agent=request.META.get('HTTP_USER_AGENT', ''),
details={'username': username},
severity='MEDIUM'
)
return Response(
{'error': 'Invalid credentials'},
status=status.HTTP_401_UNAUTHORIZED
)
# Check if account is locked
if user.is_account_locked():
return Response(
{'error': 'Account is locked'},
status=status.HTTP_423_LOCKED
)
# Check MFA if enabled
if user.mfa_enabled:
if not mfa_token:
return Response(
{'error': 'MFA token required', 'mfa_required': True},
status=status.HTTP_400_BAD_REQUEST
)
if not MFAProvider.verify_totp_token(user, mfa_token):
return Response(
{'error': 'Invalid MFA token'},
status=status.HTTP_401_UNAUTHORIZED
)
# Login successful
login(request, user)
# Update user login info
user.last_login_ip = _get_client_ip(request)
user.failed_login_attempts = 0
user.save(update_fields=['last_login_ip', 'failed_login_attempts'])
# Generate or get token
token, created = Token.objects.get_or_create(user=user)
# Set session expiry
if not remember_me:
request.session.set_expiry(3600) # 1 hour
# Log successful login
AuditLog.objects.create(
user=user,
action_type='LOGIN',
ip_address=_get_client_ip(request),
user_agent=request.META.get('HTTP_USER_AGENT', ''),
details={'remember_me': remember_me, 'mfa_used': user.mfa_enabled}
)
return Response({
'token': token.key,
'user': UserSerializer(user).data,
'message': 'Login successful'
})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def logout_view(request):
"""User logout"""
# Log logout
AuditLog.objects.create(
user=request.user,
action_type='LOGOUT',
ip_address=_get_client_ip(request),
user_agent=request.META.get('HTTP_USER_AGENT', '')
)
# Delete token
try:
request.user.auth_token.delete()
except:
pass
logout(request)
return Response({'message': 'Logout successful'})
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def user_profile(request):
"""Get current user profile"""
serializer = UserProfileSerializer(request.user)
return Response(serializer.data)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def change_password(request):
"""Change user password"""
serializer = PasswordChangeSerializer(data=request.data)
if serializer.is_valid():
user = request.user
current_password = serializer.validated_data['current_password']
new_password = serializer.validated_data['new_password']
# Verify current password
if not user.check_password(current_password):
return Response(
{'error': 'Current password is incorrect'},
status=status.HTTP_400_BAD_REQUEST
)
# Set new password
user.set_password(new_password)
user.save()
# Log password change
AuditLog.objects.create(
user=user,
action_type='PASSWORD_CHANGE',
ip_address=_get_client_ip(request),
user_agent=request.META.get('HTTP_USER_AGENT', ''),
severity='MEDIUM'
)
return Response({'message': 'Password changed successfully'})
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def mfa_status(request):
"""Get MFA status for current user"""
devices = MFAProvider.get_user_mfa_devices(request.user)
return Response({
'mfa_enabled': request.user.mfa_enabled,
'devices': devices,
'device_count': len(devices)
})
def _get_client_ip(request):
"""Get client IP address from request"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0]
else:
ip = request.META.get('REMOTE_ADDR')
return ip

View File

@@ -0,0 +1,374 @@
"""
Zero Trust API Views
Provides endpoints for device registration, risk assessment, and Zero Trust management
"""
import logging
from rest_framework import status, generics, permissions
from rest_framework.decorators import api_view, permission_classes, action
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from django.utils import timezone
from django.db import models
from ..models import (
DevicePosture, GeolocationRule, RiskAssessment,
AdaptiveAuthentication, UserBehaviorProfile
)
from ..serializers.zero_trust import (
DevicePostureSerializer, GeolocationRuleSerializer,
RiskAssessmentSerializer, AdaptiveAuthenticationSerializer,
UserBehaviorProfileSerializer, DeviceRegistrationSerializer,
RiskAssessmentRequestSerializer
)
from ..services.zero_trust import zero_trust_service
logger = logging.getLogger(__name__)
class DevicePostureViewSet(ModelViewSet):
"""ViewSet for device posture management"""
queryset = DevicePosture.objects.all()
serializer_class = DevicePostureSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
"""Users can only see their own devices"""
return DevicePosture.objects.filter(user=self.request.user)
def perform_create(self, serializer):
"""Create device posture for current user"""
serializer.save(user=self.request.user)
@action(detail=True, methods=['post'])
def update_posture(self, request, pk=None):
"""Update device posture information"""
device = self.get_object()
# Update device posture data
device_data = request.data
for field, value in device_data.items():
if hasattr(device, field) and field not in ['id', 'user', 'created_at']:
setattr(device, field, value)
# Recalculate risk score and trust level
device.risk_score = device.calculate_risk_score()
device.update_trust_level()
device.save()
return Response({
'message': 'Device posture updated',
'risk_score': device.risk_score,
'trust_level': device.trust_level,
'is_compliant': device.is_compliant
})
@action(detail=False, methods=['post'])
def register_device(self, request):
"""Register a new device"""
serializer = DeviceRegistrationSerializer(data=request.data)
if serializer.is_valid():
try:
device = zero_trust_service.register_device(
request.user,
serializer.validated_data
)
return Response({
'success': True,
'device_id': device.device_id,
'trust_level': device.trust_level,
'risk_score': device.risk_score,
'is_compliant': device.is_compliant,
'message': 'Device registered successfully'
}, status=status.HTTP_201_CREATED)
except Exception as e:
logger.error(f"Device registration failed: {e}")
return Response({
'error': 'Device registration failed',
'details': str(e)
}, status=status.HTTP_400_BAD_REQUEST)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
class GeolocationRuleViewSet(ModelViewSet):
"""ViewSet for geolocation rule management"""
queryset = GeolocationRule.objects.all()
serializer_class = GeolocationRuleSerializer
permission_classes = [IsAdminUser]
@action(detail=True, methods=['post'])
def test_rule(self, request, pk=None):
"""Test geolocation rule against provided location data"""
rule = self.get_object()
location_data = request.data
result = rule.evaluate_location(**location_data)
return Response({
'rule_name': rule.name,
'rule_type': rule.rule_type,
'test_result': result,
'location_data': location_data
})
class RiskAssessmentViewSet(ModelViewSet):
"""ViewSet for risk assessment management"""
queryset = RiskAssessment.objects.all()
serializer_class = RiskAssessmentSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
"""Users can only see their own risk assessments"""
return RiskAssessment.objects.filter(user=self.request.user)
@action(detail=False, methods=['post'])
def assess_access(self, request):
"""Perform risk assessment for access request"""
serializer = RiskAssessmentRequestSerializer(data=request.data)
if serializer.is_valid():
try:
# Collect request context
request_context = {
'ip_address': self._get_client_ip(request),
'user_agent': request.META.get('HTTP_USER_AGENT', ''),
'device_id': request.headers.get('X-Device-ID'),
'timestamp': timezone.now(),
**serializer.validated_data
}
# Perform assessment
assessment_result = zero_trust_service.assess_access_request(
request.user,
request_context
)
return Response(assessment_result)
except Exception as e:
logger.error(f"Risk assessment failed: {e}")
return Response({
'error': 'Risk assessment failed',
'details': str(e)
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
@action(detail=False, methods=['get'])
def my_risk_profile(self, request):
"""Get user's current risk profile"""
try:
# Get latest risk assessment
latest_assessment = RiskAssessment.objects.filter(
user=request.user
).order_by('-assessed_at').first()
# Get behavior profile
try:
behavior_profile = UserBehaviorProfile.objects.get(user=request.user)
except UserBehaviorProfile.DoesNotExist:
behavior_profile = None
# Get device postures
devices = DevicePosture.objects.filter(
user=request.user,
is_active=True
).order_by('-last_seen')
return Response({
'latest_assessment': RiskAssessmentSerializer(latest_assessment).data if latest_assessment else None,
'behavior_profile': UserBehaviorProfileSerializer(behavior_profile).data if behavior_profile else None,
'devices': DevicePostureSerializer(devices, many=True).data,
'risk_summary': {
'total_devices': devices.count(),
'trusted_devices': devices.filter(is_trusted=True).count(),
'compliant_devices': devices.filter(is_compliant=True).count(),
'average_risk_score': devices.aggregate(avg_risk=models.Avg('risk_score'))['avg_risk'] or 0
}
})
except Exception as e:
logger.error(f"Failed to get risk profile: {e}")
return Response({
'error': 'Failed to get risk profile',
'details': str(e)
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def _get_client_ip(self, request):
"""Get client IP address from request"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0].strip()
else:
ip = request.META.get('REMOTE_ADDR')
return ip
class AdaptiveAuthenticationViewSet(ModelViewSet):
"""ViewSet for adaptive authentication management"""
queryset = AdaptiveAuthentication.objects.all()
serializer_class = AdaptiveAuthenticationSerializer
permission_classes = [IsAdminUser]
@action(detail=True, methods=['post'])
def test_auth_requirements(self, request, pk=None):
"""Test authentication requirements for given risk score"""
adaptive_auth = self.get_object()
risk_score = request.data.get('risk_score', 0)
required_methods = adaptive_auth.get_required_auth_methods(risk_score)
return Response({
'risk_score': risk_score,
'required_auth_methods': required_methods,
'adaptive_auth_name': adaptive_auth.name
})
class UserBehaviorProfileViewSet(ModelViewSet):
"""ViewSet for user behavior profile management"""
queryset = UserBehaviorProfile.objects.all()
serializer_class = UserBehaviorProfileSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
"""Users can only see their own behavior profile"""
return UserBehaviorProfile.objects.filter(user=self.request.user)
@action(detail=True, methods=['post'])
def calculate_anomaly(self, request, pk=None):
"""Calculate anomaly score for current behavior"""
profile = self.get_object()
current_behavior = request.data
anomaly_score = profile.calculate_anomaly_score(current_behavior)
return Response({
'anomaly_score': anomaly_score,
'is_anomalous': anomaly_score > 0.7, # Threshold
'current_behavior': current_behavior
})
@api_view(['GET'])
@permission_classes([IsAuthenticated])
def zero_trust_status(request):
"""Get Zero Trust system status and configuration"""
try:
# Get user's Zero Trust status
user_devices = DevicePosture.objects.filter(user=request.user, is_active=True)
latest_assessment = RiskAssessment.objects.filter(user=request.user).order_by('-assessed_at').first()
# Get system configuration
adaptive_auth = AdaptiveAuthentication.objects.filter(is_active=True).first()
geo_rules = GeolocationRule.objects.filter(is_active=True).count()
return Response({
'zero_trust_enabled': True,
'user_status': {
'registered_devices': user_devices.count(),
'trusted_devices': user_devices.filter(is_trusted=True).count(),
'latest_risk_level': latest_assessment.risk_level if latest_assessment else 'UNKNOWN',
'latest_risk_score': latest_assessment.overall_risk_score if latest_assessment else 0
},
'system_configuration': {
'adaptive_auth_enabled': adaptive_auth is not None,
'geolocation_rules_count': geo_rules,
'behavioral_analysis_enabled': True,
'device_posture_enabled': True
},
'recommendations': _get_security_recommendations(request.user, user_devices, latest_assessment)
})
except Exception as e:
logger.error(f"Failed to get Zero Trust status: {e}")
return Response({
'error': 'Failed to get Zero Trust status',
'details': str(e)
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
@api_view(['POST'])
@permission_classes([IsAuthenticated])
def perform_risk_assessment(request):
"""Perform a comprehensive risk assessment"""
try:
# Collect request context
request_context = {
'ip_address': _get_client_ip(request),
'user_agent': request.META.get('HTTP_USER_AGENT', ''),
'device_id': request.headers.get('X-Device-ID'),
'timestamp': timezone.now(),
**request.data
}
# Perform assessment
assessment_result = zero_trust_service.assess_access_request(
request.user,
request_context
)
return Response(assessment_result)
except Exception as e:
logger.error(f"Risk assessment failed: {e}")
return Response({
'error': 'Risk assessment failed',
'details': str(e)
}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
def _get_security_recommendations(user, devices, latest_assessment):
"""Get security recommendations based on user's current status"""
recommendations = []
# Device recommendations
if devices.count() == 0:
recommendations.append({
'type': 'device',
'priority': 'high',
'message': 'Register your device for enhanced security',
'action': 'register_device'
})
untrusted_devices = devices.filter(is_trusted=False)
if untrusted_devices.exists():
recommendations.append({
'type': 'device',
'priority': 'medium',
'message': f'{untrusted_devices.count()} untrusted devices detected',
'action': 'improve_device_security'
})
# Risk recommendations
if latest_assessment and latest_assessment.risk_level in ['HIGH', 'CRITICAL']:
recommendations.append({
'type': 'risk',
'priority': 'high',
'message': f'High risk level detected: {latest_assessment.risk_level}',
'action': 'review_security_settings'
})
# MFA recommendations
if not user.mfa_enabled:
recommendations.append({
'type': 'authentication',
'priority': 'medium',
'message': 'Enable Multi-Factor Authentication',
'action': 'enable_mfa'
})
return recommendations
def _get_client_ip(request):
"""Get client IP address from request"""
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
ip = x_forwarded_for.split(',')[0].strip()
else:
ip = request.META.get('REMOTE_ADDR')
return ip