
    `gj7                    8   d Z ddlm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
 ddlmZmZmZ ddlmZ  ej"                  e      Z e       dz  Z e       d	z  Zd
Z ed       G d d             Z ed       G d d             Z ed       G d d             Z G d d      Z ee      Z ee      Zd$dZd%dZd&dZd'dZ 	 d(	 	 	 	 	 	 	 	 	 d)dZ!d*dZ"d+dZ#d,dZ$d,dZ%d-dZ&d.dZ'd/d Z(d0d!Z)d1d"Z*ed#k(  rddl+Z+ e+jX                   e*              yy)2a=  
Feishu document comment access-control rules.

3-tier rule resolution: exact doc > wildcard "*" > top-level > code defaults.
Each field (enabled/policy/allow_from) falls back independently.
Config: ~/.hermes/feishu_comment_rules.json (mtime-cached, hot-reload).
Pairing store: ~/.hermes/feishu_comment_pairing.json.
    )annotationsN)	dataclassfield)Path)AnyDictOptional)get_hermes_homezfeishu_comment_rules.jsonzfeishu_comment_pairing.json)	allowlistpairingT)frozenc                  <    e Zd ZU dZdZded<   dZded<   dZded<   y)	CommentDocumentRulez=Per-document rule.  ``None`` means 'inherit from lower tier'.NzOptional[bool]enabledzOptional[str]policyOptional[frozenset]
allow_from)__name__
__module____qualname____doc__r   __annotations__r   r        `/root/.hermes/venv/lib/python3.12/site-packages/plugins/platforms/feishu/feishu_comment_rules.pyr   r   *   s$    G"G^" FM &*J#*r   r   c                  f    e Zd ZU dZdZded<   dZded<    ee      Z	d	ed
<    ee
      Zded<   y)CommentsConfigz Top-level comment access config.Tboolr   r   strr   )default_factory	frozensetr   zDict[str, CommentDocumentRule]	documentsN)r   r   r   r   r   r   r   r   r!   r   dictr"   r   r   r   r   r   2   s8    *GTFC!)<J	<05d0KI-Kr   r   c                  :    e Zd ZU dZded<   ded<   ded<   ded<   y	)
ResolvedCommentRulez2Fully resolved rule after field-by-field fallback.r   r   r   r   r!   r   match_sourceN)r   r   r   r   r   r   r   r   r%   r%   ;   s    <MKr   r%   c                       e Zd ZdZddZddZy)_MtimeCachezOGeneric mtime-based file cache.  ``stat()`` per access, re-read only on change.c                .    || _         d| _        d | _        y )N        )_path_mtime_data)selfpaths     r   __init__z_MtimeCache.__init__K   s    
 %)
r   c                (   	 | j                   j                         }|j                  }|| j                  k(  r| j
                  | j
                  S 	 t        | j                   dd      5 }t        j                  |      }d d d        t        t              si }|| _        || _        |S # t        $ r d| _        i | _        i cY S w xY w# 1 sw Y   JxY w# t        j                  t        f$ r% t        j                  d| j                          i }Y xw xY w)Nr*   rutf-8encodingz4[Feishu-Rules] Failed to read %s, using empty config)r+   statst_mtimeFileNotFoundErrorr,   r-   openjsonload
isinstancer#   JSONDecodeErrorOSErrorloggerwarning)r.   stmtimefdatas        r   r;   z_MtimeCache.loadP   s    	"BKKE DKKDJJ$:::	djj#8 $Ayy|$dD)
 
' ! 	DKDJI	$ $ $$g. 	NNQSWS]S]^D	s;   &B( C (C>C (CCCC ;DDN)r/   r   )returnr#   )r   r   r   r   r0   r;   r   r   r   r(   r(   H   s    Y*
r   r(   c                Z    | yt        | t        t        f      rt        d | D              S y)zDParse a list of strings into a frozenset; return None if key absent.Nc              3     K   | ]7  }t        |      j                         st        |      j                          9 y w)N)r   strip).0us     r   	<genexpr>z#_parse_frozenset.<locals>.<genexpr>w   s&     GAAQGs   ??)r<   listtupler!   )raws    r   _parse_frozensetrO   r   s,    
{#e}%GGGGr   c                   | j                  d      }|t        |      }| j                  d      }|1t        |      j                         j	                         }|t
        vrd }t        | j                  d            }t        |||      S )Nr   r   r   )r   r   r   )getr   r   rH   lower_VALID_POLICIESrO   r   )rN   r   r   r   s       r   _parse_document_rulerT   {   s}    ggi Gw-WWXFV""$**,(F!#'',"78Jwv*UUr   c            	        t         j                         } | s
t               S i }| j                  di       }t	        |t
              r@|j                         D ]-  \  }}t	        |t
              st        |      |t        |      <   / t        | j                  dd            j                         j                         }|t        vrd}t        | j                  dd      |t        | j                  d            xs
 t               |      S )z,Load comment rules from disk (mtime-cached).r"   r   r   r   Tr   )r   r   r   r"   )_rules_cacher;   r   rQ   r<   r#   itemsrT   r   rH   rR   rS   rO   r!   )rN   r"   raw_docskeyrule_rawr   s         r   load_configr[      s    



C02Iww{B'H(D!%^^- 	EMC(D)&:8&D	#c(#	E 9-.446<<>F_$	4(#CGGL$9:Iik	 r   c                :    t        d | j                  D              S )z3Check if any document rule key starts with 'wiki:'.c              3  >   K   | ]  }|j                  d         yw)wiki:N)
startswith)rI   ks     r   rK   z has_wiki_keys.<locals>.<genexpr>   s     <q||G$<s   )anyr"   )cfgs    r   has_wiki_keysrc      s    <cmm<<<r   c                    | d| } j                   j                  |      }d| }|'|r%d| } j                   j                  |      }d| } j                   j                  d      }g |j                  ||f       |j                  |df       d fd}	 |	d      \  }
} |	d      \  }} |	d	      \  }}d
dddt        ||gfd      }t	        |
|||      S )uW   Resolve effective rule: exact doc → wiki key → wildcard → top-level → defaults.:zexact:r^   *wildcardc                \    D ]  \  }}t        ||       }|||fc S  t        |       dfS )Ntop)getattr)
field_namelayersourcevalrb   layerss       r   _pickzresolve_rule.<locals>._pick   sE    # 	#ME6%,CF{"	# sJ'..r   r   r   r   r         )exactrg   ri   c                L    j                  | j                  d      d   d      S )Nre   r      )rQ   split)spriority_orders    r   <lambda>zresolve_rule.<locals>.<lambda>   s     n((a!< r   )rY   )r   r   r   r&   )rk   r   )r"   rQ   appendminr%   )rb   	file_type
file_token
wiki_token	exact_keyrs   	exact_srcwiki_keyrg   rp   r   en_srcr   pol_srcr   _best_srcro   rx   s   `                @@r   resolve_ruler      s    +Qzl+IMMi(E$I}:,'!!(+XJ'	}}  %HFui()x,-/ I&OGVHoOFG,'MJ  !a:N	<H
 	 r   c                    t         j                         } | j                  di       }t        |t              rt        |j                               S t        |t              r|D ch c]  }|st        |       c}S t               S c c}w )z4Return set of approved user open_ids (mtime-cached).approved)	_pairing_cacher;   rQ   r<   r#   setkeysrL   r   )rD   r   rJ   s      r   _load_pairing_approvedr      sh     Dxx
B'H(D!8==?##(D! (.1AA..5L /s   $B,Bc                >   t         j                  j                  dd       t         j                  d      }t	        |dd      5 }t        j                  | |dd	       d d d        |j                  t                d
t        _	        d t        _
        y # 1 sw Y   5xY w)NT)parentsexist_okz.tmpwr3   r4   rr   F)indentensure_asciir*   )PAIRING_FILEparentmkdirwith_suffixr9   r:   dumpreplacer   r,   r-   )rD   tmprC   s      r   _save_pairingr      s|    dT:

"
"6
*C	c3	) 9Q		$!%89KKNN9 9s   BBc                    t         j                         }|j                  di       }t        |t              si }| |v rydt        j
                         i|| <   ||d<   t        |       y)zEAdd a user to the pairing-approved list. Returns True if newly added.r   Fapproved_atT)r   r;   rQ   r<   r#   timer   user_open_idrD   r   s      r   pairing_addr      sd     Dxx
B'Hh%x+TYY[9H\D$r   c                    t         j                         }|j                  di       }t        |t              sy| |vry|| = ||d<   t        |       y)zFRemove a user from the pairing-approved list. Returns True if removed.r   FT)r   r;   rQ   r<   r#   r   r   s      r   pairing_remover     sU     Dxx
B'Hh%8#D$r   c                     t         j                         } | j                  di       }t        |t              rt	        |      S i S )z=Return the approved dict  {user_open_id: {approved_at: ...}}.r   )r   r;   rQ   r<   r#   )rD   r   s     r   pairing_listr     s8     Dxx
B'H'$74>?R?r   c                X    || j                   v ry| j                  dk(  r|t               v S y)z5Check if user passes the resolved rule's policy gate.Tr   F)r   r   r   )ruler   s     r   is_user_allowedr     s/    t&{{i5777r   c            	        t               } t        dt                t        dt        j                                 t        dt                t        dt        j                                 t                t        d       t        d| j
                          t        d| j                          t        d| j                  rt        | j                        nd        t                | j                  rt        d	t        | j                         d
       t        | j                  j                               D ]  \  }}g }|j
                  |j                  d|j
                          |j                  |j                  d|j                          |j                  '|j                  dt        |j                                t        d| d|rdj                  |      nd         nt        d       t                t               }t        dt        |       d
       t        |j                               D ])  \  }}|j                  dd      }t        d| d| d       + y )NzRules file: z
  exists: zPairing file: z
Top-level:z  enabled:    z  policy:     z  allow_from: []zDocument rules (z):zenabled=zpolicy=zallow_from=z  [z] z, u   (empty — inherits all)zDocument rules: (none)zPairing approved (r   r     z  (approved_at=))r[   print
RULES_FILEexistsr   r   r   r   sortedr"   lenrW   rz   joinr   rQ   )rb   rY   r   partsr   uidmetatss           r   _print_statusr   *  s   
-C	L
%&	Jz((*+
,-	N<.
)*	J|**,-
./	G	,	N3;;-
()	N3::,
'(	NS^^6#..1N
OP	G
}} S]]!3 4B78 3 3 56 	\ICE||'x~67{{&wt{{m45*{6$//+B*CDECuB5tyy/>XYZ[	\ 	&'	G~H	s8}oR
01HNN,- .	TXXmQ'3%rd!,-.r   c                   t               }| j                  dd      }t        |      dk7  rt        d|  d       y |\  }}t	        |||      }t        ||      }t        d|         t        d|        t        d       t        d	|j                          t        d
|j                          t        d|j                  rt        |j                        nd        t        d|j                          t        d|rdnd        y )Nre   rq   rr   z2Error: doc_key must be 'fileType:fileToken', got ''zDocument:     zUser:         zResolved rule:z  enabled:      z  policy:       z  allow_from:   r   z  match_source: zResult:       ALLOWEDDENIED)r[   rv   r   r   r   r   r   r   r   r   r&   )doc_keyr   rb   r   r|   r}   r   alloweds           r   	_do_checkr   K  s    
-CMM#q!E
5zQB7)1MN!IzY
3DdL1G	N7)
$%	N<.
)*	
	T\\N
+,	T[[M
*+	VDOO4TR
ST	T../
01	N9X>
?@r   c            
        dd l } 	 ddlm}  |        dt         d}| j
                  dd  }|st        |       y|d   }|dk(  rt                y|dk(  r-t        |      dk  rt        d	       yt        |d   |d
          y|dk(  r/t        |      d
k  rt        d       y|d   }|dk(  rLt        |      dk  rt        d       yt        |d
         rt        d|d
           yt        d|d
           y|dk(  rLt        |      dk  rt        d       yt        |d
         rt        d|d
           yt        d|d
           y|dk(  rZt               }|st        d       t        |j                               D ]&  \  }}t        d| d|j                  dd              ( yt        d|        yt        d| d       t        |       y# t        $ r Y w xY w)Nr   )load_hermes_dotenva  Usage: python -m gateway.platforms.feishu_comment_rules <command> [args]

Commands:
  status                              Show rules config and pairing state
  check <fileType:token> <user>        Simulate access check
  pairing add <user_open_id>           Add user to pairing-approved list
  pairing remove <user_open_id>        Remove user from pairing-approved list
  pairing list                         List pairing-approved users

Rules config file: z
  Edit this JSON file directly to configure policies and document rules.
  Changes take effect on the next comment event (no restart needed).
rq   statuscheckru   z0Usage: check <fileType:fileToken> <user_open_id>rr   r   z'Usage: pairing <add|remove|list> [args]addz!Usage: pairing add <user_open_id>zAdded: zAlready approved: removez$Usage: pairing remove <user_open_id>z	Removed: zNot in approved list: rL   z(no approved users)r   z  approved_at=r   ?zUnknown pairing subcommand: zUnknown command: 
)syshermes_cli.env_loaderr   	Exceptionr   argvr   r   r   r   r   r   r   r   rW   rQ   )	r   r   usageargscmdsubr   r   r   s	            r   _mainr   ^  s2   <
		 )\ *Q	Q 
 88AB<De
q'C
hT Q 
t9q=DE$q'47#H E 
		t9q=;<1g%<4y1}9:47#Qy)*0 - *47)45, + H_4y1}<=d1g&	$q'+,   .tAwi89  F]#~H+,#HNN$45 N	T3%~dhh}c.J-KLMN  067!#b)*eG  s   G 	GG__main__)rN   r   rE   r   )rN   r#   rE   r   )rE   r   )rb   r   rE   r   ) )
rb   r   r|   r   r}   r   r~   r   rE   r%   )rE   r   )rD   r#   rE   None)r   r   rE   r   )rE   zDict[str, Any])r   r%   r   r   rE   r   )rE   r   )r   r   r   r   rE   r   )rE   int)-r   
__future__r   r:   loggingr   dataclassesr   r   pathlibr   typingr   r   r	   hermes_constantsr
   	getLoggerr   r?   r   r   rS   r   r   r%   r(   rV   r   rO   rT   r[   rc   r   r   r   r   r   r   r   r   r   r   r   exitr   r   r   <module>r      s~   #    (  & & ,			8	$ !<<
 #@@ + $+ + + $L L L $   D :&\*
V:= 	/	// / 	/
 /l @.BA&JZ zCHHUW r   