Changeset 250


Ignore:
Timestamp:
07/19/07 16:09:27 (5 years ago)
Author:
marc
Message:

0.2.1 - Backported a bug to fix serious security bug in which /screenshot prompted no auth. Also fixed sending the wrong Content-Lenght for the screenshot.

Location:
branches/release/0.2
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • branches/release/0.2/ChangeLog

    r218 r250  
     10.2.1:  
     2 * Fixed a serious security bug in which /screenshot prompted no auth. 
     3 Also fixed sending the wrong Content-Lenght for the screenshot. 
     4 
    150.2 (I've been watching you...): 
    26 * Rewrote the screenshot module completely to support active window 
  • branches/release/0.2/README

    r219 r250  
    11Itaka 
    2 Version 0.2 
     2Version 0.2.1 
    33 
    44http://itaka.jardinpresente.com.ar 
  • branches/release/0.2/README.Windows

    r157 r250  
    22Twisted (http://tmrc.mit.edu/mirror/twisted/Twisted/2.5/Twisted_NoDocs-2.5.0.win32-py2.5.exe). 
    33 
    4 You can also install all the components manually from their respective sites listed in the Requirements section. 
     4You can also install all the components manually from their respective sites. 
     5 
     6Execute itaka by double clicking the itaka.pyw file. 
  • branches/release/0.2/config.py

    r219 r250  
    3737 
    3838#: Version 
    39 version = "0.2" 
     39version = "0.2.1" 
    4040#: Revision 
    4141revision = "$Rev$" 
  • branches/release/0.2/server.py

    r215 r250  
    191191        # Here we use our own static.Data special child resource because we need Authentication handling. 
    192192        # Otherwise we would just use our own self.add_static_resource. 
    193         self.root = RootResource(self.gui, self.itakaglobals.headhtml + self.configuration['html']['html'] + self.itakaglobals.footerhtml) 
     193 
     194        # Also we create our unique authentication handler this keeps track if the user authenticated or not 
     195        self.authresource = AuthenticatedResource(self.gui) 
     196        self.root = RootResource(self.gui, self.authresource, self.itakaglobals.headhtml + self.configuration['html']['html'] + self.itakaglobals.footerhtml) 
    194197        self.add_child_to_resource('root', '', self.root) 
    195         self.add_child_to_resource('root', 'screenshot', ScreenshotResource(self.gui)) 
     198        self.add_child_to_resource('root', 'screenshot', ScreenshotResource(self.gui, self.authresource)) 
    196199        self.create_site(self.root) 
    197200 
     201class AuthenticatedResource: 
     202    """ 
     203    Helper object to handle authentication for Resources. 
     204    Please read RFC 2617 to understand the HTTP Authentication process 
     205    """ 
     206 
     207    def __init__(self, gui_instance): 
     208        """  
     209        Constructor that inherits code from resource.Resource->static.Data 
     210 
     211        @type gui_instance: instance 
     212        @param gui_instance: An instance of our L{Gui} class 
     213        """ 
     214 
     215        self.gui = gui_instance 
     216        self.configuration = self.gui.configuration 
     217        self.itaka_globals = self.gui.itakaglobals 
     218        self.noauth = self.itaka_globals.headhtml + self.configuration['html']['authfailure'] + self.itaka_globals.footerhtml 
     219        self.request_data_set = False 
     220        self.authenticated = False 
     221 
     222    def set_request_data(self, data, size, type, session_end=False): 
     223        """ 
     224        Set the information about the data we are handling 
     225 
     226 
     227        @type data: string 
     228        @param data: The data to be displayed 
     229 
     230        @type size: string 
     231        @param size: A string for Content-lenght 
     232 
     233        @type type: str 
     234        @param type: The type of data we are serving 
     235 
     236        @type session_end: bool 
     237        @param session_end: Whether this request is the last of a session. This deauthenticates the session. 
     238        """ 
     239 
     240        self.request_data_set = True 
     241        self.data = data 
     242        self.size = size 
     243        self.type = type 
     244        self.session_end = session_end 
     245 
     246    def _prompt_auth(self): 
     247        """ 
     248        Prompt the authorization dialog on the browser 
     249        """ 
     250 
     251        self.authenticated = False 
     252        self.request.setHeader('WWW-Authenticate', 'Basic realm="Itaka Screenshot Server"') 
     253        self.request.setResponseCode(http.UNAUTHORIZED) 
     254        self.request.setHeader('Content-Type', 'text/html; charset=UTF-8') 
     255        self.request.setHeader('Content-Length', str(len(self.noauth))) 
     256        self.request.setHeader('Connection', 'close') 
     257 
     258    def authenticate(self, request): 
     259        """ 
     260        Main handler for authenticated objects. 
     261 
     262        @type request: instance 
     263        @param request: twisted.web.server.Request instance 
     264 
     265        @rtype: bool 
     266        @return: Whether the user authenticated sucessfully.  
     267        """ 
     268 
     269        # Get up to date configuration values everytime there is a request 
     270        self.configuration = self.gui.configuration 
     271 
     272        self.request = request 
     273        self._prompt_auth() 
     274        self.username = self.request.getUser() 
     275        self.password = self.request.getPassword() 
     276        self.ip = self.request.getClientIP() 
     277        self.time = datetime.datetime.now() 
     278        
     279        if not self.username and not self.password: 
     280            self.gui.log.failure(('AuthenticatedResource', 'render'), ('Client provided empty username and password', 'Client %s provided empty username and password' % (self.ip)), 'WARNING') 
     281            self._prompt_auth() 
     282        else: 
     283            if self.username != self.configuration['server']['username'] or self.password != self.configuration['server']['password']: 
     284                self.gui.log.failure(('AuthenticatedResource', 'render'), ('Client provided incorrect username and password', 'Client %s provided incorrect username and password: %s:%s' % (self.ip, self.username, self.password)), 'WARNING') 
     285                self._prompt_auth() 
     286            elif self.username == self.configuration['server']['username'] and self.password == self.configuration['server']['password']: 
     287                self.authenticated = True 
     288        return self.authenticated 
     289 
     290    def return_object_data(self): 
     291        """ 
     292        Returns the data passed by set_request_data() or the default forbidden string if authentication failed. 
     293 
     294        @rtype: str 
     295        @return: self.data or self.noauth 
     296        """ 
     297 
     298        if self.request_data_set and self.authenticated: 
     299            self.request.setResponseCode(http.OK) 
     300            self.request.setHeader('Content-Type', self.type) 
     301            self.request.setHeader('Content-Length', self.size) 
     302            self.request.setHeader('Connection', 'close') 
     303            # Deauthenticate if it's the screenshot (last object request) 
     304            if self.session_end: 
     305                self.authenticated = False 
     306            self.request_data_set = True 
     307            return self.data 
     308        else: 
     309            # No authentication given 
     310            return self.noauth 
     311 
    198312class RootResource(static.Data): 
    199313    """ 
     
    203317    """ 
    204318 
    205     def __init__(self, guiinstance, data, type='text/html; charset=UTF-8'): 
     319    def __init__(self, guiinstance, auth_instance, data, type='text/html; charset=UTF-8'): 
    206320 
    207321        """  
     
    211325        @param guiinstance: An instance of our L{Gui} class. 
    212326 
     327        @type auth_instance: AuthenticatedResource 
     328        @param auth_instance: An instance of our L{AuthenticatedResource} class 
     329 
    213330        @type html: string 
    214331        @param html: The HTML to be displayed. 
     
    219336 
    220337        self.gui = guiinstance 
     338        self.auth = auth_instance 
    221339        self.console = self.gui.console 
    222340        self.itakaglobals = self.gui.itakaglobals 
     
    226344        self.children = {} 
    227345        self.data = data 
     346        self.size = str(len(self.data)) 
    228347        self.type = type 
    229  
    230         self.noauth = self.itakaglobals.headhtml + self.configuration['html']['authfailure'] + self.itakaglobals.footerhtml 
    231  
    232     def _promptAuth(self): 
    233         """ 
    234         Prompt the authorization dialog on the browser. 
    235         """ 
    236  
    237         self.request.setHeader('WWW-Authenticate', 'Basic realm="Itaka Screenshot Server"') 
    238         self.request.setResponseCode(http.UNAUTHORIZED) 
    239         self.request.setHeader('Content-Type', self.type) 
    240         self.request.setHeader('Connection', 'close') 
    241         self.request.setHeader('Content-Length', str(len(self.noauth))) 
    242348 
    243349    def render(self, request): 
     
    251357        # Get up to date configuration values everytime there is a request 
    252358        self.configuration = self.gui.configuration 
    253  
    254359        self.request = request 
    255         self.ip = self.request.getClientIP() 
    256         self.time = datetime.datetime.now() 
    257360 
    258361        if self.configuration['server']['authentication']: 
    259             self._promptAuth() 
    260             self.username = self.request.getUser() 
    261             self.password = self.request.getPassword() 
    262             
    263             if not self.username and not self.password: 
    264                 self.gui.log.failure(('RootResource', 'render'), ('Client provided empty username and password', 'Client %s provided empty username and password' % (self.ip)), 'WARNING') 
    265                 self._promptAuth() 
    266             else: 
    267                 if self.username != self.configuration['server']['username'] or self.password != self.configuration['server']['password']: 
    268                     self.gui.log.failure(('RootResource', 'render'), ('Client provided incorrect username and password', 'Client %s provided incorrect username and password: %s:%s' % (self.ip, self.username, self.password)), 'WARNING') 
    269                     self._promptAuth() 
    270                 elif self.username == self.configuration['server']['username'] and self.password == self.configuration['server']['password']: 
    271                     self.request.setResponseCode(http.OK) 
    272                     self.request.setHeader('Content-Type', self.type) 
    273                     self.request.setHeader('Connection', 'close') 
    274                     self.request.setHeader('Content-Length', str(len(self.data))) 
    275                     return self.data 
     362            if self.auth.authenticate(self.request): 
     363                self.auth.set_request_data(self.data, self.size, self.type) 
     364            return self.auth.return_object_data() 
    276365        else: 
    277366            self.request.setHeader('Content-Type', self.type) 
     367            self.request.setHeader('Content-Length', self.size) 
    278368            self.request.setHeader('Connection', 'close') 
    279             self.request.setHeader('Content-Length', str(len(self.data))) 
    280369            return self.data 
    281  
    282         # No auth given 
    283         return self.noauth 
    284370 
    285371class ScreenshotResource(resource.Resource): 
     
    288374    """ 
    289375 
    290     def __init__(self, guiinstance): 
     376    def __init__(self, guiinstance, auth_instance): 
    291377        """  
    292378        Constructor. 
     
    294380        @type guiinstance: instance 
    295381        @param guiinstance: An instance of our L{Gui} class. 
     382 
     383        @type auth_instance: AuthenticatedResource 
     384        @param auth_instance: An instance of our L{AuthenticatedResource} class 
    296385        """ 
    297386 
    298387        self.gui = guiinstance 
     388        self.auth = auth_instance 
    299389        self.console = self.gui.console 
    300390        self.itakaglobals = self.gui.itakaglobals 
     
    304394        self.counter = 0 
    305395 
    306     def render_GET(self, request): 
    307         """ 
    308         Handle GET requests for screenshot. 
    309  
    310         @type request: instance 
    311         @param request: twisted.web.server.Request instance. 
    312  
    313         @rtype: str 
    314         @return: Screenshot image. 
    315         """ 
    316  
    317         # Get up to date configuration values everytime there is a request 
    318         self.configuration = self.gui.configuration 
    319  
    320         self.request = request 
     396    def get_screenshot(self): 
     397        """ 
     398        Takes a screenshot and notifies the GUI. 
     399        """ 
     400 
    321401        self.ip = self.request.getClientIP() 
    322402        self.time = datetime.datetime.now() 
    323403 
    324         if (self.request.uri == "/screenshot"): 
     404        try: 
     405            self.shot_file = self.screenshot.take_screenshot() 
     406        except error.ItakaScreenshotError, e: 
     407            raise error.ItakaScreenshotError, e 
     408 
     409        self.data = open(self.shot_file, 'rb').read() 
     410        self.size = len(self.data) 
     411        print self.size 
     412        self.counter += 1 
     413 
     414        if self.configuration['server']['notify'] and self.itaka_globals.notify_available: 
     415            import pynotify 
     416            uri = "file://" + (os.path.join(self.itaka_globals.image_dir, "itaka-take.png"))  
     417 
     418            n = pynotify.Notification('Screenshot taken', '%s requested screenshot' % (self.ip), uri) 
     419 
     420            n.set_timeout(1500) 
     421            n.attach_to_status_icon(self.gui.statusIcon) 
     422            n.show() 
     423        self.gui.update_gui(self.counter, self.ip, self.time) 
     424 
     425    def render_GET(self, request): 
     426        """ 
     427        Handle GET requests for screenshot 
     428 
     429        @type request: instance 
     430        @param request: twisted.web.server.Request instance 
     431        """ 
     432 
     433        # Get up to date configuration values everytime there is a request 
     434        self.configuration = self.gui.configuration 
     435        self.request = request 
     436        self.type = "image/" + self.configuration['screenshot']['format'] 
     437 
     438        if self.configuration['server']['authentication']: 
     439            if self.auth.authenticated or self.auth.authenticate(self.request): 
     440                try: 
     441                    self.get_screenshot() 
     442                except error.ItakaScreenshotError: 
     443                    return 
     444                self.auth.set_request_data(self.data, self.size, self.type, True) 
     445            return self.auth.return_object_data() 
     446        else: 
    325447            try: 
    326                 self.shotFile = self.screenshot.take_screenshot() 
     448                self.get_screenshot() 
    327449            except error.ItakaScreenshotError: 
    328450                return 
    329              
    330             self.request.setHeader('Content-Type', "image/" + self.configuration['screenshot']['format']) 
    331             self.request.setHeader('Content-Length', str(len(self.shotFile))) 
     451            self.request.setHeader('Content-Type', self.type) 
     452            self.request.setHeader('Content-Length', self.size) 
    332453            self.request.setHeader('Connection', 'close') 
    333  
    334             self.counter += 1 
    335  
    336             if self.configuration['server']['notify'] and self.itakaglobals.notifyavailable: 
    337                 import pynotify 
    338                 uri = "file://" + (os.path.join(self.itakaglobals.image_dir, "itaka-take.png"))  
    339  
    340                 n = pynotify.Notification("Screenshot taken",  
    341                 "%s requested screenshot" 
    342                 % (self.ip), uri) 
    343  
    344                 n.set_timeout(1500) 
    345                 n.attach_to_status_icon(self.gui.statusIcon) 
    346                 n.show() 
    347  
    348             self.gui.update_gui(self.counter, self.ip, self.time) 
    349  
    350             return open(self.shotFile, 'rb').read() 
     454            return self.data 
Note: See TracChangeset for help on using the changeset viewer.