Changeset 243


Ignore:
Timestamp:
07/16/07 11:12:15 (5 years ago)
Author:
marc
Message:

Fixed #20 (ISA #1). Where /screenshot did not prompt auth.

Location:
trunk
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/ChangeLog

    r241 r243  
    55 * Simplified and improved Makefile 
    66 * Cleaning of the code to try to be at least more compatible with PEP-8. 
     7 
     80.2.1: 
     9 * Fixed a serious security bug in which /screenshot prompted no auth. Also 
     10 fixed sending the wrong Content-Lenght for the screenshot. 
    711 
    8120.2 (I've been watching you...): 
  • trunk/server.py

    r241 r243  
    194194        # Here we use our own static.Data special child resource because we need Authentication handling 
    195195        # Otherwise we would just use our own self.add_static_resource 
    196         self.root = RootResource(self.gui, self.itaka_globals.head_html + self.configuration['html']['html'] + self.itaka_globals.footer_html) 
     196 
     197        # Also we create our unique authentication handler this keeps track if the user authenticated or not 
     198        self.authresource = AuthenticatedResource(self.gui) 
     199        self.root = RootResource(self.gui, self.authresource, self.itaka_globals.head_html + self.configuration['html']['html'] + self.itaka_globals.footer_html) 
    197200        self.add_child_to_resource('root', '', self.root) 
    198         self.add_child_to_resource('root', 'screenshot', ScreenshotResource(self.gui)) 
     201        self.add_child_to_resource('root', 'screenshot', ScreenshotResource(self.gui, self.authresource)) 
    199202        self.create_site(self.root) 
    200203 
    201 class RootResource(static.Data): 
    202     """ 
    203     Main resource with authentication support 
    204  
    205     Please read RFC 2617 to understand the HTTP Authentication process 
    206     """ 
    207  
    208     def __init__(self, gui_instance, data, type='text/html; charset=UTF-8'): 
    209  
     204class AuthenticatedResource: 
     205    """ 
     206    Helper object to handle authentication for resources. 
     207    """ 
     208 
     209    def __init__(self, gui_instance): 
    210210        """  
    211211        Constructor that inherits code from resource.Resource->static.Data 
     
    213213        @type gui_instance: instance 
    214214        @param gui_instance: An instance of our L{Gui} class 
    215  
    216         @type html: string 
    217         @param html: The HTML to be displayed 
     215        """ 
     216 
     217        self.gui = gui_instance 
     218        self.configuration = self.gui.configuration 
     219        self.itaka_globals = self.gui.itaka_globals 
     220        self.noauth = self.itaka_globals.head_html + self.configuration['html']['authfailure'] + self.itaka_globals.footer_html 
     221        self.request_data_set = False 
     222        self.authenticated = False 
     223 
     224    def set_request_data(self, data, size, type, session_end=False): 
     225        """ 
     226        Set the information about the data we are handling 
     227 
     228 
     229        @type data: string 
     230        @param data: The data to be displayed 
     231 
     232        @type size: string 
     233        @param size: A string for Content-Lenght 
    218234 
    219235        @type type: str 
    220236        @param type: The type of data we are serving 
     237 
     238        @type session_end: bool 
     239        @param session_end: Whether this request is the last of a session. This deauthenticates the session. 
     240        """ 
     241 
     242        self.request_data_set = True 
     243        self.data = data 
     244        self.size = size 
     245        self.type = type 
     246        self.session_end = session_end 
     247 
     248    def _prompt_auth(self): 
     249        """ 
     250        Prompt the authorization dialog on the browser 
     251        """ 
     252 
     253        self.authenticated = False 
     254        self.request.setHeader('WWW-Authenticate', 'Basic realm="Itaka Screenshot Server"') 
     255        self.request.setResponseCode(http.UNAUTHORIZED) 
     256        self.request.setHeader('Content-Type', 'text/html; charset=UTF-8') 
     257        self.request.setHeader('Content-Length', str(len(self.noauth))) 
     258        self.request.setHeader('Connection', 'close') 
     259 
     260    def authenticate(self, request): 
     261        """ 
     262        Main handler for authenticated objects. 
     263 
     264        @type request: instance 
     265        @param request: twisted.web.server.Request instance 
     266 
     267        @rtype: bool 
     268        @return: Whether the user authenticated sucessfully.  
     269        """ 
     270 
     271        # Get up to date configuration values everytime there is a request 
     272        self.configuration = self.gui.configuration 
     273 
     274        self.request = request 
     275        self._prompt_auth() 
     276        self.username = self.request.getUser() 
     277        self.password = self.request.getPassword() 
     278        self.ip = self.request.getClientIP() 
     279        self.time = datetime.datetime.now() 
     280        
     281        if not self.username and not self.password: 
     282            self.gui.log.failure(('RootResource', 'render'), (_('Client provided empty username and password'), _('Client %s provided empty username and password') % (self.ip)), 'WARNING') 
     283            self._prompt_auth() 
     284        else: 
     285            if self.username != self.configuration['server']['username'] or self.password != self.configuration['server']['password']: 
     286                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') 
     287                self._prompt_auth() 
     288            elif self.username == self.configuration['server']['username'] and self.password == self.configuration['server']['password']: 
     289                self.authenticated = True 
     290        return self.authenticated 
     291 
     292 
     293    def return_object_data(self): 
     294        """ 
     295        Returns the data passed by set_request_data() or the default forbidden string if authentication failed. 
     296 
     297        @rtype: str 
     298        @return: self.data or self.noauth 
     299        """ 
     300 
     301        if self.request_data_set and self.authenticated: 
     302            self.request.setResponseCode(http.OK) 
     303            self.request.setHeader('Content-Type', self.type) 
     304            self.request.setHeader('Content-Length', self.size) 
     305            self.request.setHeader('Connection', 'close') 
     306            # Deauthenticate if it's the screenshot 
     307            if self.session_end: 
     308                self.authenticated = False 
     309            self.request_data_set = True 
     310            return self.data 
     311        else: 
     312            # No authentication given 
     313            return self.noauth 
     314     
     315class RootResource(static.Data): 
     316    """ 
     317    Main resource with authentication support 
     318 
     319    Please read RFC 2617 to understand the HTTP Authentication process 
     320    """ 
     321 
     322    def __init__(self, gui_instance, auth_instance, data, type='text/html; charset=UTF-8'): 
     323 
     324        """  
     325        Constructor that inherits code from resource.Resource->static.Data 
     326 
     327        @type gui_instance: Gui 
     328        @param gui_instance: An instance of our L{Gui} class 
     329 
     330        @type auth_instance: AuthenticatedResource 
     331        @param auth_instance: An instance of our L{AuthenticatedResource} class 
     332 
     333        @type data: string 
     334        @param data: The HTML to be displayed 
     335 
     336        @type type: str 
     337        @param type: The type of data we are serving 
    221338        """ 
    222339 
    223340        self.gui = gui_instance 
    224         self.console = self.gui.console 
    225         self.itaka_globals = self.gui.itaka_globals 
     341        self.auth = auth_instance 
    226342        self.configuration = self.gui.configuration 
    227343 
     
    229345        self.children = {} 
    230346        self.data = data 
     347        self.size = str(len(self.data)) 
    231348        self.type = type 
    232  
    233         self.noauth = self.itaka_globals.head_html + self.configuration['html']['authfailure'] + self.itaka_globals.footer_html 
    234  
    235     def _promptAuth(self): 
    236         """ 
    237         Prompt the authorization dialog on the browser 
    238         """ 
    239  
    240         self.request.setHeader('WWW-Authenticate', 'Basic realm="Itaka Screenshot Server"') 
    241         self.request.setResponseCode(http.UNAUTHORIZED) 
    242         self.request.setHeader('Content-Type', self.type) 
    243         self.request.setHeader('Connection', 'close') 
    244         self.request.setHeader('Content-Length', str(len(self.noauth))) 
    245349 
    246350    def render(self, request): 
     
    254358        # Get up to date configuration values everytime there is a request 
    255359        self.configuration = self.gui.configuration 
    256  
    257360        self.request = request 
    258         self.ip = self.request.getClientIP() 
    259         self.time = datetime.datetime.now() 
    260361 
    261362        if self.configuration['server']['authentication']: 
    262             self._promptAuth() 
    263             self.username = self.request.getUser() 
    264             self.password = self.request.getPassword() 
    265             
    266             if not self.username and not self.password: 
    267                 self.gui.log.failure(('RootResource', 'render'), (_('Client provided empty username and password'), _('Client %s provided empty username and password') % (self.ip)), 'WARNING') 
    268                 self._promptAuth() 
    269             else: 
    270                 if self.username != self.configuration['server']['username'] or self.password != self.configuration['server']['password']: 
    271                     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') 
    272                     self._promptAuth() 
    273                 elif self.username == self.configuration['server']['username'] and self.password == self.configuration['server']['password']: 
    274                     self.request.setResponseCode(http.OK) 
    275                     self.request.setHeader('Content-Type', self.type) 
    276                     self.request.setHeader('Connection', 'close') 
    277                     self.request.setHeader('Content-Length', str(len(self.data))) 
    278                     return self.data 
     363            if self.auth.authenticate(self.request): 
     364                self.auth.set_request_data(self.data, self.size, self.type) 
     365            return self.auth.return_object_data() 
    279366        else: 
    280367            self.request.setHeader('Content-Type', self.type) 
     368            self.request.setHeader('Content-Length', self.size) 
    281369            self.request.setHeader('Connection', 'close') 
    282             self.request.setHeader('Content-Length', str(len(self.data))) 
    283370            return self.data 
    284371 
    285         # No auth given 
    286         return self.noauth 
    287372 
    288373class ScreenshotResource(resource.Resource): 
     
    291376    """ 
    292377 
    293     def __init__(self, gui_instance): 
     378    def __init__(self, gui_instance, auth_instance): 
    294379        """  
    295380        Constructor 
    296381 
    297         @type gui_instance: instance 
     382        @type gui_instance: Gui 
    298383        @param gui_instance: An instance of our L{Gui} class 
     384 
     385        @type gui_instance: AuthenticatedResource 
     386        @param gui_instance: An instance of our L{AuthenticatedResource} class 
    299387        """ 
    300388 
    301389        self.gui = gui_instance 
     390        self.auth = auth_instance 
    302391        self.console = self.gui.console 
    303392        self.itaka_globals = self.gui.itaka_globals 
     
    307396        self.counter = 0 
    308397 
     398 
     399    def take_shot(self): 
     400        """ 
     401        Takes a screenshot and notify the GUI. 
     402        """ 
     403 
     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 = str(os.stat(self.shot_file).st_size) 
     411        self.counter += 1 
     412 
     413        if self.configuration['server']['notify'] and self.itaka_globals.notify_available: 
     414            import pynotify 
     415            uri = "file://" + (os.path.join(self.itaka_globals.image_dir, "itaka-take.png"))  
     416 
     417            n = pynotify.Notification(_('Screenshot taken'), _('%s requested screenshot' % (self.ip)), uri) 
     418 
     419            n.set_timeout(1500) 
     420            n.attach_to_status_icon(self.gui.statusIcon) 
     421            n.show() 
     422        self.gui.update_gui(self.counter, self.ip, self.time) 
     423 
    309424    def render_GET(self, request): 
    310425        """ 
     
    318433        """ 
    319434 
    320         # Get up to date configuration values everytime there is a request 
    321         self.configuration = self.gui.configuration 
    322  
    323435        self.request = request 
    324         self.ip = self.request.getClientIP() 
    325         self.time = datetime.datetime.now() 
    326  
    327436        if (self.request.uri == "/screenshot"): 
    328             try: 
    329                 self.shot_file = self.screenshot.take_screenshot() 
    330             except error.ItakaScreenshotError: 
    331                 return 
    332              
    333             self.request.setHeader('Content-Type', "image/" + self.configuration['screenshot']['format']) 
    334             self.request.setHeader('Content-Length', str(len(self.shot_file))) 
    335             self.request.setHeader('Connection', 'close') 
    336  
    337             self.counter += 1 
    338  
    339             if self.configuration['server']['notify'] and self.itaka_globals.notify_available: 
    340                 import pynotify 
    341                 uri = "file://" + (os.path.join(self.itaka_globals.image_dir, "itaka-take.png"))  
    342  
    343                 n = pynotify.Notification(_('Screenshot taken'), _('%s requested screenshot' % (self.ip)), uri) 
    344  
    345                 n.set_timeout(1500) 
    346                 n.attach_to_status_icon(self.gui.statusIcon) 
    347                 n.show() 
    348             self.gui.update_gui(self.counter, self.ip, self.time) 
    349  
    350             return open(self.shot_file, 'rb').read() 
     437 
     438            # Get up to date configuration values everytime there is a request 
     439            self.configuration = self.gui.configuration 
     440 
     441            self.ip = self.request.getClientIP() 
     442            self.time = datetime.datetime.now() 
     443            self.type = "image/" + self.configuration['screenshot']['format'] 
     444 
     445            if self.configuration['server']['authentication']: 
     446                if self.auth.authenticated: 
     447                    try: 
     448                        self.take_shot() 
     449                    except error.ItakaScreenshotError: 
     450                        return 
     451                    self.auth.set_request_data(self.data, self.size, self.type, True) 
     452                else: 
     453                    if self.auth.authenticate(self.request): 
     454                        try: 
     455                            self.take_shot() 
     456                        except error.ItakaScreenshotError: 
     457                            return 
     458                        self.auth.set_request_data(self.data, self.size, self.type, True) 
     459                return self.auth.return_object_data() 
     460            else: 
     461                try: 
     462                    self.take_shot() 
     463                except error.ItakaScreenshotError: 
     464                    return 
     465                self.request.setHeader('Content-Type', self.type) 
     466                self.request.setHeader('Content-Length', self.size) 
     467                self.request.setHeader('Connection', 'close') 
     468                return self.data 
Note: See TracChangeset for help on using the changeset viewer.