Changeset 174


Ignore:
Timestamp:
07/25/12 01:41:34 (12 years ago)
Author:
atzm
Message:
  • dynamic htpasswd loading support
File:
1 edited

Legend:

Unmodified
Added
Removed
  • etherws/trunk/etherws.py

    r173 r174  
    236236        except: 
    237237            traceback.print_exc() 
    238         tornado.ioloop.IOLoop.instance().stop() 
     238        tornado.ioloop.IOLoop.instance().stop()  # XXX: should unregister fd 
    239239 
    240240    def _read(self): 
     
    268268 
    269269class EtherWebSocketClient(DebugMixIn): 
    270     def __init__(self, switch, url, user=None, passwd=None, debug=False): 
     270    def __init__(self, switch, url, cred=None, debug=False): 
    271271        self._switch = switch 
    272272        self._url = url 
     
    275275        self._options = {} 
    276276 
    277         if user and passwd: 
    278             token = base64.b64encode('%s:%s' % (user, passwd)) 
     277        if isinstance(cred, dict) and cred['user'] and cred['passwd']: 
     278            token = base64.b64encode('%s:%s' % (cred['user'], cred['passwd'])) 
    279279            auth = ['Authorization: Basic %s' % token] 
    280280            self._options['header'] = auth 
     
    322322        except: 
    323323            traceback.print_exc() 
    324         tornado.ioloop.IOLoop.instance().stop() 
     324        tornado.ioloop.IOLoop.instance().stop()  # XXX: should unregister fd 
     325 
     326 
     327class Htpasswd(object): 
     328    def __init__(self, path): 
     329        self._path = path 
     330        self._stat = None 
     331        self._data = {} 
     332 
     333    def auth(self, name, passwd): 
     334        passwd = base64.b64encode(hashlib.sha1(passwd).digest()) 
     335        return self._data.get(name) == passwd 
     336 
     337    def load(self): 
     338        old_stat = self._stat 
     339 
     340        with open(self._path) as fp: 
     341            self._stat = os.fstat(fp.fileno()) 
     342 
     343            unchanged = old_stat and \ 
     344                        old_stat.st_ino == self._stat.st_ino and \ 
     345                        old_stat.st_dev == self._stat.st_dev and \ 
     346                        old_stat.st_mtime == self._stat.st_mtime 
     347 
     348            if not unchanged: 
     349                self._data = self._parse(fp) 
     350 
     351    def _parse(self, fp): 
     352        data = {} 
     353        for line in fp: 
     354            line = line.strip() 
     355            if 0 <= line.find(':'): 
     356                name, passwd = line.split(':', 1) 
     357                if passwd.startswith('{SHA}'): 
     358                    data[name] = passwd[5:] 
     359        return data 
     360 
     361 
     362def wrap_basic_auth(handler_class, htpasswd_path): 
     363    if not htpasswd_path: 
     364        return handler_class 
     365 
     366    old_execute = handler_class._execute 
     367    htpasswd = Htpasswd(htpasswd_path) 
     368 
     369    def execute(self, transforms, *args, **kwargs): 
     370        def auth_required(): 
     371            self.stream.write(tornado.escape.utf8( 
     372                'HTTP/1.1 401 Authorization Required\r\n' 
     373                'WWW-Authenticate: Basic realm=etherws\r\n\r\n' 
     374            )) 
     375            self.stream.close() 
     376 
     377        creds = self.request.headers.get('Authorization') 
     378 
     379        if not creds or not creds.startswith('Basic '): 
     380            return auth_required() 
     381 
     382        try: 
     383            name, passwd = base64.b64decode(creds[6:]).split(':', 1) 
     384            htpasswd.load() 
     385 
     386            if not htpasswd.auth(name, passwd): 
     387                return auth_required() 
     388 
     389            return old_execute(self, transforms, *args, **kwargs) 
     390 
     391        except: 
     392            return auth_required() 
     393 
     394    handler_class._execute = execute 
     395    return handler_class 
    325396 
    326397 
     
    361432 
    362433def server_main(args): 
    363     def wrap_basic_auth(cls, users): 
    364         o_exec = cls._execute 
    365  
    366         if not users: 
    367             return cls 
    368  
    369         def execute(self, transforms, *args, **kwargs): 
    370             def auth_required(): 
    371                 self.stream.write(tornado.escape.utf8( 
    372                     'HTTP/1.1 401 Authorization Required\r\n' 
    373                     'WWW-Authenticate: Basic realm=etherws\r\n\r\n' 
    374                 )) 
    375                 self.stream.close() 
    376  
    377             creds = self.request.headers.get('Authorization') 
    378  
    379             if not creds or not creds.startswith('Basic '): 
    380                 return auth_required() 
    381  
    382             try: 
    383                 name, passwd = base64.b64decode(creds[6:]).split(':', 1) 
    384                 passwd = base64.b64encode(hashlib.sha1(passwd).digest()) 
    385  
    386                 if name not in users or users[name] != passwd: 
    387                     return auth_required() 
    388  
    389                 return o_exec(self, transforms, *args, **kwargs) 
    390  
    391             except: 
    392                 return auth_required() 
    393  
    394         cls._execute = execute 
    395         return cls 
    396  
    397     def load_htpasswd(path): 
    398         users = {} 
    399         try: 
    400             with open(path) as fp: 
    401                 for line in fp: 
    402                     line = line.strip() 
    403                     if 0 <= line.find(':'): 
    404                         name, passwd = line.split(':', 1) 
    405                         if passwd.startswith('{SHA}'): 
    406                             users[name] = passwd[5:] 
    407             if not users: 
    408                 raise ValueError('no valid users found') 
    409         except TypeError: 
    410             pass 
    411         return users 
    412  
    413434    realpath(args, 'keyfile', 'certfile', 'htpasswd') 
    414435 
     
    434455    fdb = FDB(ageout=args.ageout, debug=args.debug) 
    435456    switch = SwitchingHub(fdb, debug=args.debug) 
    436     taps = [TapHandler(switch, dev, debug=args.debug) for dev in args.device] 
    437  
    438     handler = wrap_basic_auth(EtherWebSocketHandler, 
    439                               load_htpasswd(args.htpasswd)) 
    440     app = tornado.web.Application([ 
    441         (args.path, handler, {'switch': switch, 'debug': args.debug}), 
    442     ]) 
     457 
     458    handler = wrap_basic_auth(EtherWebSocketHandler, args.htpasswd) 
     459    srv = (args.path, handler, {'switch': switch, 'debug': args.debug}) 
     460    app = tornado.web.Application([srv]) 
    443461    server = tornado.httpserver.HTTPServer(app, ssl_options=ssl_options) 
    444462    server.listen(args.port, address=args.address) 
    445463 
    446     for tap in taps: 
     464    for dev in args.device: 
     465        tap = TapHandler(switch, dev, debug=args.debug) 
    447466        tap.open() 
    448467        ioloop.add_handler(tap.fileno(), tap, ioloop.READ) 
     
    460479        websocket.enableTrace(True) 
    461480 
    462     if not args.insecure: 
     481    if args.insecure: 
     482        websocket._SSLSocketWrapper = \ 
     483            lambda s: ssl.wrap_socket(s) 
     484    else: 
    463485        websocket._SSLSocketWrapper = \ 
    464486            lambda s: ssl.wrap_socket(s, cert_reqs=ssl.CERT_REQUIRED, 
    465487                                      ca_certs=args.cacerts) 
    466     else: 
    467         websocket._SSLSocketWrapper = \ 
    468             lambda s: ssl.wrap_socket(s) 
     488 
     489    if args.ageout <= 0: 
     490        raise ValueError('invalid ageout: %s' % args.ageout) 
    469491 
    470492    if args.user and args.passwd is None: 
    471493        args.passwd = getpass.getpass() 
    472494 
    473     if args.ageout <= 0: 
    474         raise ValueError('invalid ageout: %s' % args.ageout) 
    475  
     495    cred = {'user': args.user, 'passwd': args.passwd} 
    476496    ioloop = tornado.ioloop.IOLoop.instance() 
    477497    fdb = FDB(ageout=args.ageout, debug=args.debug) 
    478498    switch = SwitchingHub(fdb, debug=args.debug) 
    479     taps = [TapHandler(switch, dev, debug=args.debug) for dev in args.device] 
    480  
    481     clients = [EtherWebSocketClient(switch, uri, 
    482                                     args.user, args.passwd, args.debug) 
    483                for uri in args.uri] 
    484  
    485     for client in clients: 
     499 
     500    for uri in args.uri: 
     501        client = EtherWebSocketClient(switch, uri, cred, args.debug) 
    486502        client.open() 
    487503        ioloop.add_handler(client.fileno(), client, ioloop.READ) 
    488504 
    489     for tap in taps: 
     505    for dev in args.device: 
     506        tap = TapHandler(switch, dev, debug=args.debug) 
    490507        tap.open() 
    491508        ioloop.add_handler(tap.fileno(), tap, ioloop.READ) 
Note: See TracChangeset for help on using the changeset viewer.