Updates
This commit is contained in:
468
ETB-API/security/Documentations/API_DOCUMENTATION.md
Normal file
468
ETB-API/security/Documentations/API_DOCUMENTATION.md
Normal 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.
|
||||
372
ETB-API/security/Documentations/ZERO_TRUST_ARCHITECTURE.md
Normal file
372
ETB-API/security/Documentations/ZERO_TRUST_ARCHITECTURE.md
Normal 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.
|
||||
0
ETB-API/security/__init__.py
Normal file
0
ETB-API/security/__init__.py
Normal file
BIN
ETB-API/security/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
ETB-API/security/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
ETB-API/security/__pycache__/admin.cpython-312.pyc
Normal file
BIN
ETB-API/security/__pycache__/admin.cpython-312.pyc
Normal file
Binary file not shown.
BIN
ETB-API/security/__pycache__/apps.cpython-312.pyc
Normal file
BIN
ETB-API/security/__pycache__/apps.cpython-312.pyc
Normal file
Binary file not shown.
BIN
ETB-API/security/__pycache__/enterprise_security.cpython-312.pyc
Normal file
BIN
ETB-API/security/__pycache__/enterprise_security.cpython-312.pyc
Normal file
Binary file not shown.
BIN
ETB-API/security/__pycache__/models.cpython-312.pyc
Normal file
BIN
ETB-API/security/__pycache__/models.cpython-312.pyc
Normal file
Binary file not shown.
BIN
ETB-API/security/__pycache__/urls.cpython-312.pyc
Normal file
BIN
ETB-API/security/__pycache__/urls.cpython-312.pyc
Normal file
Binary file not shown.
326
ETB-API/security/admin.py
Normal file
326
ETB-API/security/admin.py
Normal 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
6
ETB-API/security/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class SecurityConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'security'
|
||||
9
ETB-API/security/authentication/__init__.py
Normal file
9
ETB-API/security/authentication/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
||||
# Authentication module for SSO and MFA
|
||||
from .sso import SSOAuthentication, SAMLAuthentication, OAuth2Authentication, LDAPAuthentication
|
||||
|
||||
__all__ = [
|
||||
'SSOAuthentication',
|
||||
'SAMLAuthentication',
|
||||
'OAuth2Authentication',
|
||||
'LDAPAuthentication'
|
||||
]
|
||||
Binary file not shown.
BIN
ETB-API/security/authentication/__pycache__/sso.cpython-312.pyc
Normal file
BIN
ETB-API/security/authentication/__pycache__/sso.cpython-312.pyc
Normal file
Binary file not shown.
324
ETB-API/security/authentication/sso.py
Normal file
324
ETB-API/security/authentication/sso.py
Normal 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
|
||||
829
ETB-API/security/enterprise_security.py
Normal file
829
ETB-API/security/enterprise_security.py
Normal 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
|
||||
1
ETB-API/security/management/__init__.py
Normal file
1
ETB-API/security/management/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Management commands for security app
|
||||
BIN
ETB-API/security/management/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
ETB-API/security/management/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
1
ETB-API/security/management/commands/__init__.py
Normal file
1
ETB-API/security/management/commands/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Management commands
|
||||
Binary file not shown.
Binary file not shown.
201
ETB-API/security/management/commands/setup_security.py
Normal file
201
ETB-API/security/management/commands/setup_security.py
Normal 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')
|
||||
1
ETB-API/security/mfa/__init__.py
Normal file
1
ETB-API/security/mfa/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Multi-Factor Authentication module
|
||||
BIN
ETB-API/security/mfa/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
ETB-API/security/mfa/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
ETB-API/security/mfa/__pycache__/totp.cpython-312.pyc
Normal file
BIN
ETB-API/security/mfa/__pycache__/totp.cpython-312.pyc
Normal file
Binary file not shown.
235
ETB-API/security/mfa/totp.py
Normal file
235
ETB-API/security/mfa/totp.py
Normal 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
|
||||
]
|
||||
Binary file not shown.
289
ETB-API/security/middleware/zero_trust.py
Normal file
289
ETB-API/security/middleware/zero_trust.py
Normal 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
|
||||
168
ETB-API/security/migrations/0001_initial.py
Normal file
168
ETB-API/security/migrations/0001_initial.py
Normal 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')},
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -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),
|
||||
),
|
||||
]
|
||||
@@ -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'),
|
||||
),
|
||||
]
|
||||
34
ETB-API/security/migrations/0004_securityevent.py
Normal file
34
ETB-API/security/migrations/0004_securityevent.py
Normal 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')],
|
||||
},
|
||||
),
|
||||
]
|
||||
0
ETB-API/security/migrations/__init__.py
Normal file
0
ETB-API/security/migrations/__init__.py
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
ETB-API/security/migrations/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
ETB-API/security/migrations/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
1077
ETB-API/security/models.py
Normal file
1077
ETB-API/security/models.py
Normal file
File diff suppressed because it is too large
Load Diff
1
ETB-API/security/serializers/__init__.py
Normal file
1
ETB-API/security/serializers/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Serializers for security API endpoints
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
277
ETB-API/security/serializers/security.py
Normal file
277
ETB-API/security/serializers/security.py
Normal 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'
|
||||
]
|
||||
203
ETB-API/security/serializers/zero_trust.py
Normal file
203
ETB-API/security/serializers/zero_trust.py
Normal 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)
|
||||
BIN
ETB-API/security/services/__pycache__/zero_trust.cpython-312.pyc
Normal file
BIN
ETB-API/security/services/__pycache__/zero_trust.cpython-312.pyc
Normal file
Binary file not shown.
503
ETB-API/security/services/zero_trust.py
Normal file
503
ETB-API/security/services/zero_trust.py
Normal 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()
|
||||
3
ETB-API/security/tests.py
Normal file
3
ETB-API/security/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
52
ETB-API/security/urls.py
Normal file
52
ETB-API/security/urls.py
Normal 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'),
|
||||
]
|
||||
3
ETB-API/security/views.py
Normal file
3
ETB-API/security/views.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.shortcuts import render
|
||||
|
||||
# Create your views here.
|
||||
1
ETB-API/security/views/__init__.py
Normal file
1
ETB-API/security/views/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Views for security API endpoints
|
||||
BIN
ETB-API/security/views/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
ETB-API/security/views/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
ETB-API/security/views/__pycache__/security.cpython-312.pyc
Normal file
BIN
ETB-API/security/views/__pycache__/security.cpython-312.pyc
Normal file
Binary file not shown.
BIN
ETB-API/security/views/__pycache__/zero_trust.cpython-312.pyc
Normal file
BIN
ETB-API/security/views/__pycache__/zero_trust.cpython-312.pyc
Normal file
Binary file not shown.
533
ETB-API/security/views/security.py
Normal file
533
ETB-API/security/views/security.py
Normal 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
|
||||
374
ETB-API/security/views/zero_trust.py
Normal file
374
ETB-API/security/views/zero_trust.py
Normal 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
|
||||
Reference in New Issue
Block a user