Package wtf :: Package app :: Package services :: Package session_storage :: Module sharedance
[hide private]
[frames] | no frames]

Source Code for Module wtf.app.services.session_storage.sharedance

  1  # -*- coding: ascii -*- 
  2  # 
  3  # Copyright 2007-2012 
  4  # Andr\xe9 Malo or his licensors, as applicable 
  5  # 
  6  # Licensed under the Apache License, Version 2.0 (the "License"); 
  7  # you may not use this file except in compliance with the License. 
  8  # You may obtain a copy of the License at 
  9  # 
 10  #     http://www.apache.org/licenses/LICENSE-2.0 
 11  # 
 12  # Unless required by applicable law or agreed to in writing, software 
 13  # distributed under the License is distributed on an "AS IS" BASIS, 
 14  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 15  # See the License for the specific language governing permissions and 
 16  # limitations under the License. 
 17  """ 
 18  Sharedance session storage 
 19  ========================== 
 20   
 21  This storage uses sharedance_ as session storage. 
 22   
 23  .. _sharedance: http://sharedance.pureftpd.org/project/sharedance 
 24  """ 
 25  __author__ = u"Andr\xe9 Malo" 
 26  __docformat__ = "restructuredtext en" 
 27   
 28  import datetime as _datetime 
 29  import os as _os 
 30  import sys as _sys 
 31  try: 
 32      import cPickle as _pickle 
 33  except ImportError: 
 34      import pickle as _pickle 
 35   
 36  from wtf import Error 
 37  from wtf import httputil as _httputil 
 38  from wtf import util as _util 
 39  from wtf.app.services import session as _session 
 40  from wtf.ext import sharedance as _sharedance 
41 42 43 -class SharedanceError(Error):
44 """ Sharedance communication error """
45
46 47 -class SessionCookieCodec(object):
48 """ Session ID storing cookie """ 49 __implements__ = [_httputil.CookieCodecInterface] 50
51 - def __init__(self, signkey):
52 """ 53 Initialization 54 55 :Parameters: 56 - `signkey`: Signation key (or ``None``) 57 58 :Types: 59 - `signkey`: ``str`` 60 """ 61 self._signkey = signkey
62
63 - def encode(self, value):
64 """ Identity encoding (str->str) with signature """ 65 if self._signkey: 66 from Crypto.Hash import HMAC 67 value = _sharedance.escape( 68 HMAC.new(self._signkey, value).digest()[:8] 69 ) + value 70 return value
71
72 - def decode(self, value):
73 """ Identity decoding (str->str) with integrity checking """ 74 if self._signkey: 75 sign, value = value[:11], value[11:] # 11 = 8 * 4/3 - padding 76 from Crypto.Hash import HMAC 77 if _sharedance.escape( 78 HMAC.new(self._signkey, value).digest()[:8]) != sign: 79 raise ValueError() 80 return value
81
82 83 -class BoundSharedanceStorage(object):
84 """ 85 Sharedance storage implementation 86 87 :See: `wtf.app.services.session.StorageInterface` 88 """ 89 _SIDLEN = 40 90 91 __implements__ = [_session.StorageInterface] 92 has_cookie, _need_cookie, _need_store = True, False, False 93 _sid, _store = None, None 94
95 - def __init__(self, sharedance, cookie, refresh, init):
96 """ 97 Initialization 98 99 :Parameters: 100 - `sharedance`: Sharedance object 101 - `cookie`: Cookie configuration or ``None`` 102 - `init`: Initialization function (takes self as argument) 103 104 :Types: 105 - `sharedance`: `Sharedance` 106 - `cookie`: ``dict`` 107 - `init`: ``callable`` 108 """ 109 self._sharedance = sharedance 110 self.cookie = cookie.copy() 111 self.codec = self.cookie['codec'] = \ 112 SessionCookieCodec(self.cookie.pop('sign')) 113 114 init(self) 115 116 key, now = '___next_cookie__', _datetime.datetime.utcnow() 117 if key not in self._store or self._store[key] < now: 118 self._need_store, self._store[key] = \ 119 True, now + _datetime.timedelta(seconds=refresh) 120 if self.cookie['max_age']: 121 self._need_cookie = True
122 123 @classmethod
124 - def from_request(cls, sharedance, cookie, refresh, request):
125 """ Create bound storage from request """ 126 def init(self): 127 """ Load session from request """ 128 if request is not None: 129 cookies = request.cookie(self.codec).multi( 130 self.cookie['name'] 131 ) 132 for sid in cookies: 133 if self.create_existing(sid): 134 return 135 self.create_new()
136 return cls(sharedance, cookie, refresh, init)
137 138 @classmethod
139 - def from_sid(cls, sharedance, cookie, refresh, sid):
140 """ Create bound storage from sid """ 141 def init(self): 142 """ Load session from sid """ 143 if not self.create_existing(sid): 144 self.create_new()
145 return cls(sharedance, cookie, refresh, init) 146
147 - def create_existing(self, sid):
148 """ Create session from existing one """ 149 data = None 150 try: 151 data = self._sharedance.fetch(sid) 152 except KeyError: 153 pass 154 except _sharedance.SharedanceError, e: 155 print >> _sys.stderr, "Sharedance: %s" % str(e) 156 if data is None: 157 return False 158 159 try: 160 store = _pickle.loads(data) 161 except (SystemExit, KeyboardInterrupt): 162 raise 163 except: 164 return False 165 166 store['__id__'] = sid 167 store['__is_new__'] = False 168 self._sid, self._store = sid, store 169 return True
170
171 - def create_new(self):
172 """ Create new empty session """ 173 while True: 174 sid = self._gensid() 175 if 0: 176 # Race condition here, but there's no better way :/ 177 try: 178 self._sharedance.fetch(sid) 179 continue 180 except KeyError: 181 pass 182 except _sharedance.SharedanceError, e: 183 print >> _sys.stderr, "Sharedance: %s" % str(e) 184 break 185 self._need_cookie, self._sid = True, sid 186 self._store = {'__id__': sid, '__is_new__': True}
187
188 - def _gensid(self):
189 """ Generate a random session ID """ 190 numbytes, rest = divmod(self._SIDLEN * 3, 4) 191 numbytes += int(bool(rest)) 192 return _sharedance.escape(_os.urandom(numbytes))[:self._SIDLEN]
193
194 - def set(self, name, value):
195 """ :See: `wtf.app.services.session.StorageInterface.set` """ 196 self._store[name] = value
197
198 - def get(self, name):
199 """ :See: `wtf.app.services.session.StorageInterface.get` """ 200 return self._store[name]
201
202 - def delete(self, name):
203 """ :See: `wtf.app.services.session.StorageInterface.delete` """ 204 del self._store[name]
205
206 - def contains(self, name):
207 """ :See: `wtf.app.services.session.StorageInterface.contains` """ 208 return name in self._store
209 213
214 - def need_store(self):
215 """ :See: `wtf.app.services.session.StorageInterface.need_store` """ 216 return self._need_store
217 221
222 - def store_back(self):
223 """ :See: `wtf.app.services.session.StorageInterface.store_back` """ 224 try: 225 store = self._store.copy() 226 if '__id__' in store: 227 del store['__id__'] 228 if '__is_new__' in store: 229 del store['__is_new__'] 230 self._sharedance.store(self._sid, _pickle.dumps(store)) 231 except _sharedance.SharedanceError, e: 232 print >> _sys.stderr, "Sharedance: %s" % str(e)
233
234 - def wipe(self):
235 """ :See: `wtf.app.services.session.StorageInterface.wipe` """ 236 try: 237 if '__new__' not in self._store: 238 self._sharedance.delete(self._sid) 239 return True 240 except _sharedance.SharedanceError, e: 241 print >> _sys.stderr, "Sharedance: %s" % str(e) 242 return False
243
244 245 -class SharedanceStorage(object):
246 """ 247 Sharedance storage factory 248 249 :See: `session.StorageFactoryInterface` 250 251 :CVariables: 252 - `_DEFAULT_HOST`: Default session server host 253 - `_DEFAULT_TIMEOUT`: Default session server timeout 254 255 :Types: 256 - `_DEFAULT_HOST`: ``str`` 257 - `_DEFAULT_TIMEOUT`: ``float`` 258 """ 259 __implements__ = [_session.StorageFactoryInterface] 260 _DEFAULT_HOST = 'localhost' 261 _DEFAULT_TIMEOUT = 10.0 262
263 - def __init__(self, config, opts, args):
264 """ :See: `session.StorageFactoryInterface.__init__` """ 265 # pylint: disable = E1103, R0912, R0915 266 try: 267 section = config['session:sharedance'] 268 except KeyError: 269 section = dict() 270 271 if 'timeout' in section: 272 timeout = max(0.0, float(section.timeout)) 273 else: 274 timeout = self._DEFAULT_TIMEOUT 275 276 if 'weight' in section: 277 weight = max(0, int(section.weight)) 278 else: 279 weight = 1 280 281 if 'compress_threshold' in section: 282 compress_threshold = unicode(section.compress_threshold) and \ 283 int(section.compress_threshold) or None 284 else: 285 compress_threshold = 128 286 287 if 'server' in section: 288 servers = [] 289 for spec in section.server: 290 key = 'session:sharedance %s' % spec 291 spec = _util.parse_socket_spec(unicode(spec), 292 default_port=_sharedance.DEFAULT_PORT) 293 if key in config: 294 subtimeout = \ 295 max(0.0, float(config[key]('timeout', timeout))) 296 subweight = max(0, int(config[key]('weight', weight))) 297 else: 298 subtimeout, subweight = timeout, weight 299 servers.append(_sharedance.SharedanceConnector( 300 spec, compress_threshold=compress_threshold, 301 timeout=subtimeout, weight=subweight, magic=True 302 )) 303 else: 304 servers = [_sharedance.SharedanceConnector( 305 (self._DEFAULT_HOST, _sharedance.DEFAULT_PORT), 306 compress_threshold=compress_threshold, 307 timeout=timeout, weight=1, magic=True 308 )] 309 310 self._sd = _sharedance.Sharedance(servers) 311 312 if 'refresh' in section: 313 refresh = unicode(section.refresh) 314 else: 315 refresh = u'auto' 316 if refresh == u'auto': 317 self._refresh = 60 318 else: 319 self._refresh = int(refresh) 320 321 if 'cookie' in section: 322 cookie = section.cookie 323 else: 324 cookie = dict().get 325 sign = cookie('sign', u'') or None 326 if sign: 327 sign = sign.encode('ascii').decode('base64') 328 domain = cookie('domain', u'') or None 329 if domain: 330 if domain.startswith(u'.'): 331 domain = (u'x' + domain).encode('idna')[1:] 332 else: 333 domain = domain.encode('idna') 334 self._cookie = dict( 335 name=cookie('name', u's').encode('ascii'), 336 max_age=int(cookie('max_age', 0)) or None, 337 path=unicode(cookie('path', u'/')).encode('ascii'), 338 domain=domain, 339 sign=sign, 340 )
341
342 - def __call__(self, request):
343 """ :See: `session.StorageFactoryInterface.__call__` """ 344 return BoundSharedanceStorage.from_request( 345 self._sd, self._cookie, self._refresh, request 346 )
347
348 - def from_sid(self, sid):
349 """ :See: `session.StorageFactoryInterface.from_sid` """ 350 return BoundSharedanceStorage.from_sid( 351 self._sd, self._cookie, self._refresh, sid 352 )
353
354 - def status(self):
355 """ :See: `session.StorageFactoryInterface.status` """ 356 checks = self._sd.check() 357 for check in checks: 358 check['time'] //= 1000 359 return checks
360