Changeset 243
Legend:
- Unmodified
- Added
- Removed
-
trunk/ChangeLog
r241 r243 5 5 * Simplified and improved Makefile 6 6 * Cleaning of the code to try to be at least more compatible with PEP-8. 7 8 0.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. 7 11 8 12 0.2 (I've been watching you...): -
trunk/server.py
r241 r243 194 194 # Here we use our own static.Data special child resource because we need Authentication handling 195 195 # 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) 197 200 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)) 199 202 self.create_site(self.root) 200 203 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 204 class AuthenticatedResource: 205 """ 206 Helper object to handle authentication for resources. 207 """ 208 209 def __init__(self, gui_instance): 210 210 """ 211 211 Constructor that inherits code from resource.Resource->static.Data … … 213 213 @type gui_instance: instance 214 214 @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 218 234 219 235 @type type: str 220 236 @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 315 class 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 221 338 """ 222 339 223 340 self.gui = gui_instance 224 self.console = self.gui.console 225 self.itaka_globals = self.gui.itaka_globals 341 self.auth = auth_instance 226 342 self.configuration = self.gui.configuration 227 343 … … 229 345 self.children = {} 230 346 self.data = data 347 self.size = str(len(self.data)) 231 348 self.type = type 232 233 self.noauth = self.itaka_globals.head_html + self.configuration['html']['authfailure'] + self.itaka_globals.footer_html234 235 def _promptAuth(self):236 """237 Prompt the authorization dialog on the browser238 """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)))245 349 246 350 def render(self, request): … … 254 358 # Get up to date configuration values everytime there is a request 255 359 self.configuration = self.gui.configuration 256 257 360 self.request = request 258 self.ip = self.request.getClientIP()259 self.time = datetime.datetime.now()260 361 261 362 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() 279 366 else: 280 367 self.request.setHeader('Content-Type', self.type) 368 self.request.setHeader('Content-Length', self.size) 281 369 self.request.setHeader('Connection', 'close') 282 self.request.setHeader('Content-Length', str(len(self.data)))283 370 return self.data 284 371 285 # No auth given286 return self.noauth287 372 288 373 class ScreenshotResource(resource.Resource): … … 291 376 """ 292 377 293 def __init__(self, gui_instance ):378 def __init__(self, gui_instance, auth_instance): 294 379 """ 295 380 Constructor 296 381 297 @type gui_instance: instance382 @type gui_instance: Gui 298 383 @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 299 387 """ 300 388 301 389 self.gui = gui_instance 390 self.auth = auth_instance 302 391 self.console = self.gui.console 303 392 self.itaka_globals = self.gui.itaka_globals … … 307 396 self.counter = 0 308 397 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 309 424 def render_GET(self, request): 310 425 """ … … 318 433 """ 319 434 320 # Get up to date configuration values everytime there is a request321 self.configuration = self.gui.configuration322 323 435 self.request = request 324 self.ip = self.request.getClientIP()325 self.time = datetime.datetime.now()326 327 436 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.

