ó
¿È—Pc           @   s£  d  Z  d d l Z d d l Z d d l Z d d l Z d d l Z d d l Td d l m Z m	 Z	 m
 Z
 d d l m Z d d l m Z d d l m Z m Z d d l m Z d d	 l m Z d d
 l m Z e d d d ƒe	 d d e ƒe	 d ƒ e	 d d d ƒe	 d d d ƒe	 d ƒ e	 d d d ƒe	 d d d ƒe	 d ƒ e	 d ƒ e	 d ƒ e	 d ƒ e	 d ƒ e	 d d d ƒf g Z d „  Z d e f d  „  ƒ  YZ d S(!   s÷  A simple captcha to allow anonymous ticket changes as long as the user solves
a math problem.

I thought that the ITicketManipulator prepare_ticket method would be the place
to add extra HTML stuff to the ticket page, but it seems to be for future use
only.  The only way I found that I could add to the HTML was by modifying the
Genshi template using the ITemplateStreamFilter.

I looked at http://trac-hacks.org/wiki/BlackMagicTicketTweaksPlugin for help
trying to understand how the Genshi transformation stuff worked.

Database setup borrowed from BSD licensed TicketModerator by John D. Siirola at
Sandia National Labs.  See http://trac-hacks.org/wiki/TicketModeratorPlugin

Author: Rob McMullen <robm@users.sourceforge.net>
License: Same as Trac itself
iÿÿÿÿN(   t   *(   t   Tablet   Columnt   Index(   t   IEnvironmentSetupParticipant(   t   ITicketManipulator(   t   ITemplateStreamFiltert   IRequestHandler(   t   IWikiPageManipulator(   t   tag(   t   Transformert   mathcaptcha_historyt   keyt   idt   auto_incrementt   ipt	   submittedt   typet   intt   left_operandt   operatort   right_operandt   solutiont   incorrect_solutiont   authort   summaryt   textt   hreft   solvedt   booleanc         C   s:   d d l  m } |  j | } | j ƒ  d } | j | ƒ S(   sA    Convenience function to get the to_sql for the active connector.iÿÿÿÿ(   t   DatabaseManageri    (   t   trac.db.apiR   t
   componentst   _get_connectort   to_sql(   t   envt   tableR   t   dmt   dc(    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyR"   6   s    t   MathCaptchaPluginc           B   s   e  Z e e e e e e ƒ d  Z d Z	 d Z
 d Z d Z d „  Z d „  Z d „  Z d „  Z d	 „  Z d
 „  Z d „  Z d „  Z d „  Z d „  Z d „  Z d „  Z d „  Z d „  Z d d „ Z d „  Z d „  Z d „  Z d „  Z  d „  Z! d „  Z" d „  Z# d „  Z$ d „  Z% d „  Z& RS(   iX  i   i   iöX i   c         C   sH   t  j ƒ  } |  j | |  j ƒ | j d d |  j f ƒ t  j ƒ  d  S(   Ns   INSERT INTO system VALUES s   ('mathcaptcha_version', '%s')(   t   dbt   cursort   _create_tablesR#   t   executet
   db_versiont   commit(   t   selfR)   (    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyt   environment_createdX   s    	c         C   s"   | j  ƒ  } |  j | ƒ |  j k S(   s‡   Called when Trac checks whether the environment needs to be 
        upgraded.  Returns `True` if upgrade is needed, `False` otherwise.(   R)   t   _get_versionR,   (   R.   R(   R)   (    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyt   environment_needs_upgrade`   s    c         C   s#  | j  ƒ  } |  j | ƒ } | |  j k r. d S| d k rl |  j | |  j ƒ | j d d |  j f ƒ d S| d k rít d d d ƒt d d	 t ƒt d
 ƒ t d d d ƒt d d d ƒt d ƒ t d d d ƒt d d d ƒt d ƒ t d ƒ t d ƒ t d ƒ t d ƒ t d d d ƒf } | j d d ƒ x' t	 |  j | ƒ D] } | j | ƒ qKWd j
 d
 d d d d d d d d d g
 ƒ } d j
 d
 d d d d d d d d d g
 ƒ } | j d | | f ƒ | j d  ƒ | d 7} n  | j d! | f ƒ | |  j k rt d" ƒ ‚ n  d S(#   sŒ   Actually perform an environment upgrade, but don't commit as that
        is done by the common upgrade procedure when all plugins are done.Ni    s   INSERT INTO system VALUES s   ('mathcaptcha_version', '%s')i   R   R   R   R   R   R   R   R   R   R   R   R   R   R   R   R   R   R   R   s*   ALTER TABLE mathcaptcha_history RENAME TO t   mathcaptcha_history_OLDs   , t   incorrect_authort   incorrect_summaryt   incorrect_textsK   INSERT INTO mathcaptcha_history (%s) SELECT %s FROM mathcaptcha_history_OLDs"   DROP TABLE mathcaptcha_history_OLDs;   UPDATE system SET value=%s WHERE name='mathcaptcha_version's*   MathCaptcha failed to upgrade environment.(   R)   R0   R,   R*   R#   R+   R   R   t   TrueR"   t   joint	   TracError(   R.   R(   R)   t   vert   table_v2t   stmtt
   old_fieldst
   new_fields(    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyt   upgrade_environmentf   sT    												c         C   sT   yE d d } |  j  j | ƒ | j | ƒ t | j ƒ  d p@ d ƒ SWn d SXd  S(   Ns$   SELECT value FROM system WHERE name=s   'mathcaptcha_version'i    (   t   logt   debugR+   R   t   fetchone(   R.   R)   t   sql(    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyR0   ¢   s    c         C   s<   x5 t  D]- } x$ t | | ƒ D] } | j | ƒ q Wq Wd S(   s]    Creates the basic tables as defined by schema.
        using the active database connector. N(   t   schemaR"   R+   (   R.   R)   R#   R$   R;   (    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyR*   ¬   s    c         C   sb   t  j d d ƒ | d <d | d <t  j d d ƒ | d <| d | d | d <d | d | d f S(	   sU  Hook for generation of the math problem.
        
        As a side effect, should populate the values dict with the
        'left_operand', 'operator', 'right_operand' and 'solution' keys that
        will be placed in the database.
        
        Returns a text version of the math problem that is presented to the
        user.
        i   i
   R   t   addR   R   R   s   adding %d and %d(   t   randomt   randint(   R.   t   values(    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyt   create_math_problem´   s
    

c   
      C   sÓ  i  } | j  | d <t t j ƒ  ƒ | d <|  j | ƒ } | j j d ƒ | d <| j j d ƒ | d <|  j | ƒ | d <| j | d <|  j j	 ƒ  } | j
 ƒ  } | j ƒ  } | j d d	 j | ƒ d	 j d
 g t | ƒ ƒ f g  | D] } | | ^ qä ƒ | j | d ƒ } | j ƒ  |  j j j d | j  | j | j | j | | d | d | d | d f	 ƒ t j ƒ  t j d | ƒ t j d d d d d d d d ƒ t j d d d d d t | |  j ƒ ƒ ƒ }	 |	 S(   s\   Returns the Genshi tags for the new HTML elements representing the
        Captcha.
        R   R   R   t   field_summaryR   R   R   s0   INSERT INTO mathcaptcha_history (%s) VALUES (%s)t   ,s   %sR   s:   %s %s %s%s: generating math solution: id=%d, %d %s %d = %dR   R   R   R   s*   Anonymous users are allowed to post by %s R   t   namet   emailt   class_t
   textwidgett   sizet   5t   hiddent   urlt   value(   t   remote_addrR   t   timeRH   t   argst   gett   get_failed_attempt_textt	   path_infoR#   t
   get_db_cnxR)   t   keysR+   R7   t   lent   get_last_idR-   R?   R@   t   remote_usert	   base_pathR	   t   divt   labelt   inputt   strt	   id_offset(
   R.   t   reqRG   t   math_problem_textR(   R)   t   fieldsRK   R   t   content(    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyt   get_contentÄ   s*    	
N	ac         C   s   | j  d k S(   sÊ   Hook to determine whether or not the math captcha should be shown.
        
        Currently, only anonymous users get shown the captcha, but this could
        be modified for local purposes.
        t	   anonymous(   t   authname(   R.   Re   (    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyt   is_validation_neededë   s    c         C   s}   |  j  j ƒ  } | j ƒ  } | j d | j f ƒ d } x6 | D]. } | d d  k	 r> | d r> | d 7} q> q> W| |  j k S(   Ns6   SELECT id, solved FROM mathcaptcha_history WHERE ip=%si    i   (   R#   RZ   R)   R+   RT   t   Nonet   ban_after_failed_attempts(   R.   Re   R(   R)   t   failedt   row(    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyt	   is_bannedó   s    c         C   sÅ  t  | j j d ƒ ƒ |  j } |  j | ƒ r… |  j j j d | j | j	 | j
 | j |  j f ƒ |  j | | d ƒ t d ƒ ‚ n  |  j j ƒ  } d d d d d	 d
 g } | j ƒ  } | j d d j | ƒ | f ƒ | j ƒ  } | s|  j j j d | f ƒ d g Si  } x, t t | ƒ ƒ D] } | | | | | <q$W| d |  j t j ƒ  k  rdd g S| j j d ƒ }	 |  j | | |	 ƒ }
 |
 r±|  j | | |	 ƒ |  j ƒ  n |  j | | ƒ |
 S(   s–   Validates the user (or spammer) input
        
        Uses the database storage to compare the user input with the correct
        solution.
        RR   s+   %s %s %s%s: Banned after %d failed attemptss   IP IS NOW BANNED!s   Too many failed attemptsR   R   R   R   R   R   s0   SELECT %s FROM mathcaptcha_history WHERE id=%%s RJ   s&   id=%d not found in mathcaptcha_historys   Invalid key in HTMLs3   Took too long to submit page.  Please submit again.RL   N(   Ns   Invalid key in HTML(   Ns3   Took too long to submit page.  Please submit again.(   R   RV   RW   Rd   Rq   R#   R?   t   errorRT   R^   R_   RY   Rn   t   store_failed_attemptt   RuntimeErrorRZ   R)   R+   R7   RA   Rm   t   rangeR\   t   timeoutRU   t   verify_solutiont   clean_historyt   store_successful_attempt(   R.   Re   R   R(   Rg   R)   Rp   RG   t   it   user_solutionRr   (    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyt   validate_mathcaptchaý   s4    5	c         C   sE  yá t  | ƒ } | d | k ru |  j j j d | j | j | j | j | | j j	 d ƒ |  j
 | ƒ f ƒ g  } nk |  j j j d | j | j | j | j | d | d | d | | j j	 d ƒ |  j
 | ƒ f
 ƒ d g } Wn] |  j j j d	 | j | j | j | j | | j j	 d ƒ |  j
 | ƒ f ƒ d g } n X| S(   NR   s0   %s %s %s%s: Solution: '%s' author=%s comment:
%sR   sH   %s %s %s%s: Error in math solution: %d %s %d != %s author=%s comment:
%sR   R   R   s5   Incorrect solution -- try solving the equation again!s2   %s %s %s%s: Bad digits: '%s' author=%s comment:
%ss_   Anonymous users are only allowed to post by solving the math problem at the bottom of the page.(   Ns5   Incorrect solution -- try solving the equation again!(   Ns_   Anonymous users are only allowed to post by solving the math problem at the bottom of the page.(   R   R#   R?   R@   RT   R^   R_   RY   RV   RW   RX   Rr   Rm   (   R.   Re   RG   R{   R   Rr   (    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyRw   0  s    M	bMc         C   sl   |  j  j ƒ  } | j ƒ  } | j d | | j j d ƒ | j j d ƒ |  j | ƒ t | f ƒ | j ƒ  d  S(   Nsk   UPDATE mathcaptcha_history SET incorrect_solution=%s, author=%s, summary=%s, text=%s, solved=%s WHERE id=%sR   RI   (	   R#   RZ   R)   R+   RV   RW   RX   t   FalseR-   (   R.   Re   R   R{   R(   R)   (    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyRs   ?  s    Cc         C   s?   |  j  j ƒ  } | j ƒ  } | j d t | f ƒ | j ƒ  d  S(   Ns4   UPDATE mathcaptcha_history SET solved=%s WHERE id=%s(   R#   RZ   R)   R+   R6   R-   (   R.   Re   R   R(   R)   (    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyRy   E  s    c         C   sT   d } | j  j d ƒ } | r+ | | 7} n  | j  j d ƒ } | rP | | 7} n  | S(   Nt    t   field_descriptiont   comment(   RV   RW   (   R.   Re   R   t   field(    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyRX   K  s    c         C   sp   | d  k r |  j } n  t j ƒ  | d d d } |  j j ƒ  } | j ƒ  } | j d | f ƒ | j ƒ  d  S(   Ni   i<   s2   DELETE FROM mathcaptcha_history WHERE submitted<%s(   Rm   t   clearout_daysRU   R#   RZ   R)   R+   R-   (   R.   t   dayst
   older_thanR(   R)   (    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyRx   U  s    	
c         C   s   t  d ƒ ‚ d  S(   Ns   Too many failed attempts(   Rt   (   R.   Re   (    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyt   show_banneda  s    c         C   s@  t  } | d d k r|  j | ƒ rg |  j j j d | j | j | j | j f ƒ t	 j
 d ƒ } | S| j } |  j j j | ƒ |  j j j | ƒ | d k rð d | k rÀ d | j k } qd | k rd	 | j k pç d
 | j k } qq| d k rd | j k } qn  | r<| t d ƒ j |  j | ƒ ƒ B} n  | S(   s¬  Return a filtered Genshi event stream, or the original unfiltered
        stream if no match.

        `req` is the current request object, `method` is the Genshi render
        method (xml, xhtml or text), `filename` is the filename of the template
        to be rendered, `stream` is the event stream and `data` is the data for
        the current template.

        See the Genshi documentation for more information.
        Rk   Rj   s    %s %s %s%s: IP banned as spammers   System offline.s   ticket.htmlt	   newtickett   TICKET_CREATEt   tickett   TICKET_MODIFYt   TICKET_APPENDs   wiki_edit.htmlt   WIKI_MODIFYs   //div[@class="buttons"](   R}   Rq   R#   R?   R@   RT   R^   R_   RY   R	   Ra   t   permR
   t   beforeRi   (   R.   Re   t   methodt   filenamet   streamt   datat   add_captchaR   (    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyt   filter_streamf  s&    /	$%c         C   s    |  j  | ƒ r |  j | ƒ Sg  S(   s-  Validate a ticket after it's been populated from user input.
        
        Must return a list of `(field, message)` tuples, one for each problem
        detected. `field` can be `None` to indicate an overall problem with the
        ticket. Therefore, a return value of `[]` means everything is OK.(   Rl   R|   (   R.   Re   Rˆ   (    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyt   validate_ticket  s    c         C   s   d  S(   N(    (   R.   Re   t   pageRg   (    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyt   prepare_wiki_page™  s    c         C   s    |  j  | ƒ r |  j | ƒ Sg  S(   s0  Validate a wiki page after it's been populated from user input.
        
        Must return a list of `(field, message)` tuples, one for each problem
        detected. `field` can be `None` to indicate an overall problem with the
        ticket. Therefore, a return value of `[]` means everything is OK.(   Rl   R|   (   R.   Re   R•   (    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyt   validate_wiki_pageœ  s    c         C   s   t  j d | j ƒ S(   Ns;   /mathcaptcha-(attempts|clear|successful)(?:_trac)?(?:/.*)?$(   t   ret   matchRY   (   R.   Re   (    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyt   match_request¨  s    c         C   sx   | j  j d ƒ t j d | j ƒ } | r; |  j | ƒ n, t j d | j ƒ } | rg |  j | ƒ d  S|  j | ƒ d  S(   Nt
   TRAC_ADMINs%   /mathcaptcha-clear(?:_trac)?(?:/.*)?$s*   /mathcaptcha-successful(?:_trac)?(?:/.*)?$(   RŒ   t   assert_permissionR˜   R™   RY   t   process_cleart   process_successfult   process_attempts(   R.   Re   t   matches(    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyt   process_request«  s    c         C   s   |  j  d ƒ d  S(   Ni    (   Rx   (   R.   Re   (    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyR   ¸  s    c   	   	   C   s9  | j  d ƒ | j d d ƒ |  j j ƒ  } d d d d d d	 d
 d g } | j ƒ  } | j d d j | ƒ ƒ d d j | ƒ } g  } x_ | D]W } | d d  k	 r | d r | j d d j g  | D] } t	 | ƒ ^ qÇ ƒ ƒ q q W| d j | ƒ d 7} | j d t	 t
 | ƒ ƒ ƒ | j ƒ  | j | ƒ d  S(   NiÈ   s   Content-Types	   text/htmlR   R   R   R   R   R   R   R   s5   SELECT %s FROM mathcaptcha_history ORDER BY submittedRJ   s#   <table border><tr><th>%s</th></tr>
s	   </th><th>iÿÿÿÿs   <tr><td>%s</td></tr>
s	   </td><td>s   
s   </table>s   Content-length(   t   send_responset   send_headerR#   RZ   R)   R+   R7   Rm   t   appendRc   R\   t   end_headerst   write(	   R.   Re   R(   Rg   R)   t   htmlt   linesRp   Rz   (    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyRŸ   »  s     	:
c   
      C   s\  | j  d ƒ | j d d ƒ |  j j ƒ  } d d d d d d	 d
 d g } | j ƒ  } | j d d j | ƒ ƒ d d j | d  ƒ } g  } x~ | D]v } | d r” t | d  ƒ } t j	 t j
 | d ƒ ƒ | d <| j d d j g  | D] }	 t |	 ƒ ^ qê ƒ ƒ q” q” W| d j | ƒ d 7} | j d t t | ƒ ƒ ƒ | j ƒ  | j | ƒ d  S(   NiÈ   s   Content-Types	   text/htmlR   R   R   R   R   R   R   R   s5   SELECT %s FROM mathcaptcha_history ORDER BY submittedRJ   s#   <table border><tr><th>%s</th></tr>
s	   </th><th>iÿÿÿÿi   s   <tr><td>%s</td></tr>
s	   </td><td>s   
s   </table>s   Content-length(   R¢   R£   R#   RZ   R)   R+   R7   t   listRU   t   asctimet	   localtimeR¤   Rc   R\   R¥   R¦   (
   R.   Re   R(   Rg   R)   R§   R¨   Rp   RG   Rz   (    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyRž   Î  s$    	
 :
N('   t   __name__t
   __module__t
   implementsR   R   R   R   R   Rv   R‚   R,   Rd   Rn   R/   R1   R>   R0   R*   RH   Ri   Rl   Rq   R|   Rw   Rs   Ry   RX   Rm   Rx   R…   R“   R”   R–   R—   Rš   R¡   R   RŸ   Rž   (    (    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyR'   >   s@   
			<	
			'		
	3				
		'							(   t   __doc__R˜   RE   t   sysRU   t   urllibt	   trac.coret   trac.db.schemaR   R   R   t   trac.envR   t   trac.ticket.apiR   t   trac.web.apiR   R   t   trac.wiki.apiR   t   genshi.builderR	   t   genshi.filters.transformR
   R6   RC   R"   t	   ComponentR'   (    (    (    s1   /var/www/trac/MetaModelica/plugins/MathCaptcha.pyt   <module>   s:   
								