1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
44 """ Sharedance communication error """
45
48 """ Session ID storing cookie """
49 __implements__ = [_httputil.CookieCodecInterface]
50
52 """
53 Initialization
54
55 :Parameters:
56 - `signkey`: Signation key (or ``None``)
57
58 :Types:
59 - `signkey`: ``str``
60 """
61 self._signkey = signkey
62
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
73 """ Identity decoding (str->str) with integrity checking """
74 if self._signkey:
75 sign, value = value[:11], value[11:]
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
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
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
172 """ Create new empty session """
173 while True:
174 sid = self._gensid()
175 if 0:
176
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
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
203 """ :See: `wtf.app.services.session.StorageInterface.delete` """
204 del self._store[name]
205
207 """ :See: `wtf.app.services.session.StorageInterface.contains` """
208 return name in self._store
209
211 """ :See: `wtf.app.services.session.StorageInterface.need_cookie` """
212 return self._need_cookie
213
215 """ :See: `wtf.app.services.session.StorageInterface.need_store` """
216 return self._need_store
217
219 """ :See: `wtf.app.services.session.StorageInterface.make_cookie` """
220 return _httputil.make_cookie(value=self._sid, **self.cookie)
221
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
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
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
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
347
353
355 """ :See: `session.StorageFactoryInterface.status` """
356 checks = self._sd.check()
357 for check in checks:
358 check['time'] //= 1000
359 return checks
360