Skip to content

Commit fec7109

Browse files
authored
feat: add session token middleware (#253)
1 parent cbe44df commit fec7109

7 files changed

Lines changed: 168 additions & 21 deletions

File tree

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ matrix:
1818
sudo: false
1919

2020
install:
21-
- pip install -r dev-requirements.txt
21+
- pip install -e .'[test]'
2222
- pip install codecov
2323

2424
script:

leancloud/engine/cookie_session.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# coding: utf-8
2+
3+
from __future__ import absolute_import
4+
from __future__ import division
5+
from __future__ import print_function
6+
7+
8+
from werkzeug import http
9+
from werkzeug.wrappers import Request
10+
from werkzeug.contrib.securecookie import SecureCookie
11+
12+
from leancloud.user import User
13+
14+
15+
__author__ = 'asaka <lan@leancloud.rocks>'
16+
17+
18+
class CookieSessionMiddleware(object):
19+
def __init__(self, app, secret, name='leancloud:session', exluded_paths=None, fetch_user=False):
20+
if not secret:
21+
raise RuntimeError('secret is required')
22+
self.fetch_user = fetch_user
23+
self.secret = secret
24+
self.app = app
25+
self.name = name
26+
self.exluded_paths = [
27+
'/__engine/',
28+
'/1/functions/',
29+
'/1.1/functions/',
30+
'/1/call/',
31+
'/1.1/call/',
32+
]
33+
if exluded_paths:
34+
self.exluded_paths += exluded_paths
35+
36+
def __call__(self, environ, start_response):
37+
self.pre_process(environ)
38+
def new_start_response(status, response_headers):
39+
self.post_process(response_headers)
40+
return start_response(status, response_headers)
41+
return self.app(environ, new_start_response)
42+
43+
def pre_process(self, environ):
44+
request = Request(environ)
45+
for prefix in self.exluded_paths:
46+
if request.path.startswith(prefix):
47+
return
48+
49+
cookie = request.cookies.get(self.name)
50+
if not cookie:
51+
return
52+
53+
session = SecureCookie.unserialize(cookie, self.secret)
54+
55+
if not self.fetch_user:
56+
user = User()
57+
user._session_token = session['session_token']
58+
user.id = session['uid']
59+
User.set_current(user)
60+
else:
61+
user = User.become(session['session_token'])
62+
User.set_current(user)
63+
64+
65+
def post_process(self, headers):
66+
user = User.get_current()
67+
if not user:
68+
return
69+
cookie = SecureCookie({
70+
'uid': user.id,
71+
'session_token': user.get_session_token(),
72+
}, self.secret)
73+
raw = http.dump_cookie(self.name, cookie.serialize())
74+
headers.append((b'Set-Cookie', raw))

leancloud/engine/https_redirect_middleware.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# coding: utf-8
22

3+
from __future__ import absolute_import
4+
from __future__ import division
5+
from __future__ import print_function
6+
37
import os
48

59
from werkzeug.wrappers import Request

leancloud/user.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ def create_followee_query(cls, user_id):
5454
def get_current(cls):
5555
return getattr(thread_locals, 'current_user', None)
5656

57+
@classmethod
58+
def set_current(cls, user):
59+
thread_locals.current_user = user
60+
5761
@classmethod
5862
def become(cls, session_token):
5963
response = client.get('/users/me', params={'session_token': session_token})
@@ -95,7 +99,7 @@ def _cleanup_auth_data(self):
9599

96100
def _handle_save_result(self, make_current=False):
97101
if make_current:
98-
thread_locals.current_user = self
102+
User.set_current(self)
99103
self._cleanup_auth_data()
100104
# self._sync_all_auth_data()
101105
self._attributes.pop('password', None)
@@ -142,7 +146,7 @@ def logout(self):
142146
if not self.is_current:
143147
return
144148
self._cleanup_auth_data()
145-
thread_locals.current_user = None
149+
del thread_locals.current_user
146150

147151
@classmethod
148152
def login_with_mobile_phone(cls, phone_number, password):

setup.py

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,15 @@
55

66
here = path.abspath(path.dirname(__file__))
77

8-
extra_require = {
9-
'dev': ['sphinx'],
10-
'test': ['nose', 'coverage', 'wsgi_intercept'],
11-
}
12-
13-
if sys.version_info.major == 2:
14-
extra_require['test'].append('typing')
8+
install_requires=[
9+
'arrow',
10+
'iso8601',
11+
'qiniu',
12+
'requests',
13+
'werkzeug',
14+
]
15+
if sys.version_info < (3, 5, 0):
16+
install_requires.append('typing')
1517

1618
setup(
1719
name='leancloud-sdk',
@@ -41,13 +43,10 @@
4143

4244
test_suite='nose.collector',
4345

44-
install_requires=[
45-
'arrow',
46-
'iso8601',
47-
'qiniu',
48-
'requests',
49-
'werkzeug',
50-
],
46+
install_requires=install_requires,
5147

52-
extras_require=extra_require
48+
extras_require = {
49+
'dev': ['sphinx'],
50+
'test': ['nose', 'wsgi_intercept'],
51+
}
5352
)

tests/test_engine.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@
88
import time
99
import json
1010
import requests
11+
import typing
1112

12-
from wsgi_intercept import requests_intercept, add_wsgi_intercept
13-
13+
from wsgi_intercept import requests_intercept
14+
from wsgi_intercept import add_wsgi_intercept
1415

1516
import leancloud
1617
from leancloud import Engine
@@ -22,7 +23,7 @@
2223

2324
__author__ = 'asaka <lan@leancloud.rocks>'
2425

25-
env = None
26+
env = None # type: typing.Dict[str, str]
2627

2728
TEST_APP_ID = os.environ['APP_ID']
2829
TEST_APP_KEY = os.environ['APP_KEY']

tests/test_middlewares.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# coding: utf-8
2+
3+
from __future__ import absolute_import
4+
from __future__ import division
5+
from __future__ import print_function
6+
7+
import os
8+
import time
9+
10+
import requests
11+
from wsgi_intercept import add_wsgi_intercept
12+
from wsgi_intercept import remove_wsgi_intercept
13+
from wsgi_intercept import requests_intercept
14+
15+
from leancloud import user as user_module
16+
from leancloud.engine.cookie_session import CookieSessionMiddleware
17+
18+
19+
HOST, PORT = 'localhost', 80
20+
URL = 'http://{}:{}/'.format(HOST, PORT)
21+
22+
FAKE_USER_DATA = {
23+
'sessionToken': 'qmdj8pdidnmyzp0c7yqil91oc',
24+
'updatedAt': '2015-07-14T02:31:50.100Z',
25+
'phone': '18612340000',
26+
'objectId': '55a47496e4b05001a7732c5f',
27+
'username': 'fool',
28+
'createdAt': '2015-07-14T02:31:50.100Z',
29+
'emailVerified': False,
30+
'mobilePhoneVerified': False,
31+
}
32+
33+
34+
def application(environ, start_response):
35+
start_response('200 OK', [('Content-Type', 'text/plain')])
36+
return [b'hello!']
37+
38+
39+
def setup():
40+
requests_intercept.install()
41+
42+
43+
def teardown():
44+
requests_intercept.uninstall()
45+
46+
47+
def test_cookie_session_middleware():
48+
user = user_module.User()
49+
user._update_data(FAKE_USER_DATA)
50+
user_module.thread_locals.current_user = user
51+
52+
app = CookieSessionMiddleware(application, b'wtf!')
53+
add_wsgi_intercept(HOST, PORT, lambda: app)
54+
55+
response = requests.get(URL)
56+
assert response.cookies['leancloud:session']
57+
58+
del user_module.thread_locals.current_user
59+
60+
requests.get(URL, cookies=response.cookies)
61+
current = user_module.User.get_current()
62+
assert current.id == user.id
63+
assert current.get_session_token() == user.get_session_token()
64+
assert not current._attributes
65+
remove_wsgi_intercept()

0 commit comments

Comments
 (0)