1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 import time
19 import base64
20 import hmac
21 import uuid
22 from datetime import datetime, timedelta
23
24 from twisted.internet import reactor
25 from twisted.web import server
26
27 try:
28 from twisted.web import http
29 except ImportError:
30 from twisted.protocols import http
31
32 from flumotion.common import log
33 from flumotion.component.common.streamer.resources import\
34 HTTPStreamingResource, ERROR_TEMPLATE, HTTP_VERSION
35
36 __version__ = "$Rev: $"
37
38 COOKIE_NAME = 'flumotion-session'
39 NOT_VALID = 0
40 VALID = 1
41 RENEW_AUTH = 2
42
43
45 "The requested fragment is not found."
46
47
49 "The requested fragment is not available."
50
51
53 "The requested playlist is not found."
54
55
57 "The requested key is not found."
58
59
103
104
106
107 HTTP_NAME = 'FlumotionAppleHTTPLiveServer'
108 HTTP_SERVER = '%s/%s' % (HTTP_NAME, HTTP_VERSION)
109
110 logCategory = 'fragmented-resource'
111
112 - def __init__(self, streamer, httpauth, secretKey, sessionTimeout):
121
123 if not mountPoint.startswith('/'):
124 mountPoint = '/' + mountPoint
125 if not mountPoint.endswith('/'):
126 mountPoint = mountPoint + '/'
127 self.mountPoint = mountPoint
128
131
135
144
146
147 for cookie in request.cookies:
148 if cookie.startswith('%s=%s' % (COOKIE_NAME, cookie)):
149 self.log("delete old cookie for session ID=%s", sessionID)
150 request.cookies.remove(cookie)
151
152 if authResponse and authResponse.duration != 0:
153 authExpiracy = time.mktime((datetime.utcnow() +
154 timedelta(seconds=authResponse.duration)).timetuple())
155 else:
156 authExpiracy = 0
157
158
159 token = self._generateToken(sessionID, request.getClientIP(),
160 authExpiracy)
161 request.addCookie(COOKIE_NAME, token, path=self.mountPoint)
162
167
169 uid = request.session and request.session.uid or None
170 return {'uid': uid}
171
173 """
174 From t.w.s.Request.getSession()
175 Associates the request to a session using the 'flumotion-session'
176 cookie and updates the session's timeout.
177 If the authentication has expired, re-authenticates the session and
178 updates the cookie with the new authentication's expiracy time.
179 If the cookie is not valid (bad IP or bad signature) or the session
180 has expired, it creates a new session.
181 """
182
183 def processAuthentication(response):
184 if response is None or response.duration == 0:
185 authExpiracy = 0
186 else:
187 authExpiracy = time.mktime((datetime.utcnow() +
188 timedelta(seconds=response.duration)).timetuple())
189 self._createSession(request, authExpiracy)
190
191 if not request.session:
192 cookie = request.getCookie(COOKIE_NAME)
193 if cookie:
194
195 cookieState, sessionID, authExpiracy = \
196 self._cookieIsValid(cookie, request.getClientIP(),
197 request.args.get('GKID', [None])[0])
198 if cookieState != NOT_VALID:
199
200 try:
201
202 request.session = request.site.getSession(sessionID)
203 except KeyError:
204
205 self._createSession(request, authExpiracy, sessionID)
206 self.log("replicating session %s.", sessionID)
207 if cookieState == RENEW_AUTH:
208
209 self.debug('renewing authentication')
210 d = self.httpauth.startAuthentication(request)
211 d.addCallback(lambda res:
212 self._renewAuthentication(request, sessionID, res))
213 d.addErrback(lambda x: self._delClient(sessionID))
214 return d
215
216
217 if not request.session:
218 self.debug('asked for authentication')
219 d = self.httpauth.startAuthentication(request)
220 d.addCallback(lambda res: processAuthentication(res))
221 d.addErrback(lambda x: None)
222 return d
223
224 request.session.touch()
225
226 - def _createSession(self, request, authExpiracy=None, sessionID=None):
254
256 """
257 Generate a cryptografic token:
258 PAYLOAD = SESSION_ID||:||AUTH_EXPIRACY
259 PRIVATE = CLIENT_IP||:||MOUNT_POINT
260 SIG=HMAC(SECRET,PAYLOAD||:||PRIVATE)
261 TOKEN=BASE64(PAYLOAD||:||SIG)
262 """
263 payload = ':'.join([sessionID, str(authExpiracy)])
264 private = ':'.join([clientIP, self.mountPoint])
265 sig = hmac.new(
266 self.secretKey, ':'.join([payload, private])).hexdigest()
267 return base64.b64encode(':'.join([payload, sig]))
268
270 """
271 Checks whether the cookie is valid against the authentication expiracy
272 time and the signature (and implicitly the client IP and mount point).
273 Returns the state of the cookie among 3 options:
274 VALID: the cookie is valid (expiracy and signature are OK)
275 RENEW_AUTH: the cookie is valid but the authentication has expired
276 NOT_VALID: the cookie is not valid
277 """
278 private = ':'.join([clientIP, self.mountPoint])
279 try:
280 token = base64.b64decode(cookie)
281 payload, sig = token.rsplit(':', 1)
282 sessionID, authExpiracy = payload.split(':')
283 except (TypeError, ValueError):
284 self.debug("cookie is not valid. reason: malformed cookie")
285 return (NOT_VALID, None, None)
286
287 self.log("cheking cookie for client_ip=%s auth_expiracy:%s",
288 clientIP, authExpiracy)
289
290
291 if hmac.new(self.secretKey, ':'.join([payload, private])).hexdigest()\
292 != sig:
293 self.debug("cookie is not valid. reason: invalid signature")
294 return (NOT_VALID, None, None)
295
296 if urlSessionID is not None and urlSessionID != sessionID:
297 self.debug("cookie is not valid. reason: different sessions")
298 return (NOT_VALID, None, None)
299 now = time.mktime(datetime.utcnow().timetuple())
300
301 if float(authExpiracy) != 0 and float(authExpiracy) < now:
302 self.debug("cookie is not valid. reason: authentication expired")
303 return (RENEW_AUTH, sessionID, authExpiracy)
304 self.log("cookie is valid")
305 return (VALID, sessionID, None)
306
314
321
326
341
343 return self.bytesSent
344
346 return self.bytesReceived
347
353
357
366