U
    hU                  	   @   s`  d Z ddlZddlZddlZddlZddlmZmZ ddlmZm	Z	m
Z
mZmZ ddlmZ ddlmZ ddlmZ ddlmZ dd	lmZ dd
lmZ ddlmZmZmZmZ ddlmZ ddl m!Z! ddl"m#Z# e Z$ee%dddZ&ee%dddZ'e%e(dddZ)e(dddZ*eee%ef dddZ+ee%ef ee%ef dddZ,e
e- e%e
e- e
e% ee%dd d!Z.d=e
e- e%e
e- e
e% ee-e(d#d$d%Z/d>e
e- e
e- e
e e
e ee%ef d&d'd(Z0d?e
e% e%d)d*d+Z1e%dd,d-Z2ee%ef ee%ef d.d/d0Z3d@e-e-ee%ef d2d3d4Z4dAe-e-d6d7d8Z5dBe-e	ee%ef  d:d;d<Z6dS )Ca  
Adtlas Activities Utilities

This module contains utility functions and helper classes for the activities app,
providing common functionality for activity logging, data processing, and
analysis.

Features:
    - IP address and user agent extraction
    - Activity filtering and validation
    - Data export utilities
    - Performance monitoring
    - Security helpers
    - Analytics calculations

Author: Adtlas Development Team
Version: 1.0.0
Last Updated: 2025-01-27
    N)datetime	timedelta)DictListOptionalUnionAny)urlparse)settings)cache)timezone)get_user_model)ContentType)QCountAvgSum)HttpRequest)slugify)ValidationError)requestreturnc                 C   sd   dddddddg}|D ]>}| j |}|rd|krD|dd	  }t|r|  S q| j dd
S )a(  
    Extract the client IP address from the request.
    
    This function handles various proxy configurations and
    header formats to accurately determine the client's IP address.
    
    Args:
        request: Django HttpRequest object
    
    Returns:
        str: Client IP address
    HTTP_X_FORWARDED_FORZHTTP_X_REAL_IPZHTTP_X_FORWARDEDZHTTP_X_CLUSTER_CLIENT_IPZHTTP_FORWARDED_FORZHTTP_FORWARDEDREMOTE_ADDR,r   z	127.0.0.1)METAgetsplitstripis_valid_ip)r   Z
ip_headersheaderip r"   0/var/www/html/Focus/src/apps/activities/utils.pyget_client_ip,   s     

r$   c                 C   s"   | j dd}|r|dd S dS )z
    Extract the user agent string from the request.
    
    Args:
        request: Django HttpRequest object
    
    Returns:
        str: User agent string (truncated to 500 characters)
    HTTP_USER_AGENT N  Unknown)r   r   )r   
user_agentr"   r"   r#   get_user_agentS   s    
r*   )r!   r   c                 C   s,   zt |  W dS  tk
r&   Y dS X dS )z
    Validate if a string is a valid IP address.
    
    Args:
        ip: IP address string to validate
    
    Returns:
        bool: True if valid IP address, False otherwise
    TFN)	ipaddress
ip_address
ValueError)r!   r"   r"   r#   r   c   s
    

r   )r   c              	   C   s   | j j}| j j}ttdds dS ttdddddg}||kr@dS ttd	d
ddg}| d| }||krldS t| di }t|tr|ddS t|tr|S ttdddddddg}||kS )aT  
    Determine if activities should be logged for a given model.
    
    This function checks various conditions to decide whether
    model changes should be automatically logged as activities.
    
    Args:
        model_class: Django model class
    
    Returns:
        bool: True if activities should be logged, False otherwise
    ZACTIVITIES_AUTO_LOG_ENABLEDTFZACTIVITIES_EXCLUDED_APPSsessionsadmincontenttypesauthZACTIVITIES_EXCLUDED_MODELSzactivities.activityzactivities.activitysummaryzcore.activitylog.Z_activity_loggingenabledZACTIVITIES_INCLUDED_APPSaccounts	campaignschannels	analyticsvast	playlists)	_meta	app_label
model_namegetattrr
   
isinstancedictr   bool)model_classr;   r<   Zexcluded_appsZexcluded_modelsZmodel_labelZmodel_configZincluded_appsr"   r"   r#   should_log_activityt   sD       

     rB   c                    s   t | t| | j| jt  d}| jd}|r<||d< | j	ri }ddddg}| j	
 D ]&\ }t fdd	|D s\|| < q\|r||d
< |S )z
    Extract activity context information from the request.
    
    Args:
        request: Django HttpRequest object
    
    Returns:
        dict: Context information for activity logging
    )r,   r)   methodpath	timestampHTTP_REFERERrefererpasswordtokenkeysecretc                 3   s   | ]}|   kV  qd S Nlower).0excludedrJ   r"   r#   	<genexpr>   s     z'get_activity_context.<locals>.<genexpr>query_params)r$   r*   rC   rD   r   now	isoformatr   r   GETitemsany)r   contextrG   Zsafe_paramsZexcluded_paramsvaluer"   rQ   r#   get_activity_context   s$    
	
r[   )metadatar   c                    s   t | tsi S i }ddddddddd	d
ddddg}|  D ]f\ }t fdd|D rbd| < q:t |trzt|| < q:t |trdd |D | < q:|| < q:|S )z
    Sanitize metadata to remove sensitive information.
    
    Args:
        metadata: Raw metadata dictionary
    
    Returns:
        dict: Sanitized metadata
    rH   passwdpwdrK   rJ   rI   api_keyZaccess_tokenZrefresh_tokensession_key
csrf_token
auth_tokenZprivate_keyZ
public_keyc                 3   s   | ]}|   kV  qd S rL   rM   )rO   Z	sensitiverQ   r"   r#   rR      s     z$sanitize_metadata.<locals>.<genexpr>z
[REDACTED]c                 S   s"   g | ]}t |trt|n|qS r"   )r>   r?   sanitize_metadata)rO   itemr"   r"   r#   
<listcomp>   s     z%sanitize_metadata.<locals>.<listcomp>)r>   r?   rW   rX   rc   list)r\   Z	sanitizedZsensitive_keysrZ   r"   rQ   r#   rc      s6    

           



rc   )user_idactioncontent_type_id	object_idrE   r   c              
   C   sF   |j ddd}|  d| d| d| d|  	}t|d S )aK  
    Generate a unique hash for an activity to prevent duplicates.
    
    Args:
        user_id: User ID
        action: Activity action
        content_type_id: Content type ID
        object_id: Object ID
        timestamp: Activity timestamp (rounded to minute)
    
    Returns:
        str: SHA-256 hash of the activity
    r   )secondmicrosecond:zutf-8)replacerU   hashlibsha256encode	hexdigest)rg   rh   ri   rj   rE   Zrounded_timestampZ
hash_inputr"   r"   r#   generate_activity_hash   s    $rs      )rg   rh   ri   rj   rE   window_minutesr   c                 C   s>   t | ||||}d| }t|r(dS t|d|d  dS )a  
    Check if an activity is a duplicate within a time window.
    
    Args:
        user_id: User ID
        action: Activity action
        content_type_id: Content type ID
        object_id: Object ID
        timestamp: Activity timestamp
        window_minutes: Time window in minutes to check for duplicates
    
    Returns:
        bool: True if duplicate activity exists, False otherwise
    Zactivity_hash_T<   F)rs   r   r   set)rg   rh   ri   rj   rE   ru   Zactivity_hash	cache_keyr"   r"   r#   is_duplicate_activity  s        

ry   )rg   category_id
start_dateend_dater   c              	   C   s,  ddl m} |j }| r&|j| d}|r6|j|d}|rF|j|d}|rV|j|d}| }|jdd }|jd	d }|d
kr|| d nd
}	|jd	djtddd pd
}
t|	dj
tddddd }|jd	d	d  }|	d  }|||t|	dt|
d|||dS )aV  
    Calculate activity statistics for given parameters.
    
    Args:
        user_id: User ID to filter by (optional)
        category_id: Category ID to filter by (optional)
        start_date: Start date for filtering (optional)
        end_date: End date for filtering (optional)
    
    Returns:
        dict: Activity statistics
    rt   Activity)rg   )rz   )created_at__gte)created_at__lteTis_successfulFr   d   )duration_ms__isnullduration_ms)avg_durationr   rh   count-countN
   )Zuser__isnulluserr,      )total_activitiessuccessful_activitiesfailed_activitiessuccess_rateavg_duration_mstop_actionsunique_users
unique_ips)modelsr~   objectsallfilterr   	aggregater   rf   valuesannotater   order_bydistinctround)rg   rz   r{   r|   r~   querysetr   r   r   r   r   r   r   r   r"   r"   r#   get_activity_statistics2  sR    
r   )filenamer   c           	      C   s  ddl }ddlm} | }||}dddddd	d
dddddddg}|| | dddD ]}t|j|jrv|jj	nd|
 |j|jr|jjnd|jrt|jnd|jpd|jpd|jpd|jrdnd|jpd|jpd|j |jrt|jndg}|| q\| S )z
    Export activities to CSV format.
    
    Args:
        queryset: Activity queryset to export
        filename: Optional filename for the export
    
    Returns:
        str: CSV content as string
    r   N)StringIOIDUserActionDescriptionCategoryzContent Typez	Object IDz
IP Addressz
User AgentzIs SuccessfulzDuration (ms)zError Messagez
Created AtMetadatar   categorycontent_typeSystemr&   YesNoz{})csvior   writerwriterowselect_relatedstridr   emailget_action_displaydescriptionr   namer   rj   r,   r)   r   r   error_message
created_atrU   r\   jsondumpsgetvalue)	r   r   r   r   outputr   headersactivityrowr"   r"   r#   export_activities_to_csvw  sL    
          
r   c                 C   s  g }|  dddD ]}t|j|jr,|jjnd|jr<|jjnd|jrN|j ndd|j| |j|j	rp|j	jnd|j	r|j	j
nd|j	r|j	jndd|jr|jjnd|jr|jjnd|jr|jjndd|j|j|j|j|j|j|j|j |j d}|| qtj|d	d
dS )z
    Export activities to JSON format.
    
    Args:
        queryset: Activity queryset to export
    
    Returns:
        str: JSON content as string
    r   r   r   N)r   r   	full_name)r   r   code)r   r;   model)r   r   rh   Zaction_displayr   r   r   rj   r,   r)   r   r   r   r\   r   
updated_atr   F)indentensure_ascii)r   r   r   r   r   get_full_namerh   r   r   r   r   r   r   r;   r   rj   r,   r)   r   r   r   r\   r   rU   r   appendr   r   )r   
activitiesr   activity_datar"   r"   r#   export_activities_to_json  s<    
r   )datar   c              	   C   s  i }|  dsd|d< |  ds(d|d< |  dd}|rLtd|sLd|d< |  d}|rjt|sjd	|d< |  d
}|dk	rz.t|}|dk rd|d
< n|dkrd|d
< W n  ttfk
r   d|d
< Y nX |  d}|dk	rt|tsd|d< n t	
|}t|dkrd|d< |r"t||  }|r<t||d< | dr\|d dd |d< | dr||d dd |d< | dr|d dd |d< |S )z
    Validate activity data before logging.
    
    Args:
        data: Activity data dictionary
    
    Returns:
        dict: Validated and cleaned activity data
    
    Raises:
        ValidationError: If data is invalid
    rh   zAction is requiredr   zDescription is requiredr&   z	^[a-z_]+$z:Action must contain only lowercase letters and underscoresr,   zInvalid IP address formatr   Nr   zDuration cannot be negativei6 zDuration cannot exceed 1 hourzDuration must be a valid numberr\   zMetadata must be a dictionaryi'  z Metadata is too large (max 10KB)i  r   r)   r'   )r   rematchr   floatr-   	TypeErrorr>   r?   r   r   lenr   copyrc   )r   errorsrh   r,   r   r\   Zmetadata_strcleaned_datar"   r"   r#   validate_activity_data  sP    









r      )rg   daysr   c                 C   sN  ddl m} t }|t|d }|jj| ||d}| }|jdd }|jdd }t|	dd	j
td
dd}	t|	dj
td
dddd }
g }t|D ]>}|t|d }|j| d }||  |d qt|jddid	dj
td
dd}| |||||dkr>|| d nd|	|
||d
S )z
    Get activity summary for a specific user.
    
    Args:
        user_id: User ID
        days: Number of days to include in summary
    
    Returns:
        dict: User activity summary
    rt   r}   r   )rg   r   r   Tr   Fcategory__namecategory__coder   r   r   rh   Nr   )created_at__date)dater   hourzEXTRACT(hour FROM created_at))selectr   r   )
rg   period_daysr   r   r   r   category_breakdownaction_breakdowndaily_activitieshourly_activities)r   r~   r   rT   r   r   r   r   rf   r   r   r   r   ranger   r   rU   extra)rg   r   r~   r|   r{   r   r   r   r   r   r   r   idayZday_activitiesr   r"   r"   r#   get_user_activity_summary&  st     


r   Z   )days_to_keepr   c                 C   s8   ddl m} t t| d }|jj|d \}}|S )z
    Clean up old activities based on retention policy.
    
    Args:
        days_to_keep: Number of days to keep activities
    
    Returns:
        int: Number of activities deleted
    rt   r}   r   )created_at__lt)r   r~   r   rT   r   r   r   delete)r   r~   cutoff_datedeleted_count_r"   r"   r#   cleanup_old_activitiesq  s    
r      )hoursr   c           	      C   s  ddl m} g }t }|t| d }|jjd||ddjt	ddjd	d
}|D ]6}|
dd|d |d d|d  d|d  d qV|jj||ddjt	dt	ddddjdd	d}|D ]H}|
dd|d |d |d d|d  d|d  d|d  dd q|S ) z
    Get security-related activity alerts.
    
    Args:
        hours: Number of hours to check for alerts
    
    Returns:
        list: List of security alerts
    rt   r}   )r   login_failed)rh   r   r   r,   r   r      )
count__gteZmultiple_failed_loginshighr   z Multiple failed login attempts (z
) from IP )typeseverityr,   r   message)r   r   r   T)r   )r   
user_countr   )r   Zuser_count__gteZsuspicious_activitymediumr   zSuspicious activity from IP z: z activities across z users)r   r   r,   Zactivity_countr   r   )r   r~   r   rT   r   r   r   r   r   r   r   )	r   r~   Zalertsend_time
start_timeZfailed_loginsZfailed_loginZsuspicious_ipsZsuspicious_ipr"   r"   r#   get_security_alerts  sZ    

	
 "
	r   )rt   )NNNN)N)r   )r   )r   )7__doc__r   r   ro   r+   r   r   typingr   r   r   r   r   urllib.parser	   django.confr
   django.core.cacher   django.utilsr   django.contrib.authr   "django.contrib.contenttypes.modelsr   django.db.modelsr   r   r   r   django.httpr   django.utils.textr   django.core.exceptionsr   r   r   r$   r*   r@   r   rB   r[   rc   intrs   ry   r   r   r   r   r   r   r   r"   r"   r"   r#   <module>   sj   '8& $   
   "     
E10 NK